On the part I of this article, we had created database system based MVVM pattern.
On this article we will modify our app further, so that when user open app :
- if there is internet it will fetch data from server and save it to app database
- if there is no internet, app will still display data from app database.
- Create Worker class to download data
The task (in this case is to download data) is defined using the Worker class. ThedoWork()
method runs asynchronously on a background thread provided by WorkManager.
@Singleton
class DownloadWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
@Inject var repo: NewsRepository
init {
val repoFactory = EntryPointAccessors.fromApplication(
appContext,
RepoEntryPoint::class.java
)
repo = repoFactory.newsRepository
}
override fun doWork(): Result {
return try {
fetchNews(repo)
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
private fun fetchNews(repository: NewsRepository) {
CoroutineScope(Dispatchers.IO).launch {
val response = repository.fetchNewsFromNet()
withContext(Dispatchers.Main){
if (response.isSuccessful){
val newsList = response.body()
if (newsList != null) {
repo.insertNewsToDB(newsList)
}
}
}
}
}
}
Inside this class, we migrated fetchNews() method from viewModel. The main method is called inside doWork().
2. Create WorkRequest & submit it to system
For this application we will using OneTimeWorkRequestBuilder and submit it to system using .enqueue method.
private var workManager = WorkManager.getInstance(CryptoNewsApplication())
internal fun fetchDataFromNet () {
val newsDownloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.build()
workManager.enqueue(newsDownloadRequest)
}
To execute it, we will called fetchDataFromNet inside our fragment.
... viewModel.fetchDataFromNet(viewLifecycleOwner, {})
...
binding.refreshLayout.setOnRefreshListener {
viewModel.fetchDataFromNet(viewLifecycleOwner, {})
}
}
...
Until this step, we are successfully fetch the data from server and save it to our app.
3. Add constraint on the Work Request and WorkInfo for observing states
To let the WorkManager know that the work should not be executed when there is no internet, we should add WorkConstraint for the Work Request.
We can also add WorkInfo to inform when the Work is done, so that we can update UI
...
private var workManager = WorkManager.getInstance(CryptoNewsApplication())
private val networkConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
private val newsParameterWorkInfoItems: LiveData<List<WorkInfo>> =
workManager.getWorkInfosByTagLiveData("news_download")
internal fun fetchDataFromNet (lifecycleOwner: LifecycleOwner, uiUpdate: () -> Unit) {
val newsDownloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setConstraints(networkConstraints)
.addTag("news_download")
.build()
workManager.enqueue(newsDownloadRequest)
newsParameterWorkInfoItems.observe(lifecycleOwner, {
val workInfo = it[0]
if (!it.isNullOrEmpty() && workInfo.state.isFinished) {
uiUpdate
}
})
}
...
Update our Fragment and define the UI updates that we need.
...
viewModel.fetchDataFromNet(viewLifecycleOwner, {
Toast.makeText(requireContext(), "Download is successfully performed", Toast.LENGTH_SHORT).show()
})
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(viewLifecycleOwner, {
Toast.makeText(requireContext(), "Download is successfully performed", Toast.LENGTH_SHORT).show()
})
}
}
4. Display data from database
Until this step, what we are doing is to performed Download Data task when the internet is available. However, our app screen will still blank, because we haven’t display the data from our database.
Update our LiveData on the ViewModel by calling method to pull data from app database
val newsList = repository.fetchNewsFromDB()
Finish. Here is the complete source code.
5. Run and testing
If we execute our app now, there is no change on the display. However, if there is no internet, app will still display the list of Crypto News.
Further challenge (I will do it for part III, whenever I am free) :
- Create offline database system for Detail Fragment as well
- Change DownloadWorker to be executed periodically (not every time when there is internet)
Source : https://developer.android.com/topic/libraries/architecture/workmanager/basics