The Issue
I implemented the Android Architecture library and with this I am restoring data from Room Database, with MVVM (Model View View-Model) and whenever I use the observer to view LiveDada every time I start the app there is a noticeable delay when the items from the recycler view loads.
Image illustrates on what I mean when the app stats and the items show delay when they load.
What I want it to do
What I want to achieve with LiveData is this and the way I was able to achieve this is in my Dao I used a Query to get all of the data and pass it as a List rather than using LiveData and then in the Repository convert it to MutableLiveData and then pass it to database and from there observe it as LiveData in my fragment but using this approach actually doesn't update on delete or on insert unless if i restart the app.
Is there any way to fix this issue?
I would much like to use LiveData.
This is my DevicesDao interface:
#Dao
public interface DevicesDao {
#Insert
void insert(Devices... devices);
#Query("SELECT * FROM devices")
LiveData<List<Devices>> getDevices();
/*
#Query("SELECT * FROM devices")
List<Devices> getDevices();
*/
#Delete
void delete(Devices... device);
#Update
void update(Devices... device);
}
Database:
#Database(entities = {Devices.class}, version = 1)
public abstract class DevicesDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "devices_registered";
private static DevicesDatabase instance;
public abstract DevicesDao devicesDao();
public static DevicesDatabase getInstance(final Context context) {
if (instance == null) {
synchronized (DevicesDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
DevicesDatabase.class,
DATABASE_NAME)
.fallbackToDestructiveMigration()
.build();
}
}
}
return instance;
}
}
Repository:
public class DevicesRepository {
private final DevicesDao devicesDao;
public DevicesRepository(Application application) {
DevicesDatabase db = DevicesDatabase.getInstance(application);
devicesDao = db.devicesDao();
}
public void addDevices(Devices devices) {
new InsertDeviceAsync(devicesDao).execute(devices);
}
public void updateDevice(Devices devices) {
new UpdateDeviceAsync(devicesDao).execute(devices);
}
public void deleteDevice(Devices devices) {
new DeleteDeviceAsync(devicesDao).execute(devices);
}
//Gets all data from SQLite
public LiveData<List<Devices>> getAllDevices() {
return devicesDao.getDevices();
}
/*
public LiveData<List<Devices>> getAllDevices() {
MutableLiveData<List<Devices>> devices = new MutableLiveData<>();
try {
devices.setValue(new GetDeviceAsync(devicesDao).execute().get());
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return devices;
}
*/
}
View Model File:
public class HomeFragmentViewModel extends AndroidViewModel {
private final DevicesRepository devicesRepository;
private LiveData<List<Devices>> devices;
public HomeFragmentViewModel(#NotNull Application application) {
super(application);
devicesRepository = new DevicesRepository(application);
devices = devicesRepository.getAllDevices();
}
public LiveData<List<Devices>> getAllDevices() {
return devices;
}
public void addNewDevice(Devices devices) {
devicesRepository.addDevices(devices);
}
public void deleteDevice(Devices devices) {
devicesRepository.deleteDevice(devices);
}
public void editDevice(Devices devices) {
devicesRepository.updateDevice(devices);
}
}
And lasty, the observer in my fragment:
///////Other code
//Implements ViewModel to HomeFragment
homeFragmentViewModel = ViewModelProviders.of(this).get(HomeFragmentViewModel.class);
homeFragmentViewModel.getAllDevices().observe(getViewLifecycleOwner(), devicesList -> {
//Validation tool
validationUtil = new ValidationUtil(devicesList);
//Adds to adapter
adapter.submitList(devicesList);
/////// Other code
});
Thank you for your time!!!
New update: When I reset the app data, it loads fine but as soon as I decide to update the code, the issue comes back regardless of that edit I make in Android Studio.
Related
I've been doing Android Studio for a month only and I've got to say I'm kind of confused with Room database, I'm sorry if the question sounds confused.
I'm using a Room database as stated, here are my Database-related classes:
#Entity(tableName="board")
public class BoardItem {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name="board_id")
private int boardId;
#ColumnInfo(name="board_name")
private String boardName;
#ColumnInfo(name="board_description")
private String boardDescription;
#ColumnInfo(name="board_image_list")
#TypeConverters(ImageListTypeConverter.class)
private List<ImageItem> boardImageList;
public BoardItem(String boardName, String boardDescription){
this.boardName = boardName;
this.boardDescription = boardDescription;
this.boardImageList = new ArrayList<>();
}
public int getBoardId() { return boardId; }
public String getBoardName() {
return boardName;
}
public String getBoardDescription() {
return boardDescription;
}
public String getPhotosCount() {
return String.valueOf(this.boardImageList.size());
}
public void setBoardId(int boardId) {
this.boardId = boardId;
}
public List<ImageItem> getBoardImageList() {
return boardImageList;
}
public void setBoardImageList(List<ImageItem> list) { this.boardImageList = list; }
#Dao
public interface BoardItemDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
void addBoardItem(BoardItem boardItem);
#Transaction
#Query("SELECT * from board ORDER BY board_id DESC")
LiveData<List<BoardItem>> getBoardItems();
#Transaction
#Query("SELECT * from board ORDER BY board_id DESC")
List<BoardItem> getBoardItemsNow();
}
#Database(entities = {BoardItem.class}, version = 1)
public abstract class BoardItemDatabase extends RoomDatabase {
public abstract BoardItemDAO boardItemDAO();
//Singleton
private static volatile BoardItemDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
static BoardItemDatabase getDatabase(final Context context){
if (INSTANCE == null) {
synchronized (BoardItemDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), BoardItemDatabase.class, "board_database")
.allowMainThreadQueries()
.build();
}
}
}
return INSTANCE;
}
}
//Repository
public class BoardItemRepository {
private BoardItemDAO boardItemDAO;
private LiveData<List<BoardItem>> boardItemList;
private List<BoardItem> boardItemListNow;
public BoardItemRepository (Application application) {
BoardItemDatabase db = BoardItemDatabase.getDatabase(application);
boardItemDAO = db.boardItemDAO();
boardItemList = boardItemDAO.getBoardItems();
boardItemListNow = boardItemDAO.getBoardItemsNow();
}
//Room executes all queries on a separate thread
//Observed LiveData will notify the observer when data has changed
public LiveData<List<BoardItem>> getBoardItemList() { return boardItemList; }
//this method is called on a non-UI thread or the app will throw an exception. Room ensures
//that there are no long running operations on the main thread, blocking the UI.
public void addBoardItem(final BoardItem boardItem) {
BoardItemDatabase.databaseWriteExecutor.execute(() -> boardItemDAO.addBoardItem(boardItem));
}
public List<BoardItem> getBoardItemListNow() {return boardItemListNow; }
}
Note that BoardItem and ImageItem are classes I made myself: a board is supposed to contain multiple ImageItems.
My boardItem has different fields, one of which is a list of ImageItems.
Now, in a specific fragment I try to update this list of ImageItems in a board that already exists in my database, which is the board with id = 0 (the very first board in the db). I try to retrieve the list from the Database and replace it with a new one.
I have used LiveData in certain cases to update the view of my app when the item change, but I have non LiveData methods for this specific piece of my code I need to change my database as soon as I click the button that contains this code:
List<BoardItem> boardItems = boardListViewModel.getBoardItemsNow();
newList = boardItems.get(0).getBoardImageList();
newList.add(newItem);
boardItems.get(0).setBoardImageList(newList);
When I click the button, the code is executed with no errors, but the database isn't updated; it contains the list as it was before, without the new item.
Thanks in advance, again I'm sorry if this sounds confusing!
EDIT:
here's my ViewModel:
public class BoardListViewModel extends AndroidViewModel {
private final MutableLiveData<BoardItem> boardSelected = new MutableLiveData<>();
private LiveData<List<BoardItem>> boardItems;
private List<BoardItem> boardItemsNow;
public BoardListViewModel(#NonNull Application application) {
super(application);
BoardItemRepository repo = new BoardItemRepository(application);
boardItems = repo.getBoardItemList();
boardItemsNow = repo.getBoardItemListNow();
}
public void select(BoardItem boardItem) {
boardSelected.setValue(boardItem);
}
public LiveData<BoardItem> getSelected() {
return boardSelected;
}
public LiveData<List<BoardItem>> getBoardItems() {
return boardItems;
}
public BoardItem getBoardItem(int position) {
return boardItems.getValue() == null ? null : boardItems.getValue().get(position);
}
public List<BoardItem> getBoardItemsNow() { return boardItemsNow; }
}
I believe that your issue is that you are not updating but attempting to add (insert) a new item (row) via :-
#Insert(onConflict = OnConflictStrategy.IGNORE)
void addBoardItem(BoardItem boardItem);
As the boardid already exists and that it is the primary key, which is implicitly unique, a conflict occurs, and this conflict is ignored, thus the new row is not added and the database remains unchanged.
What you should be doing is updating the existing row, so you want an #Update dao.
So add and then use the following dao :-
#Update(onConflict = OnConflictStrategy.IGNORE)
int updateBoardItem(BoardItem boardItem);
the int returned is the number of rows that have been update (you would expect 1, 0 if a conflict resulted in the update being ignored).
The structure of my application is as follows:
MainActivity(Activity) containing Bottom Navigation View with three fragments nested below
HomeFragment(Fragment) containing TabLayout with ViewPager with following two tabs
Journal(Fragment)
Bookmarks(Fragment)
Fragment B(Fragment)
Fragment C(Fragment)
I am using Room to maintain all the records of journals. I'm observing one LiveData object each in Journal and Bookmarks fragment. These LiveData objects are returned by my JournalViewModel class.
JournalDatabase.java
public abstract class JournalDatabase extends RoomDatabase {
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService dbWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
private static JournalDatabase INSTANCE;
static synchronized JournalDatabase getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), JournalDatabase.class, "main_database")
.fallbackToDestructiveMigration()
.build();
}
return INSTANCE;
}
public abstract JournalDao journalDao();
}
JournalRepository.java
public class JournalRepository {
private JournalDao journalDao;
private LiveData<List<Journal>> allJournals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalRepository(Application application) {
JournalDatabase journalDatabase = JournalDatabase.getInstance(application);
journalDao = journalDatabase.journalDao();
allJournals = journalDao.getJournalsByDate();
bookmarkedJournals = journalDao.getBookmarkedJournals();
}
public void insert(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.insert(journal);
});
}
public void update(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.update(journal);
});
}
public void delete(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.delete(journal);
});
}
public void deleteAll() {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.deleteAll();
});
}
public LiveData<List<Journal>> getAllJournals() {
return allJournals;
}
public LiveData<List<Journal>> getBookmarkedJournals() {
return bookmarkedJournals;
}
}
JournalViewModel.java
public class JournalViewModel extends AndroidViewModel {
private JournalRepository repository;
private LiveData<List<Journal>> journals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalViewModel(#NonNull Application application) {
super(application);
repository = new JournalRepository(application);
journals = repository.getAllJournals();
bookmarkedJournals = repository.getBookmarkedJournals();
}
public void insert(Journal journal) {
repository.insert(journal);
}
public void update(Journal journal) {
repository.update(journal);
}
public void delete(Journal journal) {
repository.delete(journal);
}
public void deleteAll() {
repository.deleteAll();
}
public LiveData<List<Journal>> getAllJournals() {
return journals;
}
public LiveData<List<Journal>> getBookmarkedJournals() {
return bookmarkedJournals;
}
}
I'm instantiating this ViewModel inside onActivityCreated() method of both Fragments.
JournalFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
journalAdapter.submitList(list);
}
});
}
BookmarksFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getBookmarkedJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
adapter.submitList(list);
}
});
}
However, the problem when I use this approach is as I delete make some changes in any of the Fragment like delete or update some Journal some other Journal's date field changes randomly.
I was able to solve this issue by using single LiveData object and observe it in both fragments. The changes I had to make in BookmarkFragment is as follows:
BookmarksFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
List<Journal> bookmarkedJournals = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getBookmark() == 1)
bookmarkedJournals.add(list.get(i));
}
adapter.submitList(bookmarkedJournals);
}
});
}
It works properly now.
However, I want to know why it didn't work using my first approach which was to use two different LiveData objects and observe them in different fragments.
Are multiple LiveData objects not meant to be used in single ViewModel?
OR
Are two instances of same ViewModel not allowed to exist together while making changes and fetching different LiveData objects from the same table simultaneously?
I found out the reason causing this problem.
As I was using LiveData with getViewLifecycleOwner() as the LifecycleOwner, the observer I passed as parameter was never getting removed. So, after switching to a different tab, there were two active observers observing different LiveData objects of same ViewModel.
The way this issue can be solved is by storing the LiveData object in a variable then removing the observer as you switch to different fragment.
In my scenario, I solved this issue by doing the following:
//store LiveData object in a variable
LiveData<List<Journal>> currentLiveData = journalViewModel.getAllJournals();
//observe this livedata object
currentLiveData.observer(observer);
Then remove this observer in a suitable Lifecycle method or anywhere that suits your needs like
#Override
public void onDestroyView() {
super.onDestroyView();
//if you want to remove all observers
currentLiveData.removeObservers(getViewLifecycleOwner());
//if you want to remove particular observers
currentLiveData.removeObserver(observer);
}
I have a database in my app. Here's the way how I create it:
App class:
public class TraktTvApp extends Application {
private static Context sAppContext;
public static TraktTvApp instance;
private MovieDatabase database;
#Override
public void onCreate() {
super.onCreate();
sAppContext = getApplicationContext();
instance = this;
database = Room.databaseBuilder(this, MovieDatabase.class, "MovieDatabase").build();
}
#NonNull
public static Context getAppContext() {
return sAppContext;
}
public static TraktTvApp getInstance() {
return instance;
}
public MovieDatabase getDatabase() {
return database;
}
}
DAO class
#Dao
public interface MovieDao {
#Query("SELECT * from MovieEntity")
List<MovieEntity> getFavorites();
#Insert(onConflict = OnConflictStrategy.REPLACE)
Completable insertMovie(final MovieEntity movie);
#Delete
void deleteMovie(MovieEntity movie);
}
Database class
#Database(entities = {MovieEntity.class}, version = 1)
public abstract class MovieDatabase extends RoomDatabase {
public abstract MovieDao movieDao();
}
And here's the way how I call insert method:
mCompositeDisposable.add(Observable.fromCallable(()->movieDao.insertMovie(movieEntity))
.doOnSubscribe(disposable -> mView.showLoadingIndicator(true))
.doOnComplete(() -> {
mView.showEmptyState(false);
mView.onMoviesAdded();
})
.doOnError(throwable -> mView.showEmptyState(true))
.doOnTerminate(() -> mView.showLoadingIndicator(false))
.observeOn(Schedulers.io())
.subscribe());
But when I want to check data in my database in Stetho, there's nothing here:
So, what's the matter and how can I solve this problem? It seems to me that it can be problem in creating database, but I used the same way as usual and usually it works ok
call setupDebugTools() in application's onCreate() like
{
super.onCreate()
setupDebugTools()
}
And
private void setupDebugTools() {
if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this)
}
}
for more information
What the proper way to create DAO with Room and Retrofit?
I have database module like this:
#Module
public class ApplicationDatabaseModule {
private final String mDatabaseName;
ApplicationDatabase mApplicationDatabase;
public ApplicationDatabaseModule(#ApplicationContext Context context, Class<? extends ApplicationDatabase> roomDataBaseClass, String databaseName) {
mDatabaseName = databaseName;
mApplicationDatabase = Room.databaseBuilder(context, roomDataBaseClass, mDatabaseName).build();
}
#Singleton
#Provides
ApplicationDatabase provideApplicationDatabase() {
return mApplicationDatabase;
}
#Singleton
#Provides
CitiesDao provideCitiesDao() {
return mApplicationDatabase.getCitiesDao();
}
}
POJO class like this:
#Entity
public class City {
#PrimaryKey
#ColumnInfo(name = "id")
private int cityId;
#ColumnInfo(name = "name")
private String cityName;
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
#Override
public String toString() {
return "City [cityId = " + cityId + ", cityName = " + cityName + "]";
}
}
DAO interface like this:
#Dao
public interface CitiesDao {
#Insert
void insertCities(City... cities);
#Query("SELECT * FROM City")
City[] queryCities();
}
And API for Retrofit:
public interface CitiesApi {
#GET("/api/cities")
Call<City[]> requestCities();
}
As I know DAO is responsible for accessing data, including data passed through REST-client. But these two parts are represented by interfaces and built into separate classes. What is the proper way to implement DAO?
DAO is responsible for accessing data
yes
, including data passed through REST-client.
God no
What is the proper way to implement DAO?
Room already generates a proper way of implementation for your DAO based on your interface + annotations, I think it's called CitiesDao_Impl.
What the proper way to create DAO with Room and Retrofit?
Room doesn't know about Retrofit and shouldn't need to know about Retrofit. It only cares about local data persistence.
Meaning your DAO needs to look like this:
#Dao
public interface CitiesDao {
#Insert
#Transaction
void insertCities(City... cities);
#Query("SELECT * FROM City")
LiveData<List<City>> queryCities();
}
So what you actually need is a Worker that will fetch new data in background when either cache is invalid (force fetch new data) or when your sync task should run (for example when device is charging and you are on WIFI and you're at 2 AM to 7 AM -- for this you'd need WorkManager).
Immediately fetching new data though is fairly easy, all you need is either an AsyncTask in a singleton context that returns null from doInBackground, or your own Executor that you post your background task to.
public class FetchCityTask extends AsyncTask<Void, Void, Void> {
...
#Override
protected Void doInBackground(Void... params) {
List<City> cities = citiesApi.requestCities().execute().body(); // TODO error handling
citiesDao.insertCities(cities);
return null;
}
}
And then
new FetchCityTask(...).execute();
Now when this task runs, your UI will be updated with latest data by observing the LiveData that you store in a ViewModel.
public class CitiesViewModel
extends ViewModel {
private final CitiesDao citiesDao;
private LiveData<List<City>> liveResults;
public CitiesViewModel(...) {
...
liveResults = citiesDao.queryCities();
}
public LiveData<List<City>> getCities() {
return liveResults;
}
}
And
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recycler_view);
CitiesViewModel viewModel = ViewModelProviders.of(this).get(CitiesViewModel.class, ...);
...
viewModel.getTasks().observe(getViewLifecycle(), list -> {
//noinspection Convert2MethodRef
listAdapter.submitList(list);
});
}
You want to create a repository class to handle your data. Then you simply interact with your repository. Some pseudocode for you:
class Repository {
private CitiesDao localSource;
private CitiesApi remoteSource;
public Repository() {
//initialize objects here
}
City[] getCities() {
if (networkIsAvailable) {
City[] cities = remoteSource.requestCities();
saveCitiesToDatabase(cities);
return cities;
} else {
return localSource.queryCities();
}
}
private void saveCitiesToDatabase(City[] cities) {
//TODO save cities to databse
}
}
My app is using Android's Architecture components library and is displaying a list of items fetched from a paginated REST api with an infinite scroll effect.
What I'm trying to do is to use the Paging Library in conjunction with a NetworkBoundResource, so that when the user scrolls down the list, the next items are fetched from the database and displayed if they exist, and the API is simultaneously called to update items in DB.
I could not find any example of these two patterns cohabiting.
Here is the DAO:
#Query("SELECT * FROM items ORDER BY id DESC")
LivePagedListProvider<Integer,MyItem> loadListPaginated();
Here is my NetworkBoundResource implementation:
public class PagedListNetworkBoundResource extends NetworkBoundResource<PagedList<MyItem>, List<MyItem>> {
#Override
protected void saveCallResult(#NonNull List<MyItem> items) {
// Inserting new items into DB
dao.insertAll(items);
}
#Override
protected boolean shouldFetch(#Nullable PagedList<MyItem> data) {
return true;
}
#NonNull
#Override
protected LiveData<PagedList<MyItem>> loadFromDb() {
return Transformations.switchMap(dao.loadListPaginated().create(INITIAL_LOAD_KEY, PAGE_SIZE),
new Function<PagedList<MyItem>, LiveData<List<MyItem>>>() {
#Override
public LiveData<PagedList<MyItem>> apply(final PagedList<MyItem> input) {
// Here I must load nested objects, attach them,
// and return the fully loaded items
}
});
}
#NonNull
#Override
protected LiveData<ApiResponse<List<MyItem>>> createCall() {
// I don't get the current paged list offset to perform a call to the API
return ...;
}
}
I also search lot about NetworkBoundResource i came to conclusion that NetworkBoundResource & Paging Lib its not related to each other. They both have there own functionality
As per article give by google about paging library
https://developer.android.com/topic/libraries/architecture/paging.html
1.for loading data from local db you need use DataSource
My Dao
#Dao
public interface UserDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(User... user);
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(List<User> users);
#Query("Select * from User ")
public abstract DataSource.Factory<Integer,User> getList();
}
2.then requesting data from network we need implement BoundaryCallback class with LivePagedListBuilder
public class UserBoundaryCallback extends PagedList.BoundaryCallback<User> {
public static final String TAG = "ItemKeyedUserDataSource";
GitHubService gitHubService;
AppExecutors executors;
private MutableLiveData networkState;
private MutableLiveData initialLoading;
public UserBoundaryCallback(AppExecutors executors) {
super();
gitHubService = GitHubApi.createGitHubService();
this.executors = executors;
networkState = new MutableLiveData();
initialLoading = new MutableLiveData();
}
public MutableLiveData getNetworkState() {
return networkState;
}
public MutableLiveData getInitialLoading() {
return initialLoading;
}
#Override
public void onZeroItemsLoaded() {
//super.onZeroItemsLoaded();
fetchFromNetwork(null);
}
#Override
public void onItemAtFrontLoaded(#NonNull User itemAtFront) {
//super.onItemAtFrontLoaded(itemAtFront);
}
#Override
public void onItemAtEndLoaded(#NonNull User itemAtEnd) {
// super.onItemAtEndLoaded(itemAtEnd);
fetchFromNetwork(itemAtEnd);
}
public void fetchFromNetwork(User user) {
if(user==null) {
user = new User();
user.userId = 1;
}
networkState.postValue(NetworkState.LOADING);
gitHubService.getUser(user.userId,20).enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
executors.diskIO().execute(()->{
if(response.body()!=null)
userDao.insert(response.body());
networkState.postValue(NetworkState.LOADED);
});
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
String errorMessage;
errorMessage = t.getMessage();
if (t == null) {
errorMessage = "unknown error";
}
Log.d(TAG,errorMessage);
networkState.postValue(new NetworkState(Status.FAILED, errorMessage));
}
});
}
}
3.My VM Code to load data from DB + Network
public class UserViewModel extends ViewModel {
public LiveData<PagedList<User>> userList;
public LiveData<NetworkState> networkState;
AppExecutors executor;
UserBoundaryCallback userBoundaryCallback;
public UserViewModel() {
executor = new AppExecutors();
}
public void init(UserDao userDao)
{
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
userBoundaryCallback = new UserBoundaryCallback(executor);
networkState = userBoundaryCallback.getNetworkState();
userList = (new LivePagedListBuilder(userDao.getList(), pagedListConfig).setBoundaryCallback(userBoundaryCallback))
.build();
}
}
This assumes that each item in the callback has contains an index/offset. Typically that is not the case - the items may only contain ids.