Skip to main content
The data layer implements the domain layer’s gateways and handles all data operations. It’s organized into specialized modules for different data sources and concerns.

Module Overview

Local

Room database and local storage

Remote

Retrofit API and network calls

Repository

Gateway implementations

Session

DataStore preferences and session

Source

Data source interfaces

Local Module

Module: data:local Package: es.mobiledev.data.local Handles local data persistence using Room database.

AppRoomDatabase

Main Room database class for the application.
@Database(
    entities = [ArticleDbo::class],
    version = 1,
    exportSchema = false
)
abstract class AppRoomDatabase : RoomDatabase() {
    abstract fun articleDao(): ArticleDao
}
entities
Array<KClass<*>>
Database entities: ArticleDbo
version
Int
Database version: 1

ArticleDao

File: data/local/src/main/java/es/mobiledev/data/local/article/dao/ArticleDao.kt:11 Data Access Object for article operations.
@Dao
interface ArticleDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun saveFavoriteArticle(article: ArticleDbo)

    @Delete
    suspend fun removeFavoriteArticle(article: ArticleDbo)

    @Query("SELECT * FROM articles")
    suspend fun getFavoriteArticles(): List<ArticleDbo>

    @Query("SELECT * FROM articles WHERE id = :articleId")
    suspend fun getFavoriteArticleById(articleId: Long): ArticleDbo?
}

Methods

saveFavoriteArticle
suspend function
Inserts or replaces an article in the favorites table
article
ArticleDbo
required
Article database object to save
removeFavoriteArticle
suspend function
Deletes an article from the favorites table
article
ArticleDbo
required
Article to remove
getFavoriteArticles
suspend function
Retrieves all favorite articlesReturns: List<ArticleDbo>
getFavoriteArticleById
suspend function
Retrieves a specific favorite article by ID
articleId
Long
required
Article identifier
Returns: ArticleDbo? - Article if found, null otherwise

ArticleDbo

Database entity for articles.
@Entity(tableName = "articles")
data class ArticleDbo(
    @PrimaryKey val id: Long,
    val title: String,
    val authors: String,  // JSON serialized
    val url: String,
    val imageUrl: String,
    val newsSite: String,
    val summary: String,
    val publishedAt: String,
    val updatedAt: String,
)
The “Dbo” suffix stands for “Database Object”. These objects are separate from domain models (Bo) to maintain separation of concerns.

ArticleLocalDataSourceImpl

Implementation of the local data source.
class ArticleLocalDataSourceImpl(
    private val articleDao: ArticleDao
) : ArticleLocalDataSource {
    override suspend fun saveFavoriteArticle(article: ArticleBo) {
        articleDao.saveFavoriteArticle(article.toDbo())
    }

    override suspend fun removeFavoriteArticle(article: ArticleBo) {
        articleDao.removeFavoriteArticle(article.toDbo())
    }

    override suspend fun getFavoriteArticles(): List<ArticleBo> {
        return articleDao.getFavoriteArticles().map { it.toBo() }
    }

    override suspend fun isArticleFavorite(id: Long): Boolean {
        return articleDao.getFavoriteArticleById(id) != null
    }
}

Remote Module

Module: data:remote Package: es.mobiledev.data.remote Handles network operations using Retrofit.

ArticleWs

File: data/remote/src/main/java/es/mobiledev/data/remote/article/ArticleWs.kt:9 Retrofit web service interface for article API.
interface ArticleWs {
    @GET("articles")
    suspend fun getArticles(
        @Query("limit") limit: Long,
        @Query("offset") offset: Long
    ): ArticleResponseDto

    @GET("articles/{id}/")
    suspend fun getArticleById(
        @Path("id") id: Long
    ): ArticleDto
}

Endpoints

GET /articles
endpoint
Retrieves paginated list of articlesQuery Parameters:
limit
Long
required
Maximum number of results
offset
Long
required
Pagination offset
Returns: ArticleResponseDto
GET /articles/{id}/
endpoint
Retrieves a single article by IDPath Parameters:
id
Long
required
Article identifier
Returns: ArticleDto

ArticleDto

Data Transfer Object for article API responses.
data class ArticleDto(
    val id: Long,
    val title: String,
    val authors: List<AuthorDto>,
    val url: String,
    val imageUrl: String,
    val newsSite: String,
    val summary: String,
    val publishedAt: String,
    val updatedAt: String,
)
The “Dto” suffix stands for “Data Transfer Object”. These objects represent the API’s JSON structure and are mapped to domain models (Bo) before reaching the domain layer.

ArticleResponseDto

Paginated response wrapper.
data class ArticleResponseDto(
    val results: List<ArticleDto>,
    val count: Int,
    val next: String?,
    val previous: String?
)

AuthorDto & SocialsDto

data class AuthorDto(
    val name: String,
    val socials: SocialsDto?
)

ArticleRemoteDataSourceImpl

