Android Example : Offline Database App with WorkManager (Part I)

Andrea
3 min readMar 8, 2022

--

Most of our app nowadays are mean to display some data that retrieve from backend or internet. It became an expensive cost if every time user open the app, the app must fetch the data directly from server. One of the solution is that app has its own database and fetch from server periodically.

This is something that I learned from my project, on how to convert a fully online app to an app that used offline/cache database.

Supposed that you have an app fully online like the app bellow (source code). Which fetch news data about crypto from api and display it every time user open the app or refresh the app.

  1. Create database & ViewModel
    To make things easier, I had assume that Hilt Dependency Injection already properly set-up. Create offline database based on MVVM pattern.

*Model/POJO to be converted to entity. Don't forget to add PrimaryKey if it havent so

@Entity(tableName = "news_table")
data class News (
@SerializedName("title") var title: String? = null,
@SerializedName("url") var url: String? = null,
@SerializedName("source") var source: String? = null,
@PrimaryKey(autoGenerate = true) var id: Int
)

*Add dao to the database


@Dao
interface NewsDao {
@Query("DELETE FROM news_table")
fun deleteAll()

@Insert
fun insertAll(newsList: List<News?>?)

@Query("SELECT * from news_table ORDER BY id ASC")
fun getAllNews(): LiveData<List<News>>
}

*Add database to the database system

@Database(entities = [News::class], version = 1, exportSchema = true)
abstract class NewsDB : RoomDatabase() {

abstract fun newsDao(): NewsDao

companion object {
@Volatile
private var instance: NewsDB? = null
private val LOCK = Any()

operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also { instance = it }
}

private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
NewsDB::class.java,
"news.db"
).build()
}
}

*Add repository to the database system. Pay attention on what function that we will need for this system. On this app, it will need fetch data, save data to db, display data from database and delete data from database

class NewsRepository @Inject constructor(
val api: RetrofitAPI,
val dao: NewsDao
) {
suspend fun fetchNewsFromNet() =
api.fetchNews()

fun insertNewsToDB(list: List<News>) =
dao.insertAll(list)

fun fetchNewsFromDB() =
dao.getAllNews()

fun deleteNewsFromDB() =
dao.deleteAll()
}

*Add viewModel to the system

@HiltViewModel
class NewsViewModel @Inject constructor(
private val repository: NewsRepository
) :
ViewModel() {

val newsList = MutableLiveData<List<News>>()

var job: Job? = null
var loading = MutableLiveData<Boolean?>()


override fun onCleared() {
job?.cancel()
super.onCleared()
}
}

2. Remove the function from Fragment to viewModel
After we had set up all the database system, now we can migrate the retrieve data function from Fragment to ViewModel. On the Fragment now we can called the function from viewModel.

@AndroidEntryPoint
class NewsFragment : Fragment() {

...

private val viewModel by activityViewModels<NewsViewModel>()

...

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

...

viewModel.fetchDataFromNet()

viewModel.newsList.observe(viewLifecycleOwner, {
adapter.setData(it)
})

viewModel.loading.observe(viewLifecycleOwner, {
if (it == true){
binding.loadingBar.visibility = View.VISIBLE
} else {
binding.loadingBar.visibility = View.INVISIBLE
binding.refreshLayout.isRefreshing = false
}
})

binding.refreshLayout.setOnRefreshListener {
viewModel.fetchDataFromNet()
}
}

...
}
@HiltViewModel
class NewsViewModel @Inject constructor(
private val repository: NewsRepository
) :
ViewModel() {

val newsList = MutableLiveData<List<News>>()

var job: Job? = null
var loading = MutableLiveData<Boolean?>()

fun fetchDataFromNet() {
job = CoroutineScope(Dispatchers.IO).launch {
loading.postValue(true)
val list = repository.fetchNewsFromNet()
withContext(Dispatchers.Main){
if (list.isSuccessful){
newsList.postValue(list.body())
}
loading.postValue(false)
}
}
}

...
}

3. Test the app again, ensure it is working properly.
Now if we run our app, there is not much change from the previous code on tis display. But it lay a good foundation for the next step.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Andrea
Andrea

No responses yet

Write a response