Android Room with RxJava handle empty query result - android

Trying to test new Android Room librarty with RxJava adapter. And I want to handle result if my query returns 0 objects from DB:
So here is DAO method:
#Query("SELECT * FROM auth_info")
fun getAuthInfo(): Flowable<AuthResponse>
And how I handle it:
database.authDao()
.getAuthInfo()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.switchIfEmpty { Log.d(TAG, "IS EMPTY") }
.firstOrError()
.subscribe(
{ authResponse -> Log.d(TAG, authResponse.token) },
{ error -> Log.d(TAG, error.message) })
My DB is empty, so I expect .switchIfEmty() to work, but none of handling methods is firing. Neither .subscribe() nor .switchIfEmpty()

Db Flowables are observable (so they keep dispatching if database changes) so it never completes. You can try returning List<AuthResponse>. We've considered back porting an optional but decided not to do it, at least for now. Instead, we'll probably add support for Optional in different known libraries.

In version 1.0.0-alpha5, room added support of Maybe and Single to DAOs, so now you can write something like
#Query("SELECT * FROM auth_info")
fun getAuthInfo(): Maybe<AuthResponse>
You can read more about it here

switchIfEmpty takes as parameter a Publisher<AuthResponse>. Through SAM-conversion your given anonymous function is turned into this class. However it does not follow the behavior expected from a Publisher so it will not work as expected.
Replace it with a correct implementation like Flowable.empty().doOnSubscribe { Log.d(TAG, "IS EMPTY") } and it should work.

You could use some wrapper for result. For example:
public Single<QueryResult<Transaction>> getTransaction(long id) {
return createSingle(() -> database.getTransactionDao().getTransaction(id))
.map(QueryResult::new);
}
public class QueryResult<D> {
public D data;
public QueryResult() {}
public QueryResult(D data) {
this.data = data;
}
public boolean isEmpty(){
return data != null;
}
}
protected <T> Single<T> createSingle(final Callable<T> func) {
return Single.create(emitter -> {
try {
T result = func.call();
emitter.onSuccess(result);
} catch (Exception ex) {
Log.e("TAG", "Error of operation with db");
}
});
}
And use it like 'Single' in this case you will get result in any case. Use:
dbStorage.getTransaction(selectedCoin.getId())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(r -> {
if(!r.isEmpty()){
// we have some data from DB
} else {
}
})

Related

Coroutines Flow with Dao and generic Result class

I have a Dao class which returns List of Car objects as flow.
#Query("SELECT * FROM Car")
fun getAllCars(): Flow<List<Car>>
In my repository, I can use this Dao as follows
fun getAllCars(): Flow<List<Car>>
//Implementation
override fun getAllCars() = carDao.getAllCars()
I can observe this flow in view model and everything works and life was fine.
Now, after reading the post on Developer Android site about
A generic class that contains data and status about loading this data.
I got inspired, so I read one more post here which discuss about having Result class.
So, I have done some changes to repository and I am not able to solve them.
Error:
suspend fun getSomeData(): Flow<Result<List<Car>>> {
carDao.getAllCars().collect {
if (it.isNotEmpty()) {
return flowOf(Result.Success(it)) //<-- Here I am getting error from IDE
}
else {
val throwable = Throwable()
return flowOf(Result.Failure<List<Car>>(throwable)) //<-- Here I am getting error from IDE
}
}
}
The error is Return is not allowed here and Change to 'return#Collect'
What I want to achieve is:
// At view model side
viewmodelScope.launch {
repo.getSomeData().collect {
if (it == Result.Success) {
//Show data
}
else {
//Show empty screen
}
}
}
Is my approach of implementation of Result is wrong? I am not able to figure out what is wrong. Why I can't just return Flow from a flow
If you want to use Result, you should should return Result < YourClass>. It will look like that :
suspend fun getSomeData(): Result<Flow<List<Car>>> {
return carDao.getAllCars().collect {
if (it.isNotEmpty()) {
Result.Success(flowOf(it))
} else {
Result.Failure(Throwable()))
}
}
}
This is what your function should look like. Note there's no need for it to be a suspend fun.
fun getSomeData(): Flow<Result<List<Car>>> = flow {
carDao.getAllCars().collect {
if (it.isNotEmpty()) {
emit(Result.Success(it))
}
else {
emit(Result.Failure<List<Car>>(Throwable()))
}
}
}
But what it does is nothing more than adding a mapping step, which you can generalize.
fun <T> Flow<List<T>>.toResultFlow(): Flow<Result<List<T>>> = this.map {
if (it.isNotEmpty()) Result.Success(it)
else Result.Failure(Throwable())
}

