In mobile application development, we are often asked to build applications that can operate in both online and offline modes. Users may switch networks, lose signal, or be in areas with high latency.
There are several approaches to ensuring that applications can function seamlessly in both modes. One of them is NetworkBoundResource, a class that allows us to define data synchronization strategies between local data sources and remote API data.
This approach is relatively easy to implement and highly customizable. We can configure various processes as needed, such as authentication handling and pagination.
In this tutorial, I will focus specifically on native Android development using Kotlin. The sample application will be a Movie List app that uses data from the TMDB API. NetworkBoundResource can also be applied in other platforms such as iOS (Swift) or cross-platform frameworks like Flutter and React Native.
NetworkBoundResource Process Flow

Brief Explanation of the Workflow
- Load data from the database. The UI always monitors the database (Room + Flow).
- Check whether fetching is necessary. Usually based on:
- Null data
- Empty data
- Expired (TTL)
- Force refresh
- If fetching is necessary
- Call the API
- Save to the database
- The database automatically re-emits the latest data
- If fetching is not necessary
- Directly emit local data
- No network call
Explanation of TTL
TTL (Time To Live) is the validity period of data stored locally (e.g., in a database or cache). As long as the TTL period has not expired, the data is considered valid and can be used without having to make another request to the server.
If the TTL has expired, the data is considered invalid, so the application needs to resynchronize (with the server to get the latest data).
Technology Used
In implementing online and offline data synchronization, the following technology stack is used:
- Room Database Used as a local database to store data that can be accessed when the application is offline.
- Retrofit + OkHttp Used for communication with REST API.
- Retrofit functions as an HTTP client abstraction to define API endpoints.
- OkHttp handles network connections, logging, interceptors, and timeout configurations.
- Gson Used to parse JSON responses from the API into Kotlin objects (DTO).
- Koin Used as dependency injection for providing dependencies such as Repository, ViewModel, Database, and Network layers.
- Kotlin Coroutines Used to handle asynchronous operations in a non-blocking manner, especially for database operations and network calls.
- Glide used for image loading and caching.
- Chucker used for monitoring and debugging HTTP requests/responses during the development process.
- MVVM (Model-View-ViewModel) is used as an architecture to separate business logic, data layer, and UI layer so that the application is more modular, maintainable, and testable.
Creating a Network Bound Resource class

Explanation of Each Stage
1. query()
- Retrieve data from Room
- Alternatively, you can perform other processes such as data validation, generating unique codes, etc.
2. shouldFetch(data)
Determining whether to call the API:
Example conditions:
- Null data
- Empty data
- Data expired (TTL)
- Force refresh
3. createCall()
- Performing the API call process for API data synchronization
4. saveCallResult(response)
- Mapping DTO → Entity
- Save to the database
5. Emit State
We will use sealed classes as state in the NetworkBoundResource process.
What is a Sealed Class?
A sealed class is a type of closed class in Kotlin that cannot be freely inherited anywhere. It can only have predetermined derivatives. This is different from an open class, which can be inherited by any class.

Creating an API Service
I will retrieve data from the TMDB API.
Endpoint used:
GET:
https://api.themoviedb.org/3/movie/popular
Response:

GET:
https://image.tmdb.org/t/p/w185
Used to obtain image data. w185 is the size of the image obtained. You can change the image size according to your needs. Then combine this endpoint with the poster_path data that we will use in Glide.
I save these two endpoints in the Configs class along with the error message and timeout settings.

Creating the MovieService class:

Creating the MovieModel data class:

I added a function to map MovieModel data to MovieEntity so it can be saved to the local database.
Creating the MovieEntity data class:

I added the getPosterUrl() function as endpoint data to retrieve images.
Creating the Dao class:

Creating the TypeConverter class:

This class is used to convert genre ID list data into strings when saving it to the movie table, and will be converted back into a genre ID list when we select the data.
Creating the LocalDatabase class:

Creating the Repository class:

This is where we use NetworkBoundResource as a layer for the process of retrieving data to the local database and synchronizing it to the API.
Creating a ViewModel class:

In the MainViewModel class, I initialize getMovieList() using flatMapLatest so that the old flow is automatically canceled, there are no stacked APIs, it is safe to refresh repeatedly, and it creates a refresh function with StateFlow.
Previously, I did not use flatMapLatest to process data from getMovieList. I directly created the same function in viewModel and then made it LiveData so that it could be observed in the UI. If I wanted to refresh the data, I would call the function again, resulting in the old flow remaining active while a new flow was created. As a result, the API was called repeatedly (multiple requests).
In addition, if there were other function calls within the flow, those functions would also be called repeatedly because both flows were running simultaneously.
Koin Modules
I divided it into three modules: remote, local, and domain.
- Remote Module : For initializing Retrofit, OKHttp, Chunker, etc.
- Local Module : For initializing Room Database
- Domain Module : For initializing the repository and viewModel
Remote Module

Local Module

Domain Module

Don’t forget to create an Application class and initialize all module coins in it.

Implementation in UI

To refresh, simply call the refresh function that has been created in the viewmodel.

Since Android version 13, apps must request runtime permission to display notifications. Therefore, we need to request permission for notifications because otherwise, the chunker will not appear in the notification window.
Results

Start with an online connection to get the initial data. Then you can turn off the network mode on your device to try offline mode. You can refresh the data or close the app and start again. You will see that in offline mode, the app still displays the data.

Closing
Online and offline synchronization strategies are not just a technical matter, but also a matter of ensuring that applications can be used anytime and anywhere. With an approach such as NetworkBoundResource, we can provide a more stable and comfortable experience for users, even when internet conditions are poor.
Of course, in addition to the data retrieval process, the application also needs to be able to process data such as transactions that can run in offline mode so that users of our application can continue their activities without being interrupted by the network. The data stored locally can then be synchronized to the server.
