Replace AsyncTask with Rxjava for Room Database - android

I'm trying to replace AsyncTask with RxJava, since Async is deprecated in Android 11. however I cannnot get RxJava to work with Room,
This is currently working code with Asynctask:
In Repository:
public void insertItems(List<Items> items){
new insertItemsAsyncTask(PrjDao).execute(items);
}
private static class insertItemAsyncTask extends AsyncTask<List<Items>, Void, Void>{
private PrjDao prjDao;
private insertItemAsyncTask(PrjDao prjDao){
this.prjDao = prjDao;
}
#Override
protected Void doInBackground(List<Items>... lists) {
prjDao.insertItems(lists[0]);
return null;
}
}
in DAO
#Insert
void insertItems(List<Items> items);
I replaced repository code with:
public void insertItems(List<Items> items){
Completable.fromAction(() -> prjDao.insertItems(items)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onComplete() {
}
#Override
public void onError(#NonNull Throwable e) {
}
});
}
However It is not working, even though I managed to get Log output in onComplete.

Try:
DAO:
#Insert
Completable insertItems(List<Items> items);
Repository:
public void insertItems(List<Items> items){
prjDao.insertItems(items))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
}
Or better so:
Repository:
public Completable insertItems(List<Items> items){
return prjDao.insertItems(items))
}
And then, subscribe to the completable and handle subscribe callbacks where you actually call insertItems().
insertItems(items)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
In my opinion, repository should only provide bridge to the DB interface, and the caller should be the one to handle the subscribe callbacks, as each caller may want to handle the callbacks differently.
UPDATE
To use rxjava with room, please check that you have all needed dependencies in your build.gradle file:
implementation "androidx.room:room-runtime:[roomVersion]"
implementation "androidx.room:room-rxjava2:[roomVersion]"
annotationProcessor "androidx.room:room-compiler:[roomVersion]"
I currently use roomVersion 2.2.5
Here is a simple working demo of room + rxjava I just created, maybe you will find differences there:
https://github.com/phamtdat/RxRoomDemo

Related

Using Rxjava to insert , delete from room db

