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>
}
Related
I have following code that works well.
Observable.from(...)
.map { // List<Object>
if (My_Condition_is_true) {
//...
}
val newList = getNewListIfConditionIsOkay(it)
newList.map { item -> toSomethingElse(item) }
}
.subscribeBy(myErrorFun) {
//...
}
I feel map operator does not looks cool but I have no idea how to fix it. This is what is in my mind.
Observable.from(...)
.doOnNext {// List<Object>
if (My_Condition_is_true) {
//...
return getNewListIfConditionIsOkay(it)
}
return it
.map { // List<Object>
it.map { item -> toSomethingElse(item) }
}
.subscribeBy(myErrorFun) {
//...
}
My Observable returns only a list. What is your recommendation?
map is fine. Save doOnNext for side effect tasks, doOnNext actually doesn't return any value, so I don't think your code would even work here.
(I don't know if I completely understand your idea or not)
As far as I know, currently there no operator allows us to do as you want.
So, in order to solve your problem, the way I always try is combine operations.
Please see the details below:
First: a method to get Your List
private List getYourList() {
// do something here to get your list
return yourList;
}
Second: A method to get List with condition, remember to use Observable.fromCallable
private Observable<List> getListWithCondition() {
return Observable.fromCallable(new Callable<List<Employee>>() {
#Override
public List<Employee> call() throws Exception {
// check your condition if needed
if (My_Condition_is_true) {
//...
}
val newList = getNewListIfConditionIsOkay(it);
return newList;
}
});
}
Finally, do your work by calling function above
public void doYourWork() {
getListWithCondition().map(new Func1<List<>, Object>() {
item -> toSomethingElse(item)
}).subscribe();
}
Please let me know if I'm not get your point correctly, I'll remove my answer.
Hope that help.
I need to get the categories, and then get the channels of that categories, and finally invoke a method when all categories and their channels are retrieved from the server. I guess that I need to use RxJava, but I could not find a similar implementation. (Preferably without using lambda/retrolambda expressions).
#GET("/api/{categoryId})
Call<Category> getCategory(#Path("categoryId") String categoryId)
private void getCategories() {
for (Tab t : tabs) {
Call<Category> getCategory = videoAPI.getCategory(t.getId());
getCategory.enqueue(new Callback<Category>() {
#Override
public void onResponse(Call<Category> call, Response<Category> response) {
Category cat = response.body();
categories.add(cat);
// I will call the getChannels(String categoryId) method here,
// however I think implementing RxJava would be much better.
}
#Override
public void onFailure(Call<Category> call, Throwable t) {
Log.i(TAG, "failure: " + t.getLocalizedMessage());
}
});
}
}
You can do that with
Observable
.fromArray(/*your list of observables go here, make sure that within flatMap you get as type Observable<T>, not Observable<List<T>>*/)
.flatMap(/*here you subscribe every item to a different thread, so they're parallel requests: subscribeOn(Schedulers.computation())*/)
.subscribe (/*each request*/,/*error*/,/*completed all requests*/)
Now your request needs to be of type Observable
#GET("/api/{categoryId})
Observable<Category> getCategory(#Path("categoryId") String categoryId)
Example code in Java:
// Setup a list of observables
List<Observable<Category>> parallelRequests = new ArrayList<>();
for (Tab t : tabs) {
parallelRequests.add(videoAPI.getCategory(t.getId()));
}
Observable[] array = new Observable[parallelRequests.size()];
// Convert the list to array
parallelRequests.toArray(array);
Observable
.fromArray(array)
.flatMap(observable -> observable.subscribeOn(Schedulers.computation()))
.subscribe(o -> {
// each request is fulfilled
}, Throwable::printStackTrace, () -> finishFunctionHere());
Or if you're using Kotlin
Observable
// The asterisk is called "spread operator": It converts an array to vararg
.fromArray(*tabs.map { api.getCategory(it.getId()) }.toTypedArray())
.flatMap { it.subscribeOn(Schedulers.computation()) }
.subscribe({ category ->
// onEach
}, Throwable::printStackTrace, {
// All requests were fulfilled
})
I develop an app with mpv (mosby3) + socket.io.
I want to use rxjava 2 to relate provider and repository.
I have CategoryManager
public class CategoryManager {
private List<Category> list = null;
...
}
If list not null i can do it
public Single<List<Category>> getList() {
return Single.just(this.list);
}
But if I need load the list I have do it async like a it
socket.on("category", (data) -> {
Type founderListType = new TypeToken<ArrayList<Category>>() {}.getType();
list = gson.fromJson(data[0].toString(), founderListType);
// here i need to generate event to single subscriber
})
I think i should use to
public Single<List<Category>> getList(int count) {
return Single.create(s -> {
if (list == null) {
socket.emit("category");
// i need async load list
} else {
s.onSuccess(list.subList(0, count));
}
});
}
And CategoryPresenter should have code like
disposable.add(session.getCategoryManager()
.getList(5)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
if (isViewAttached()) {
getView().setData(data);
}
}, throwable -> {
throwable.printStackTrace();
})
);
}
I think i can keep subscribers in class property
private List<SingleEmmiter> subscribers;
and remove subscribers in setCancellable method, but i don't think it good idea.
Help me please :)
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 {
}
})
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.