I tried googling this but results are all about what's changed between rxjava 1 and 2... less than helpful, lol.
I'm new to Rx Java and am working through it. I'm able to create an observable pretty easily and all subscribers are getting the right calls. I just have a question:
Object myObject = something1;
I create the observable and all subscribers and they get myObject in the onNext() method.
But, somewhere down the line, we do "myObject = something2".
How do I notify the subscribers that myObject has changed?
Here's how I'm creating the observable:
Observable<MyObject> myObservable = Observable.create(new ObservableOnSubscribe<MyObject>() {
#Override
public void subscribe(ObservableEmitter<MyObject> e) throws Exception {
MyObject myObject = someObject1;
e.onNext(myObject);
}
});
Here's how I'm subscribing:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(mDisposableObserver);
}
private DisposableObserver<MyObject> mDisposableObserver = new DisposableObserver<MyObject>() {
#Override
public void onNext(MyObject myObject) {
// Do UI stuff with 'myObject' variables
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
Side Note: If anybody has links or suggestions to really good Rx Java 2 tutorials (like... super beginner through god-status), I'd greatly appreciate it
You can't get notified straight after your object has been changed, if you dont observe for the "setters" of the object which notify somehow if the object has been changed. Using Observables from android architecture library fixes this issue.
Anyway, you can poll your Object for changes and notify the subscribers if the object has been changed. Using repeatWhen and distintUntilChanged makes sure that you dont get your data emitted if those are not changed. Example (untested) may be:
Observable<MyObject> myObservable = Observable.just(yourObject)
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1000, TimeUnit.MILLISECONDS)))
.distinctUntilChanged();
myObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(d -> Log.d("Data", "Got Object "+ d), e -> Log.e("Error", "Received Error:" + e);
Another better attempt is (like Observables from architecture components) that you modify your Object/Model to observe for changes.
That means that you create a Publish Subject inside your model which gets notified if the setter has been called emitting itself as value. Subscribing to this subject means that all subscribers who subscribe to this subject gets notified as soon as the setter has been called with the object which has been changed.
public static class ModelClass {
private PublishSubject<ModelClass> changeObservable = PublishSubject.create();
private String field1;
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
changeObservable.onNext(this);
}
public Observable<ModelClass> getModelChanges() {
return changeObservable;
}
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ModelClass myModel = new ModelClass ();
myModel.getModelChanges().subscribe(c -> Log.d("Change:" + c));
myModel.setField1("1");
myModel.setField1("2");
myModel.setField1("3");
}
Related
I'm using LiveData with MVVM. After updating my database with Room, I am trying to sendback both the Object I inserted into my Room database, and also the adapter position. In my ViewModel class, the method is:
private MutableLiveData<String> insertItemLiveData = new MutableLiveData<>;
public void insertMenuItem(MenuItem menuItem, int adapterPositionToUpdate){
repo.insertOrder(menuItem.getId())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull Integer integer) {
//The order is successfully inserted into database
//So I return back the name of the inserted order
String s = "Inserted Item: " + menuItem.getNameOfOrder();
insertItemLiveData.setValue(s);
}
#Override
public void onError(#NonNull Throwable e) {
errorLiveData.setValue("Failed to cancel order.");
}
});
}
In the on success method, it returns the String I want to display, but I also want to update the position of the Recyclerview item that has changed. What is the best way to handle this situation?
I can use a wrapper class and have setters for a String and the adapter position, but I feel like there's probably a better way to do this.
A resource wrapper is a good idea for it.MVVM Resource Wrapper With Live Data you can check my code to get an insight on how to use it
In my app I am trying to use MediatorLiveData to listen to the changes to a livedata. Since DB operations are involved I use an executor service like this.
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content content) {
Log.i("LIVE", "FIRED");
}
});
});
First I try to insert a new content object into the db. I get the id of the inserted object which I log in the next line. I get some id which is good. After this, I use the id to query for the same object. The query returns a LiveData. (If I use content.getValue() at this time, I get null.)
Then I listen to changes in this liveData using a MediatorLiveData. Unfortunately, the onChange method of the mediatorLiveData is never fired. Thus the Log is not printed too.
This is my content dao class
#Dao
public interface ContentDao {
#Insert
long insert(Content content);
#Query("SELECT * FROM my_table WHERE id = :id")
LiveData<Content> getContentById(long id);
}
I can't understand what I am doing wrong. Can someone please help. Thanks!!
Edit: To clarify, this is how the code looks.
return new NetworkBoundResource<Content, CreateContent>(appExecutors) {
#Override
protected void saveCallResult(#NonNull CreateContent item) {
//Something
}
#Override
protected boolean shouldCall(#Nullable Content data) {
//Something;
}
#Override
protected LiveData<Content> createDbCall() {
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content c) {
Log.i("LIVE", "FIRED");
mediatorLiveData.removeSource(content);
mediatorLiveData.postValue(c);
}
});
});
return mediatorLiveData;
}
#NonNull
#Override
protected LiveData<ApiResponse<CreateContent>> createCall() {
//Something
}
}.asLiveData();
The value is returned to the constructor.
#MainThread
public NetworkBoundResource(AppExecutors appExecutors) {
this.appExecutors = appExecutors;
result.setValue(Resource.loading(null));
//TODO:: Add method to check if data should be saved. This should apply for search data.
LiveData<ResultType> dbSource = createDbCall();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldCall(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
}
});
}
As discussed you need to make sure the mediatorLiveData has an active observer attached.
If you take a look at the addSource method it checks whether any active observers are attached before subscribing to the source.
https://github.com/aosp-mirror/platform_frameworks_support/blob/d79202da157cdd94c2d0c0b6ee57170a97d12c93/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java#L95
In case anyone is re initializing a mediator live data, the old object only will be observed, new object will not be observed.
That is , dont do this:
Observe
myViewModel.observe(....)
Trying to allocate new memory to mediator
myMediatorObj = new MediatorLiveData<>(); //this can be the issue. Try removing if you have any lines like this.
//after this point,anything set to the object myMediatorObj will not be observed
In case you are trying to reset the data, pass in some data that signals null/empty/rest.
I have a ViewPager with two pages namely Popular and All. What I'm trying to achieve is only push items that have popular tag true to Popular whereas push all items to All.
Currently I have a single class which is used in the PagerAdapter and passing in the page type. How do I filter out PublishSubject so that each page only displays necessary items accordingly.
Both my Observer are subscribed to a single PublishSubject, but I
want to filter when emitting.
Please comment if the question is unclear. I'll try my best to relay this problem. Also sorry if it has already been answered since I couldn't find anything relevant.
The code I'm using is this based on this architecture in which I have a Firebase data store FirebaseSubscriptionDataStore which provides the PublishSubject. This is later subscribed to by SubscribeToSubscriptionUpdates in SubscriptionListPresenterImpl
Thanks in advance.
You can basically define two different methods to get Observable (or Flowable) from PublishSubject. First observable will emit all of the items and second one only popular ones:
public class DataStore {
private PublishSubject<DataItem> dataItemPublishSubject = PublishSubject.create();
public Flowable<DataItem> getAllObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER);
}
public Flowable<DataItem> getPopularObservable() {
return dataItemPublishSubject.toFlowable(BackpressureStrategy.BUFFER)
.filter(new Predicate<DataItem>() {
#Override
public boolean test(DataItem dataItem) throws Exception {
return dataItem.popular;
}
});
}
public static class DataItem {
public final boolean popular;
public DataItem(boolean popular) {
this.popular = popular;
}
}
}
In case you don't want to two methods, you can move .filter() operator everywhere within you Rx chain and you might end up with something like this:
dataStore.getAllObservable()
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataAll(dataItem);
}
})
.filter(new Predicate<DataStore.DataItem>() {
#Override
public boolean test(DataStore.DataItem dataItem) throws Exception {
return dataItem.popular;
}
})
.doOnNext(new Consumer<DataStore.DataItem>() {
#Override
public void accept(DataStore.DataItem dataItem) throws Exception {
pagerAdapter.addDataPopular(dataItem);
}
})
.subscribe();
I created an Observable(RxJava2 + Volley) that repeat for each 5 seconds,
It works but when I Dump Java Heap(memory),there are many Instance of my Model JAVA class,and it will increase for each time that the Observable get repeating.
Why RX create several instance of my model? How can I use only ONE instance of it?
Model
public RequestFuture<String> getLiveRefreshFuture() {
RequestFuture<String> future = RequestFuture.newFuture();
VolleyStringRequest request = new VolleyStringRequest(Request.Method.POST
, REFRESH_URL
, future
, future) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return getRefreshParams();
}
};
VolleySingleton.getInstance().addToRequestQueue(request);
return future;
}
Activity
private final CompositeDisposable disposables = new CompositeDisposable();
final LiveRemoteModel model = DaggerLiveComponent.builder().build().getModel();
Observable<LiveResponse> observable = Observable
.interval(Constants.TOOLBAR_BADGES_REFRESH_DELAY, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.map(dummy -> model.getLiveRefreshFuture())
.map(RequestFuture::get)
.map(LiveResponse::new)
.observeOn(AndroidSchedulers.mainThread());
DisposableObserver<LiveResponse> disposableObserver =
new DisposableObserver<LiveResponse>() {
#Override
public void onNext(#NonNull LiveResponse liveResponse) {
setToolbarBadges(liveResponse.getToolbarBadges());
}
public void onError(#NonNull Throwable e) {
Log.e("RX", "onError: ", e);
}
#Override
public void onComplete() {
Log.d("RX", "onComplete: ");
}
};
disposables.add(observable.subscribeWith(disposableObserver));
Why RX create several instance of my model? How can I use only ONE instance of it?
If you look carefully the object in the heapdump is LiveRemoteModel$2 which indicates it is an anonymous class within LiveRemoteModel.
Looking at your code this is probably the VolleyStringRequest object that gets created each time model.getLiveRefreshFuture() is called. There is nothing retaining that object within the RX pipeline so there must be something within Volley retaining it.
I have a list of Observables like so:
List<Observable<MyObj>> listObservables = new ArrayList<Observable<MyObj>>();
I'd like to combine all Observable in a single one, I can handle it if I know the number of Observable using zip(), for example we have 3 Observable:
Observable<MyObj1> obs1= MyRestClient.getSomeData1();
Observable<MyObj2> obs2= MyRestClient.getSomeData2();
Observable<MyObj3> obs3= MyRestClient.getSomeData3();
I have a wrapper obj:
class MyWrapperObj {
private MyObj1 onj1;
private MyObj2 onj2;
private MyObj3 onj3;
public MyWrapperObj(MyObj1 onj1, MyObj2 onj2, MyObj3 onj3) {
this.onj1 = onj1;
this.onj2 = onj2;
this.onj3 = onj3;
}
}
So I can combine them like so:
Observable<MyWrapperObj> combinedObservable = Observable.zip(obs1, obs2, obs3, new Func3<MyObj1, MyObj2, MyObj3, MyWrapperObj>() {
#Override
public MyWrapperObj call(MyObj1 obj1, MyObj2 obj2, MyObj3 obj3) {
return new MyWrapperObj(obj1, obj2, obj3);
}
});
combinedObservable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<MyWrapperObj>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onNext(MyWrapperObj wrapperObj) {
}
});
Everything is working fine, so my problem is how to organize this combination to be for n Observable?
RESPONSE
as #maciekjanusz mentioned in it's answer I did:
Observable<MyWrapperObj> combinedObservable = Observable.zip(listObservables, new FuncN<MyWrapperObj>() {
#Override
public MyWrapperObjcall(Object... args) {
return null;
}
});
If you want to zip n Observables, put them in a list and apply the public static <R> Observable<R> zip(#NotNull java.lang.Iterable<? extends Observable<?>> ws, rx.functions.FuncN<? extends R> zipFunction) factory method.
List<Observable<String>> observables = Arrays.asList(Observable.just("String 1"), Observable.just("String 2"));
Observable.zip(observables, args -> {
// put your zipping code here
});
For example, if you want to create a list of strings for each emission from all observables:
Observable.zip(observables, Arrays::asList);
Or, if using RxJava on android without retrolambda:
Observable.zip(observables, args -> Arrays.asList(args));
Suppose you have the list:
List<Observable<MyObj>> listObservables
You might consider using Observable.concatDelayError
The advantage if it is finishing all Obbservable's even if any of them finishes with error (resulting in an error in such case).
Remember, that every Observable in this sequence must return the result to onNext method of Subscriber. The result also must have the same type.
Example:
Observable.concatDelayError(listObservables);
You can wait until all observables is complete by using
.zip(observable1, ..., observableN, funcN).first() operators. There is an overload, accepting Observable> argument (as in FlatMap).
First overload takes Iterable> - you can pass list of observables of arbitrary size and second argument - FuncN - receives list of values.