networkBoundResource

inline fun <ResultType, RequestType> networkBoundResource(crossinline query: () -> Flow<ResultType>, crossinline fetch: suspend () -> RequestType, crossinline saveFetchedResult: suspend (RequestType) -> Unit, crossinline shouldFetch: (ResultType) -> Boolean = { true }): Flow<Resource<ResultType>>

Creates a network-bound resource flow that implements the offline-first pattern.

This function creates a Flow that:

  1. First queries the local database and emits cached data

  2. Decides whether to fetch from network based on shouldFetch predicate

  3. If fetching, emits Loading state with cached data

  4. Fetches from network, saves to database, and emits updated data

  5. On error, emits Error state with cached data still available

This pattern ensures your UI always has data to display (when available) even during network requests or when offline.

Usage Example

// In a Repository
override fun getUsers(): Flow<Resource<List<User>>> = networkBoundResource(
query = {
// Observe local database
localDataSource.observeUsers()
},
fetch = {
// Fetch from network API
networkDataSource.getUsers()
},
saveFetchedResult = { networkUsers ->
// Save network response to local database
localDataSource.saveUsers(networkUsers.map { it.toEntity() })
},
shouldFetch = { cachedUsers ->
// Fetch if cache is empty or stale
cachedUsers.isEmpty() || isCacheStale()
}
)

Advanced Example with Timestamp-Based Caching

override fun getArticles(): Flow<Resource<List<Article>>> = networkBoundResource(
query = { localDataSource.observeArticles() },
fetch = { networkDataSource.getArticles() },
saveFetchedResult = { articles ->
localDataSource.saveArticles(articles, fetchedAt = System.currentTimeMillis())
},
shouldFetch = { cachedArticles ->
val cacheAge = System.currentTimeMillis() - cachedArticles.firstOrNull()?.fetchedAt ?: 0
cachedArticles.isEmpty() || cacheAge > 5.minutes.inWholeMilliseconds
}
)

Network-Only (No Cache)

// If you don't want caching, use empty query and save
override fun getCurrentWeather(): Flow<Resource<Weather>> = networkBoundResource(
query = { flowOf(null) },
fetch = { weatherApi.getCurrentWeather() },
saveFetchedResult = { /* no-op */},
shouldFetch = { true } // Always fetch
)

Return

A Flow emitting Resource states throughout the operation lifecycle: - Resource.Loading(cachedData) - when fetch starts (if shouldFetch returns true) - Resource.Success(freshData) - when data is successfully fetched and saved - Resource.Error(cachedData, error) - if fetch fails (cached data still available) - Resource.Success(cachedData) - if shouldFetch returns false

Parameters

ResultType

The type of the data from the local database (what UI consumes).

RequestType

The type of the data from the network API (may differ from ResultType).

query

Function that returns a Flow of cached data from the local database. This Flow will be observed and emitted throughout the resource lifecycle.

fetch

Suspend function that fetches fresh data from the network. Should throw exceptions on failure (will be caught and converted to Error state).

saveFetchedResult

Suspend function that saves the fetched network data to local storage. Called before emitting the updated query results.

shouldFetch

Predicate that determines whether to fetch from network based on cached data. Defaults to true (always fetch). Common patterns: - { it.isEmpty() } - fetch only if cache is empty - { it.isEmpty() || isStale(it) } - fetch if empty or stale - { true } - always fetch (refresh on every call) - { false } - never fetch (local-only)

See also

For detailed information about Resource states