i'm working on project where i have to insert and delete data from room db , so basically i was using the old approach which is to implement Asynctask for background operations but since it is no longer recommended , i decided to use Rxjava instead , i tried to implement it but i'm not getting any result so far , and this is a piece of code where it shows the insertion of data
Completable.fromAction(new Action() {
#SuppressLint("CheckResult")
#Override
public void run() throws Exception {
recordingDb.insertRecording(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
And this is the deletion method
public void DeleteData(modelUidd modelUidd) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
recordingDb.delete(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
So basically i tried to use completable with the operator fromaction , i'm not sure if what i implemented is correct or not , any help would appreciated guys , thank you
The problem is that you are actually not subscribing to the observables, so nothing is happening.
To subscribe to an observable, you have to call the .subscribe() method.
I suggest that your methods defined in your DAO classes (or you "repository" classes), such as DeleteData in your example, return the Observable. Then, you can call the method in the DAO to get the Observable and subscribe to it from (ideally) a ViewModel or, if not, directly from an Activity. The moment you call the subscribe you will trigger the actual insertion or deletion, and will get a response from the onSuccess or onError defined callbacks.
For example:
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
You can also check these two other answers for more information:
About async operations in RxJava
Using CompositeDisposable in ViewModel

Android paging works not as expected

I tried to use paging in my project. Unfortunately, it works not as I expected. I expected that the liveDataObserver will work after callBack.onResult.But in fact, the liveDataObserver observes immediately when the loadInitial finished.The callBack works later, and didn't post data to the observer.
The code:
First I wrote a class extend PageKeyedDataSource and interface SingleCreator
public class MyPagingDataSource<T> extends PageKeyedDataSource<Integer, T>
public interface SingleCreator<T> {
SingleSubscribeProxy<Page<T>> createSingle(int page, int pageSize);
}
Then the constructor of MyPagingDataSource:
public MyPagingDataSource(SingleCreator<T> singleCreator) {
this.singleCreator = singleCreator;
}
And override loadInitial:
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, T> callback) {
singleCreator.createSingle(1, params.requestedLoadSize)
.subscribe(new SingleObserver<Page<T>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Page<T> ts) {
callback.onResult(ts.list, ts.pageNumber, ts.total, ts.pageNumber - 1, ts.pageNumber + 1);
Timber.d("registerLiveData" + ts.list.size());
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
});
try {
//when I add this, observer will work after callback
//And if not observer works before callback.onResult
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Then the datasourceFactory is just newInstanced in viewModel:
public LiveData<PagedList<MyData>> page = loadPageData();
public LiveData<PagedList<MyData>> loadPageData() {
return new LivePagedListBuilder<>(new DataSource.Factory<Integer, MyData>() {
#Override
public DataSource<Integer, MyData> create() {
return new HBPagingDataSource<>((page, pageSize) -> loadPageSingle(page, pageSize));
}
}, 2).build();
}
the single
private SingleSubscribeProxy<Page<MyData>> loadPageSingle(int pageNum, int pageSize) {
return mModel.loadMyDates(pageNum, pageSize)
.doOnError(Throwable::printStackTrace)
.as(autoDisposable(this));
}
at fragment
mViewModel.page.observe(this, myDatas -> {
Timber.d("registerLiveData%s", myDatas.size());
myAdapter.submitList(myDatas);
});
Maybe related things:
I wrote subscribeOn and observeOn in retrofit's callAdapter
The viewModel is a scopeProvider since I'm using autoDispose
I tried some example in github. And it seems, the setValue for pageLivedata is always work after loadInitial. In this case, how can I use single?
It's seems solved.
The error is because schedule the thread using rxjava.
It makes single and datasource work in different thread.
In this case, callback onResult run after the observer.
So, I updated the callAdapter where I wrote subscribeOn and observeOn for single.
Filter by className when It's Page class, it won't do subscribeOn and observeOn.
Now the conclusion is, let paging handle the thread.

Realm accessed from incorrect thread, with Dagger

I am currently learning Dagger, RxJava, Realm and MVP in a simple project.
Basically what this app can do is it can view, add, delete and update data from database, which I'm using Realm.
I have decided to follow MVP architecture and applied repository pattern as well for data manipulation at the back end layer.
For an extra learning, I added Dagger for the dependency injection in the architecture.
Before this, I have developed an app without applying MVP nor repository pattern, not even Dagger and RxJava in mind. All seems to work well without any errors from Realm threading system. Maybe because I tied everything in a single class.
So, now that I'm moving away from that approach, I'm now having trouble implementing it in the new approach, which I think is more loosely coupled and should be better if implemented correctly.
Enough of introduction, let's get back to the topic.
The issue I'm facing right now is Realm always giving me this error:
Exception has been thrown: Realm accessed from incorrect thread.
I was suspecting that my Dagger graph isn't properly managed (especially on providing Realm instance), thus whenever I make query for data, it gives me the error.
So, my Dagger component looks like this:
#Singleton
#Component(modules = {ContextModule.class, RepositoryModule.class, PresenterModule.class})
public interface AppComponent {
/* Inejct application */
void inject(FourdoApp fourdoApp);
/* Realm Helper */
void inject(DatabaseRealm databaseRealm);
/* Activity */
void inject(MainActivity mainActivity);
void inject(TaskDetailActivity taskDetailActivity);
/* Presenter*/
void inject(MainPresenter mainPresenter);
void inject(TaskDetailPresenter taskDetailPresenter);
/* Model repository*/
void inject(TaskRepositoryImpl taskRepository);
}
Inside RepositoryModule.class;
#Module
public class RepositoryModule {
#Provides
#Singleton
Repository<Task> provideTaskRepository() {
return new TaskRepositoryImpl();
}
// Here I provide DatabaseRealm class instance
#Provides
#Singleton
public DatabaseRealm provideDatabaseRealm() {
return new DatabaseRealm();
}
}
Not sure whether I did this correctly or not. You can view the source for DI here.
For the data request to happen, inside MainActivity, I injected MainPresenter and call onRequestData interface to request it from the Presenter. From there, Presenter will make the call to Repository for the said data.
public class MainActivity extends BaseActivity implements MainContract.View {
#Inject
MainPresenter mainPresenter;
// ...
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Injecting MainActivity class
Injector.getAppComponent().inject(this);
mainPresenter.attachView(this);
// Requesting for data from Presenter
mainPresenter.onRequestData();
}
// ...
#Override
public void onRequestDataSuccess(List<String> taskList) {
doAdapter.addAll(taskList);
doAdapter.notifyDataSetChanged();
}
}
Inside MainPresenter, I injected Repository interface to make request from TaskRepositoryImpl for the real data from database.
public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter {
#Inject
Repository<Task> taskRepository;
public MainPresenter() {
Injector.getAppComponent().inject(this);
}
#Override
public void onRequestData() {
requestData();
}
private void requestData() {
taskRepository.findAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.map(this::mapToStringList)
.subscribe(new Observer<List<String>>() {
#Override
public void onNext(List<String> strings) { // Error in this line
if (strings.size() > 0) {
mView.onRequestDataSuccess(strings);
} else {
mView.showEmpty();
}
}
});
}
}
Inside TaskRepositoryImpl, here is how I did the findAll and it should return data from DatabaseRealm:
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
List<Task> models = databaseRealm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
Code for DatabaseRealm are as follows:
public class DatabaseRealm {
#Inject
Context context;
RealmConfiguration realmConfiguration;
public DatabaseRealm() {
Injector.getAppComponent().inject(this);
}
public void setup() {
if (realmConfiguration == null) {
Realm.init(context);
realmConfiguration = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
} else {
throw new IllegalStateException("Realm already configured");
}
}
public Realm getRealmInstance() {
return Realm.getDefaultInstance();
}
public <T extends RealmObject> T add(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealm(model);
realm.commitTransaction();
return model;
}
public <T extends RealmObject> T update(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealmOrUpdate(model);
realm.commitTransaction();
return model;
}
public <T extends RealmObject> T remove(T model) {
Realm realm = getRealmInstance();
realm.beginTransaction();
realm.copyToRealm(model);
realm.deleteAll();
realm.commitTransaction();
return model;
}
public <T extends RealmObject> List<T> findAll(Class<T> clazz) {
return getRealmInstance().where(clazz).findAll();
}
public void close() {
getRealmInstance().close();
}
}
Full source code for this flawed code is right here.
I'd like to make it clear that I have limited knowledge on Realm instances being used in Dagger.
I followed this tutorial for the Repository Design Pattern with Realm, but it doesn't include Dagger for its dependency injection.
Can someone guide me on why it is always telling I'm calling Realm from incorrect thread?
I think you get this error because of this:
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
List<Task> models = databaseRealm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
You are subscribing to io Thread but you inject your databaseRealm in Main Thread.
if you get instance in your observable's create you'll not get this error.
#Override
public Observable<List<Task>> findAll() {
return Observable.create(subscriber -> {
try {
Realm realm = getRealmInstance();
List<Task> models = realm.findAll(Task.class);
subscriber.onNext(models);
subscriber.onComplete();
} catch (Exception e) {
subscriber.onError(e);
}
});
}
You need to setup RealmConfigration only once in the Application class and use the Realm.getDeafultInstance() method to access Realm Database
With Dagger you need to Pass only realm instance in constructor
You can follow this Example and fork it
Its not exactly the same code you posted here.But it might help to understand dagger better with MVP ,RxJava and Realm

In what occasion can onNext() be called more than once?

I have defined and interface, with an endpoint that returns JSON. Retrofit converts this JSON into MyObject. It could be also a list, map, etc, it doesn't matter now.
This is how I subscribe.
subscription = Retrofit.create(MyApi.class)
.doSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MyObject>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(MyObject myObject) {
}
});
My question is:
Is it possible that onNext is called more than once?
If yes, in which occasion?
In your case, no it's impossible, of course if you do not emit more items in doSomething() method.
But there is another, quite usual cases, for instance, if you use Local first approach and subscribing on hot observable which will emit new item each time when data in data base has change.
E.g. using retrofit:
#Override
public Observable<List<FollowMeUser>> getFollowMeUsers() {
return realm.where(FollowMeUser.class)
.findAll()
.asObservable()
.filter(RealmResults::isLoaded);
}
getFollowMeUsers()
.subscribe(users -> {Timber.d("saved data has changed")}, Timber::e);
Each time when you will insert/modify/delete FollowMeUser collection, all subscribers of getFollowMeUsers will be notified.
If your retrofit returns an array/list of data, onNext is called multiple times.
But if your retrofit returns a single data objext, onNext will be called only once.
Example:
//POJO
class User {
int userId;
String UserName;
}
//POJO
class UserData {
List<User> users;
}
interface RetrofitGithub {
#GET("...")
Observable<List<User>> getUsers();
#GET("...")
Observable<UserData> getUserData();
}
If you subscribe to getUsers() onNext will be called multiple N times.(N = size of the list)
If you subscribe to getUserData() onNext will be called only once.

Schedulers.immediate() not working with command line gradle tests

I'm trying to test a presenter that use RxJava to retrieve data from an interactor. In the setup method I'm doing something like:
#Before
public void setup() {
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
So in my test method I can test the presenter call:
#Test
public void testLoad() {
presenter.load();
verify(view).dataLoaded(data);
verify(interactor).load();
}
If I run the test with Android Studio everything work as expected, the issue is that If I try on command line
gradle test
Then the test fails because:
Actually, there were zero interactions with this mock.
So I've tried to put a Thread.sleep(2000) right after the call to the presenter and then it works, so I guess the Schedulers.immediate(); is not working from command line but I have no idea why and how to debug/fix. Do you have any idea?
EDIT: presenter implementation ->
public void load() {
Observable<List<Data>> obs = interactor.load()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
obs.subscribe(new Observer<List<Data>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Data> data) {
view.dataLoaded(data);
}
});
}
You can mock RxJava Schedulers as well.
RxJavaHooks.reset();
RxJavaHooks.setOnIOScheduler(scheduler -> Schedulers.immediate());
Typically a good thing to call reset on setup & teardown.

Categories

Resources