Add RetryWhen to Single When DB Insert Fails

I've been trying to figure this out still new to RXJava and still not liking it, rather use Livedata and coroutines but anyway.. I have this:
Single.just(entity)
.map {
insertDb(it)
return#map it
}
.doOnSubscribe { Timber.d("Updating in database") }
.doOnSuccess { Timber.d("Added row in database") }
.doOnError { Timber.e(it, "Unable to insert object in db") }
fun insertDb(entity: T) {
try {
// basic update or insert database.begingTransaction() and endTransaction()
} catch (e: SQLiteDatabaseLockedException) {}
}
So when the error of DB locked occurs I want to catch it and retry the insert using the Single with a retryWhen(). The examples I read are very convoluted and not quite what I want. And don't worry about the locked DB stuff this is just an example of how I would catch DB errors.
You can use a PublishProcessor:
val retryProcessor = PublishProcessor.create<Unit>()
Single.just("Entity")
.map { insertDB(it) }
.doOnError { Log.e(TAG, "Error") }
.retryWhen { retryProcessor }
.subscribe { entity -> Log.i(TAG, "Success: $entity") }
Insert to DB:
fun insertDB(entity: String): String {
// Insert to DB
return entity
}
Whenever you want to perform a retry, call onNext on the PublishProcessor:
retryProcessor.onNext(Unit)
If you just want to retry when a specific exception occurs in the stream, the retry(N) operator would probably be better here. (Where N is the max number of times you want to retry the subscription).
Single.just(entity)
.map {
insertDb(it)
return#map it
}
.retry(1) { e -> e is SQLiteDatabaseLockedException }
You also don't want to catch the exception in your insertDb() function, let it fall into the error stream so you can catch it inside your retry function.
You might also might want to consider returning a Single or Completable from insertDb(), then you call it like insertDb(entity).retry(1)... without using that awkward map that returns itself.
Your insertDb() function would become something like this:
fun <T> insertDb(entity: T): Completable {
return Completable.fromCallable {
// Insert operation
}
}

Best way to get List from Observable in Rxjava