Implementation of the remote data source.
class ArticleRemoteDataSourceImpl(
    private val articleWs: ArticleWs
) : ArticleRemoteDataSource {
    override suspend fun getArticles(
        limit: Long,
        offset: Long
    ): ArticleResponseBo {
        return articleWs.getArticles(limit, offset).toBo()
    }

    override suspend fun getArticleById(id: Long): ArticleBo {
        return articleWs.getArticleById(id).toBo()
    }
}

Repository Module

Module: data:repository Package: es.mobiledev.data.repository Implements domain gateways by coordinating local and remote data sources.

ArticleRepository

File: data/repository/src/main/java/es/mobiledev/data/repository/article/ArticleRepository.kt:11 Implements ArticleGateway interface.
class ArticleRepository(
    private val remote: ArticleRemoteDataSource,
    private val local: ArticleLocalDataSource,
) : ArticleGateway {
    override suspend fun getArticles(
        limit: Long,
        offset: Long
    ): Flow<ArticleResponseBo> = 
        flowOf(remote.getArticles(limit = limit, offset = offset))

    override suspend fun getArticleById(id: Long): Flow<ArticleBo> = 
        flowOf(remote.getArticleById(id = id))

    override suspend fun getFavoriteArticles(): Flow<List<ArticleBo>> = 
        flowOf(local.getFavoriteArticles())

    override suspend fun saveFavoriteArticle(articleBo: ArticleBo) = 
        local.saveFavoriteArticle(articleBo)

    override suspend fun removeFavoriteArticle(articleBo: ArticleBo) = 
        local.removeFavoriteArticle(articleBo)

    override suspend fun isArticleFavorite(id: Long) = 
        flowOf(local.isArticleFavorite(id))
}
The repository pattern abstracts data sources from the domain layer. It decides whether to fetch from local cache or remote API, handling data synchronization transparently.

PreferencesRepository

Implements PreferencesGateway interface.
class PreferencesRepository(
    private val preferencesDataSource: PreferencesDataSource
) : PreferencesGateway {
    override suspend fun saveLastOpenTime(timeInMillis: Long) {
        preferencesDataSource.saveLastOpenTime(timeInMillis)
    }

    override suspend fun getLastOpenTime(): Long {
        return preferencesDataSource.getLastOpenTime()
    }
}

Session Module

Module: data:session Package: es.mobiledev.data.session Handles user session and app preferences using DataStore.

PreferencesDataSourceImpl

File: data/session/src/main/java/es/mobiledev/data/session/PreferencesDataSourceImpl.kt:11 Implements preferences storage using Jetpack DataStore.
class PreferencesDataSourceImpl(
    private val preferences: DataStore<Preferences>
) : PreferencesDataSource {
    override suspend fun saveLastOpenTime(timeInMillis: Long) {
        preferences.savePreference(
            key = PrefKeys.LAST_OPEN_TIME,
            value = timeInMillis
        )
    }

    override suspend fun getLastOpenTime(): Long =
        preferences.getPreference(
            key = PrefKeys.LAST_OPEN_TIME,
            defaultValue = LAST_OPEN_TIME_DEFAULT_VALUE
        )
}

PrefKeys

Preference key constants.
object PrefKeys {
    val LAST_OPEN_TIME = longPreferencesKey("last_open_time")
}

SessionConstants

const val SESSION_DATASTORE_NAME = "session_preferences"
const val LAST_OPEN_TIME_DEFAULT_VALUE = 0L
DataStore is the modern replacement for SharedPreferences, providing type-safe, asynchronous data storage.

Source Module

Module: data:source Package: es.mobiledev.data.source Defines interfaces for data sources, implemented by local and remote modules.

ArticleLocalDataSource

File: data/source/src/main/java/es/mobiledev/data/source/article/ArticleLocalDataSource.kt:5
interface ArticleLocalDataSource {
    suspend fun saveFavoriteArticle(article: ArticleBo)
    suspend fun removeFavoriteArticle(article: ArticleBo)
    suspend fun getFavoriteArticles(): List<ArticleBo>
    suspend fun isArticleFavorite(id: Long): Boolean
}

ArticleRemoteDataSource

File: data/source/src/main/java/es/mobiledev/data/source/article/ArticleRemoteDataSource.kt:6
interface ArticleRemoteDataSource {
    suspend fun getArticles(
        limit: Long,
        offset: Long
    ): ArticleResponseBo

    suspend fun getArticleById(id: Long): ArticleBo
}

PreferencesDataSource

interface PreferencesDataSource {
    suspend fun saveLastOpenTime(timeInMillis: Long)
    suspend fun getLastOpenTime(): Long
}
Data source interfaces provide an additional layer of abstraction, making it easy to swap implementations (e.g., switching from Room to SQLDelight) without affecting repositories.

Data Flow Architecture

Dependency Injection

@Module
@InstallIn(SingletonComponent::class)
object LocalModule {
    @Provides
    @Singleton
    fun provideAppDatabase(
        @ApplicationContext context: Context
    ): AppRoomDatabase {
        return Room.databaseBuilder(
            context,
            AppRoomDatabase::class.java,
            "cpt_database"
        ).build()
    }

    @Provides
    fun provideArticleDao(
        database: AppRoomDatabase
    ): ArticleDao = database.articleDao()
}

Build docs developers (and LLMs) love