I use the approach recommended in Android Architechture Guide and use NetworkBoundResource in my repository. I have SwipeRefreshLayout in my Fragments and I want to implement swipe-to-refresh action for my data in the following way:
I implements methods to get data in my repository in the following way:
public LiveData<Resource<List<Address>>> getAddresses() {
return new NetworkBoundResource<List<Address>, AddressResponse>(appExecutors) {
@Override
protected void saveCallResult(@NonNull AddressResponse response) {
database.runInTransaction(() -> {
addressesDao.deleteAll();
addressesDao.insert(response.items);
});
}
@Override
protected boolean shouldFetch(@Nullable List<Address> data) {
// isAddressesCacheUpToDate() checks the lifetime of cached data
return data == null || data.isEmpty() || !isAddressesCacheUpToDate(data);
}
@NonNull
@Override
protected LiveData<List<Address>> loadFromDb() {
return addressesDao.get();
}
@NonNull
@Override
protected LiveData<ApiResponse<AddressResponse>> createCall() {
return serviceApi.getAddresses();
}
}.asLiveData();
}
I can't realize how to modify NetworkBoundResource or my repository to implement such forced network fetches. Are there any good solutions for this task?
You need to implement new function for example forceRefresh() look exactly like getAddress() but the logic of shouldFetch is return true;
public LiveData<Resource<List<Address>>> refreshAddress() {
return new NetworkBoundResource<List<Address>, AddressResponse>(appExecutors) {
@Override
protected void saveCallResult(@NonNull AddressResponse response) {
database.runInTransaction(() -> {
addressesDao.deleteAll();
addressesDao.insert(response.items);
});
}
@Override
protected boolean shouldFetch(@Nullable List<Address> data) {
return true;
}
@NonNull
@Override
protected LiveData<List<Address>> loadFromDb() {
return addressesDao.get();
}
@NonNull
@Override
protected LiveData<ApiResponse<AddressResponse>> createCall() {
return serviceApi.getAddresses();
}
}.asLiveData();
}
check out my code at https://github.com/phamquangsang/PetNBU/blob/master/app/src/main/java/com/petnbu/petnbu/repo/FeedRepository.kt which I implement the logic similar to your.
I did the same with you @phamquangsang . My problem is that I have this code in my ViewModel
val results : LiveData<Resource<List<Planet>>> = repository.loadPlanets()
(I follow the same logic with the given sample).
Anyway, this code runs only once, at startup. Below you can see my Fragment code. What am I missing?
private fun initPlanetList(viewModel: PlanetViewModel) {
viewModel.results.observe(this, Observer { listResource ->
if (listResource?.data != null ) {
adapter.submitList(listResource.data)
}
})
binding.planetsListId.adapter = adapter }
My loadPlanets() fun
`fun loadPlanets(): LiveData
return object : NetworkBoundResource<List<Planet>, StarWarsSearchResponse>(appExecutors) {
override fun onFetchFailed() {
loadFromDb()
}
override fun saveCallResult(item: StarWarsSearchResponse) {
planetDao.deleteOldData()
planetDao.insertPlanets(item.items)
}
override fun shouldFetch(data: List<Planet>?): Boolean {
return true
}
override fun createCall(): LiveData<ApiResponse<StarWarsSearchResponse>> = starWarsService.getPlanets()
override fun loadFromDb() = planetDao.loadPlanets()
}.asLiveData()
}`
Anyway, this code runs only once, at startup. Below you can see my Fragment code. What am I missing?
The code is supposed to be run only once, I still not sure what exactly flow you want to implement.
On the startup, it loads data from disk and immediately show UI, then fetching new data from the network (since shouldFetch return true).
But if you want to let users explicitly force refresh data, you may want to implement pull-to-refresh in the fragment, when pull-to-refresh trigger an event -> call forceRefresh() in your ViewModel which implement logic get new data from web service, remember that data return from web service should be saved to the disk. So that the viewModel.results in ViewModel could be notified and then dispatch new value to UI
Why not just do this
public LiveData<Resource<List<Address>>> getAddresses(boolean forceRefresh) {
return new NetworkBoundResource<List<Address>, AddressResponse>(appExecutors) {
@Override
protected void saveCallResult(@NonNull AddressResponse response) {
database.runInTransaction(() -> {
addressesDao.deleteAll();
addressesDao.insert(response.items);
});
}
@Override
protected boolean shouldFetch(@Nullable List<Address> data) {
// isAddressesCacheUpToDate() checks the lifetime of cached data
return forceRefresh || data == null || data.isEmpty() || !isAddressesCacheUpToDate(data);
}
@NonNull
@Override
protected LiveData<List<Address>> loadFromDb() {
return addressesDao.get();
}
@NonNull
@Override
protected LiveData<ApiResponse<AddressResponse>> createCall() {
return serviceApi.getAddresses();
}
}.asLiveData();
}
In the viewModel I have a public addresses mediator which the view is observing. Also in the viewModel, in the init method, I do addresses.addSource(repository.getAdressess(false))
Now if ever want to refresh the data I cannot just call repository.getAddresses(true) because it always returns a new LiveData, it's not tied with addresses anymore and we can't continue adding sources to the addresses mediator, even if it works it's a leak waiting to happen.
How did you address this problem?
@panosmir
Hi, I ran into the same problem and came up with nothing better like this:
override suspend fun saveCallResult(data: CurrentState) {
mapDao.deleteCurrentStates()
mapDao.insertCurrentState(data)
}
is there any other solution to the problem?
Consider look at Store, which seeks to cover the repository pattern, encapsulating the loading logic and specifically has support for busting through your local cache.
Most helpful comment
Why not just do this