How I can improve this RxJava code with Room and Retrofit? - android

I am working with API and Room to get list of Service Technician.
I want to do the following:
Query to getAll() from the Service Technician database table.
Display results to users instantly.
If it is empty or fails to get database results, make API call to get the same list.
Make an API call to get updated list of Service Technician.
Save them into a database table.
Refresh UI changes based on the latest database content.
Here is what I have, and it works. But I think that it can be improved:
This looks like not needed call, if I could use somehow Flowable .flatMap(ids -> techniciansDbRepository.getServiceTechnicians())
I can not use Flowable since filter(ServiceTechnician::isActive).toList(); never completes and I do not get result in subscribe().
public void getServiceTechnicians() {
disposables = RxUtil.initDisposables(disposables);
Disposable disposable = techniciansDbRepository.getServiceTechnicians()
.subscribeOn(Schedulers.io())
.onErrorResumeNext(throwable -> techniciansApiRepository.getServiceTechnicians())
.flatMap(serviceTechnicians -> techniciansApiRepository.getServiceTechnicians())
.flatMap(techniciansDbRepository::insertAll)
.flatMap(ids -> techniciansDbRepository.getServiceTechnicians())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
serviceTechnicians -> view.onServiceTechniciansLoaded(serviceTechnicians),
view::handleError
);
disposables.add(disposable);
}
public class ServiceTechniciansDbRepository implements ServiceTechniciansRepository {
private final ServiceTechnicianDao dao;
public ServiceTechniciansDbRepository(ServiceTechnicianDao dao) {
this.dao = dao;
}
#Override public Single<List<ServiceTechnician>> getServiceTechnicians() {
return dao.getAll()
.flatMapPublisher(Flowable::fromIterable)
.map(serviceTechnicianAndUser -> ServiceTechnicianMapper.convertToApiModel(
serviceTechnicianAndUser.getServiceTechnician(),
serviceTechnicianAndUser
))
.filter(ServiceTechnician::isActive)
.toList();
}
}
#Dao public abstract class ServiceTechnicianDao implements BaseDao<ServiceTechnicianDb> {
#Transaction
#Query("SELECT * FROM service_technician")
public abstract Single<List<ServiceTechnicianAndUser>> getAll();
#Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void insertUser(UserDb user);
}
public interface BaseDao<E> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
Single<Long> insert(E e);
#Insert(onConflict = OnConflictStrategy.REPLACE)
Single<List<Long>> insert(List<E> list);
#Update Single<Integer> update(E e);
#Delete Single<Integer> delete(E e);
}
public class ServiceTechniciansApiRepository implements ServiceTechniciansRepository {
private final ServiceTechnicianApi api;
public ServiceTechniciansApiRepository(ServiceTechnicianApi api) {
this.api = api;
}
#Override public Single<List<ServiceTechnician>> getServiceTechnicians() {
return api.getServiceTechnicians()
.subscribeOn(Schedulers.io());
}
#Override public Single<List<Long>> insertAll(List<ServiceTechnician> serviceTechnicians) {
return Single.error(new CanNotBeUsedWithApiException());
}
}
Any idea of how I could improve more this code?

Related

Room Database - How to return entire row after insert?

I have query that inserts a row
#Dao
public interface NoteDao {
#Insert
Long insert(Note note);
}
I'm using RxJava to perform the query on the background thread:
public void insert(Note note) {
Single.fromCallable(() -> noteDao.insert(note))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Long>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull Long aLong) {
Log.d(TAG, "onSuccess: New row Id: " + aLong);
}
#Override
public void onError(#NonNull Throwable e) {
}
});
}
Currently it successfully returns, the newly created primary key for the inserted DAO. How would I return the entire inserted row, WITHOUT performing a second query using the new row id?
In postgresql I would do something like this:
`INSERT INTO note_table(note_title, note_description) VALUES ($1, $2) RETURNING *`
Not sure how to do it with the Room library
As stated in the documents of Transcation, if you want to perform two queries in one shot you have to use transactions and there is no other option as far as I know for standard database operations
check below as here we are doing you should do similar
#Dao
public interface NoteDao {
#Insert
Long insert(Note note);
##Query(“SELECT * FROM Note WHERE noteId = :id)
Long getNote(id Long);
#Transaction
public void insertAndRetrieve(Note note):Note {
// Anything inside this method runs in a single transaction.
val id = insert(note);
return getNote(id);
}
}

Why database isn't showed in Stetho

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

RxJava: Nested Observers

I have a really tricky problem.
I have a method of void insert(User user) in my DAO. I wrapped it with :
public Completable insertUser(User user) {
return Completable.fromAction(() -> userDao.insert(user))
.subscribeOn(Schedulers.io())
which returns a Completable.
In my ViewModel, I just return the same thing:
public Completable insertUser(User user) {
return userDao.insertUser(user);
And in my UI, I observe on the completable :
vm.insertUser(vm.getSomeUsers())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
// Do nothing
}
#Override
public void onComplete() {
userAdapter.populate( ? ) // here I need the refreshed user
}
#Override
public void onError(Throwable e) {
}
});
The problem is that my insert does not return the updated user, I cannot change it to Observable<User> insertUser() because I'm using a lower version of Room.
So my question is, how can I populate the adapter with the updated user ? I have another method in my dao Flowable<User> getUsers(), but I have to use another observer to retrieve the users, so that leads to nested observers.
What is the best way to do it ?
You can do it as simple as
public Single<User> insertUser(User user) {
return Completable.fromAction(() -> userDao.insert(user))
.subscribeOn(Schedulers.io())
.andThen(Single.just(user));
It will return user you just inserted.

Combine Room and Retroffit with Dagger2

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
}
}

How to integrate Android Paging Library with NetworkBoundResource

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.

Categories

Resources