Repositories and a single source of truth

Architecture and dependency injection Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Pick the fix

The list screen shows the new title after an edit, but the detail screen shows the old one. Both call the API. Best fix?

Options
Hint 1 of 1

How many places hold the data?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Have the repository write results to Room and both screens observe the Room flow

Correct. One source of truth keeps every screen in sync.

Why not the others?
  • B Refresh the detail screen on resume. Patches one case; drift continues elsewhere.
  • C Pass the updated object between screens. Couples screens and breaks after process death.
  • D Add an event bus to broadcast edits. Another channel that can get out of sync.

30-second answer

A repository is the only entry point to one kind of data, like articles or the user profile. It hides where data comes from, merges network and local sources, and exposes it as Kotlin types and Flows. Single source of truth means the UI always reads from one place, usually the database: the network writes into it, and the UI observes it, so every screen agrees.

Practise saying it · 35 s. No recording, just a timer

Explain repositories and single source of truth.

Say your answer out loud, then check which of these points you covered:

  • One entry point per data type
  • Hides sources, maps DTOs
  • UI observes the database
  • Network writes into it

Deep dive

Reads return Flow from Room; refresh functions are suspend and write to Room.

Map DTOs to app models at the repository boundary.

Decide the threading inside: Room and Retrofit are main-safe; CPU mapping goes to Default.

Repositories are usually singletons provided by DI.

An offline-first repository Compile-checked · Kotlin 2.4.21
@Entity data class ArticleEntity(@PrimaryKey val id: String, val title: String)

@Dao interface ArticleDao {
    @Query("SELECT * FROM ArticleEntity") fun observe(): Flow<List<ArticleEntity>>
    @Upsert suspend fun upsert(items: List<ArticleEntity>)
}

class ArticleRepository(private val dao: ArticleDao, private val api: Api) {
    val articles: Flow<List<Article>> = dao.observe().map { list -> list.map { Article(it.id, it.title) } }

    suspend fun refresh() {
        dao.upsert(api.feed().map { ArticleEntity(it.id, it.title) })
    }
}

Common mistakes

  • Returning the network result to the UI and the cached one elsewhere.
  • Exposing Retrofit or Room types from the repository.
  • Making a repository per screen instead of per data type.

Senior take & follow-ups

Explain the write-to-DB, observe-DB loop. It's the heart of offline-first.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why read from the database instead of the network response?

    Show hint

    So every screen sees the same data and offline works.

  2. Should a repository be a singleton?

    Show hint

    Usually, so its cache and in-flight requests are shared.

Similar on iOS: A testable network layer The iOS page focuses on the network layer; Android's guide makes the database the source of truth.

Suggest a fix if something here is wrong or out of date.