I'm just exploring Rxjava in one of my android application, and got stuck at one place, honestly speaking I'm very new to this library so don't mind if my question frustrate someone;-)
So I'm trying to access the Room Database using RxJava where I'm returning the Observable List, once I get this Observable I'm trying to use map operator to get a list of ids & query again the database, which again returns me the Observable List but the map operator expects List as a return type. How can I tackle this please suggest?
Below is the code snippet:
private void getAllPcbs() {
isLoading.setValue(true);
getCompositeDisposable().add(
getRepositoryManager().loadAllPcbDetails()
.flatMap((Function<List<PcbDetails>, ObservableSource<?>>) pcbDetails -> {
List<Long> pcbList = new ArrayList<>();
for (PcbDetails details : pcbDetails)
pcbList.add(details.getPcbId());
return getRepositoryManager().loadAllPcbs(pcbList);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError)
);
}
private void onError(Throwable throwable) {
isLoading.setValue(false);
}
private void onSuccess(Object o) {
isLoading.setValue(false);
pcbList.setValue((List<Pcb>) o);
}
public interface DbHelper {
Observable<List<PcbDetails>> loadAllPcbDetails();
Observable<List<Pcb>> loadAllPcbs(List<Long> pcbIdList);
}
Go like
getRepositoryManager().loadAllPcbDetails()
.flatMapIterable {
listPcbDetail-> listPcbDetail
// listPcbDetail is ArrayList<PcbDetails>
// Converts your list of ids into an Observable
// which emits every item in the list
}
.flatMap { pcbDetail ->
// pcbDetail is PcbDetails
getRepositoryManager().loadAllPcbs(pcbDetail.pcbIdList)
}.subscribe { listPcb ->
// listPcb is ArrayList<Pcb>
}

RxJava .filter is blocking other code from sending data to API

I'm using RxJava inside an evernote job to send some data to API. This code was working just fine till now. The problem is somewhere in the .filter I think as it isn't even getting to getApiService().createReport(requestModel) method unless there are photos in the model (then the report.getImages() is not null.
public static Observable<Report> createReport(CreateReportModel model) {
return Observable.just(model)
.filter(report -> report.getImages() != null)
.flatMap(report -> {
return Observable.from(report.getImages())
.map(photoModel -> {
return photoModel;
})
.filter(photoModel -> photoModel.hasImage())
.filter(photoModel -> photoModel.getImage().exists())
.flatMap(photoModel -> uploadFile(photoModel)).toList();
})
.map(photoModels -> model)
.flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
}
This function is called inside this code
Observable<PalletReport> report = createReport(model);
report.subscribe(new Subscriber<PalletReport>() {
#Override
public void onCompleted() {
resultHolder.setResult(Result.SUCCESS);
#Override
public void onError(Throwable e) {
Timber.d(e, "Upload Error");
resultHolder.setResult(Result.RESCHEDULE);
}
#Override
public void onNext(PalletReport model) {
Timber.d("On Next " + model);
}
});
And here it goes to Result.SUCCESS but the response isn't get and the report isn't create on back end. My concern is that this code was working just fine a few days ago, and without any changes it stopped.
[EDIT]
I have this function that is called inside the first flatMap, and it's used to send the photos.
#NonNull
private static Observable<? extends CreatePalletPhotoModel> uploadPalletFile(CreatePalletPhotoModel photo) {
MultipartBody.Part fileBody = Paperless.createFileBody(photo.getImage());
return Paperless.getApiService().uploadPalletPhoto(fileBody)
.map(upload -> {
photo.setPalletStatus(upload.getPalletStatus());
photo.setImage(upload.getImage());
return photo;
});
}
If there are no reports after filter nothing will get executed. Consider removing
.map(photoModels -> model)
and just end the first observable there (you would need to subscribe to it) and start again with
Observable.just(model).flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
that will ensure that getApiService call is always executed.

Rxjava - isolation of logic

Basically , I want to check if i have data in my DB, and if i dont have, make an api call. I'm using this logic for making the request to the API:
private void requestDataToApi() {
mSubscribe = createRequest()
.delay(DELAY_SPLASH_SCREEN_SECONDS, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(categoryModels -> {
writeDataToDb(categoryModels);
}, (throwable -> {
dealError();
}));
}
And this logic to verify if there any data stored:
if (mRealm.where(CategoryModel.class).findAll().size() == 0) {
requestDataToApi();
} else {
getView().openMainActivity(readDataFromDb());
}
There is any way to join this both logics? Basically, be the dispose verifying the db and just make the call if needed?
You can use filter and switchIfEmpty operator
#Test
public void ifEmpty() throws InterruptedException {
Observable.just(getDataFromDatabase())
.filter(value -> !value.isEmpty())
.switchIfEmpty(Observable.just("No data in database so I go shopping"))
.subscribe(System.out::println);
}
private String getDataFromDatabase() {
if(new Random().nextBoolean()){
return "data";
}
return "";
}
You can learn more from reactive world here https://github.com/politrons/reactive
Looks like you need the Repository Pattern
What this pattern does it isolate the business logic from the data origin. So you just ask for data and don'r care where this data come from. So you could hava something like:
public class CategoryModelRepo {
public Observable<CategoryModel> getAll() {
return Observable.defer(() -> {
List<CategoryModel> fromRealm = mRealm.where(CategoryModel.class).findAll();
if (fromRealm.size() == 0) {
return requestDataToApi()
.onNext(dataList -> storeDataInRealm(dataList))
} else {
return Observable.just(fromRealm);
}
}
}
// This code fragment could be improved by applying a DAO pattern
// http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
private Observable<CategoryModel> requestDataToApi() {
return createRequest()
.delay(DELAY_SPLASH_SCREEN_SECONDS, TimeUnit.SECONDS)
}
So from your business layer (or, in your case, view layer) you can load the data to ensure it has been stored locally.
Don't forget to use .subscribeOn(...) and .observeOn(...) where necessary
If you are willing to add one more dependency to your project, Store is a (very) nice solution. Otherwise, I would recommend using concepts from the Repository pattern.

Categories

Resources