Repository & Data Layer Quiz
ARCHITECTURE › Patterns
Which layer or class should other layers go through to reach the data layer?
- Data source classes directly, without going through a repository
- The repository class, the entry point to the data layer
- The ViewModel, which sits above the data layer and wraps it
- A use case, which talks to the database without a repository
Answer: The repository class, the entry point to the data layer
Repositories are the entry points to the data layer; other layers should never access data sources directly.
A repository needs to expose a continuously updating list of articles as the cache changes. What should it expose?
- A suspend function returning List<Article>
- A LiveData created in the repository constructor
- A Flow<List<Article>>
- A blocking getter returning a mutable list
Answer: A Flow<List<Article>>
To notify consumers of data changes over time, the data layer exposes a Flow; suspend functions are for one-shot operations.
Why is a local database typically recommended as a repository's single source of truth?
- It is faster than any in-memory cache, even under heavy reads
- It survives process death and keeps reads consistent offline
- It removes the need to map DTOs into domain models entirely
- It makes the repository main-safe automatically on every call
Answer: It survives process death and keeps reads consistent offline
A persistent local source survives process death and supports offline-first, giving consistent data independent of the network.
Where should the logic that resolves conflicts between a network response and the cached database value live?
- In the ViewModel that collects the data
- In the Composable that renders the data
- In the remote data source
- In the repository
Answer: In the repository
Centralizing changes and resolving conflicts between multiple data sources is a core repository responsibility, keeping that policy out of the UI.
A data source must call a blocking network library. How should it stay main-safe?
- Wrap the blocking call in withContext(ioDispatcher)
- Require callers to launch it on Dispatchers.Main
- Run it inside a runBlocking block on the caller's thread
- Mark the function with @MainThread and call it directly
Answer: Wrap the blocking call in withContext(ioDispatcher)
The data source is responsible for moving long-running blocking work off the calling thread, e.g. with withContext(ioDispatcher).
Which task is best run with WorkManager rather than a screen-scoped coroutine?
- Loading list items only while the user views a screen
- Caching a network result for reuse while the app is open
- A business-critical sync that must survive process death
- Debouncing a search query live as the user types each key
Answer: A business-critical sync that must survive process death
Business-oriented operations cannot be cancelled and must survive process death; WorkManager is the recommended tool for them.
Why does the data layer expose dedicated domain models instead of raw network DTOs?
- Because Retrofit requires domain models when calling suspend endpoints
- To keep only needed fields, map external types, and avoid UI coupling
- Because Flow only works when the emitted type is a domain model
- To let the UI modify shared objects directly without copying data
Answer: To keep only needed fields, map external types, and avoid UI coupling
Separate models save memory, adapt external types to app types, and improve separation of concerns so the UI isn't coupled to the source format.
A repository method places a single order on the server and returns once, with no ongoing updates afterward. How should the data layer expose this one-shot operation?
- As a cold Flow<Order> that the caller must collect
- As a suspend function that returns the created Order
- As a public mutable property the caller polls for updates
- As LiveData exposed from the repository for observing changes
Answer: As a suspend function that returns the created Order
One-shot CRUD operations are exposed as suspend functions; a Flow is only needed when consumers must be notified of changes over time.
Why should the objects a repository returns to higher layers be immutable?
- So they can be shared across threads and never mutated into bad state
- Because Room rejects any mutable class annotated with @Entity outright
- Because Flow is only able to emit strictly immutable types
- Because Retrofit serializes immutable objects noticeably faster
Answer: So they can be shared across threads and never mutated into bad state
Immutable data can't be tampered with by other classes, avoiding inconsistent state, and can be shared across threads without synchronization.
A repository keeps a mutable in-memory cache that several coroutines read and write concurrently. How should it prevent corruption?
- Mark the field @Volatile and let coroutines read and write it freely
- Confine all cache access to Dispatchers.Main
- Guard every read and write with a Mutex inside withLock
- Wrap each access in its own runBlocking block
Answer: Guard every read and write with a Mutex inside withLock
A Mutex with withLock serializes concurrent coroutine access so the shared cache isn't corrupted; @Volatile only guarantees visibility, not atomic compound updates.
An analytics-event upload should still complete even if the user leaves the screen, but it does not need to survive process death. Where is it best run?
- In viewModelScope so it stays tied to the screen lifecycle
- In a LaunchedEffect inside the Composable to run on UI state
- In a one-time WorkManager request for deferred background work
- In an injected application-scoped CoroutineScope
Answer: In an injected application-scoped CoroutineScope
This is an app-oriented operation that should live as long as the app process; an injected application-scoped scope fits, while WorkManager is reserved for business-critical work that must survive process death.
In an offline-first repository where Room is the single source of truth, how should fresh network data reach the UI?
- Emit the network response straight to the UI while writing it to Room separately
- Write the network response into Room and let the UI observe a Flow from Room
- Return whichever of Room or the network responds first
- Store the response in a companion-object field that the UI reads
Answer: Write the network response into Room and let the UI observe a Flow from Room
With a single source of truth, writes go to the database and consumers always observe the database Flow, so the UI reflects one consistent source after the network result is persisted.
How should a repository obtain the dispatcher it passes to withContext so that it remains easy to unit test?
- Inject a CoroutineDispatcher through its constructor
- Hardcode Dispatchers.IO inside every function
- Call Dispatchers.setMain() from within the repository
- Look it up from a global mutable singleton
Answer: Inject a CoroutineDispatcher through its constructor
Injecting the dispatcher lets tests substitute a test dispatcher, whereas hardcoding Dispatchers.IO couples the class to a real thread pool and makes deterministic testing hard.
According to modern Android data-layer guidance, how should repositories be organized in an app?
- A single global repository handling all data for the entire app
- One repository per Composable screen
- One repository per data source instance
- One repository for each different type of data the app handles
Answer: One repository for each different type of data the app handles
The guidance recommends a repository per type of data (e.g. MoviesRepository, PaymentsRepository), each potentially backed by several data sources, rather than a single god repository or per-screen repositories.