Here I am not asking for code problem, but just architecture, I will show what Iof course.
So I was just playing around with this MVVM architecture, I was displaying a list of items, I click an item I see its details, there are many categories in those details, meaning that for example I had a personal data displayed in one cardview, informations about his credit card for example in an other cardview, etc.. For some Users who are connected, credit cards data must not be displayed, but when I created the view model in the begging it was like this :
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But now I need the user connected role in the app to display the possible data that he can consult. The first thought coming to my head was just calling the service that return my userConnected.
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<UserConnected> userConnected = new MutableLiveData<UserConnected>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
#Inject
UserConnectedService userConnectedService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
DaggerUserConnectedApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void getUserConnected(){
disposable.add(
userConnectedService.getUserConnected()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<UserConnected>() {
#Override
public void onSuccess(UserConnected userConnectedParams) {
userConnected.setValue(userConnectedParams);
}
#Override
public void onError(Throwable e) {
}
})
);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But it is not clean, I have to call two methods at the same time in my Activity, which is terrible, the second thought is that I have to edit my contract type from MutableLiveData<ContractModel> to something like this MutableLiveData<NewModel> knowing that this NewModel architecture is like this :
public NewModel {
ContractModel contractModel;
UserModel userModel;
// ofc here a constuctor and getters and setters
}
But this has a problem too that will be caused in the backend, because what if I need the userConnected in an other activity ? I will have to write similar code in multiple places to get this userConnected, but I want to get it once.
Any help, thoughts would be really helpful.
Related
I am new to MVVM and trying to clear my rxJava disposables, i have seen some answers saying to clear it in ViewModel in onClear method but how do i get to add the disposable in the first place ?
//Repository Code
public class MyRepository {
public MutableLiveData<String> deleteDraftById(int recordId {
final MutableLiveData<String> result = new MutableLiveData<>();
Completable deleteDraftById = completedDao.deleteDraftById(recordId);
deleteDraftById.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
result.setValue("1");
}
#Override
public void onError(Throwable e) {
result.setValue(e.getMessage());
}
});
return result;
}
}
//ViewModel
public class MyViewModel extends AndroidViewModel {
public MutableLiveData<String> deleteDraftById(int recordId){
return myRepository.deleteDraftById(recordId);
}
}
In my opinion nothing wrong with using live data in repos, for example if single source of truth is needed. Here is what I'd suggested (rxjava 1.x assumed, pseudocode a-la java) :
public class MyRepository {
public final MutableLiveData<String> result = new MutableLiveData<>();
public Completable deleteDraftById(int recordId) {
return completedDao.deleteDraftById(recordId)
.doOnSubscribe(...) //potentially report progress start, if needed
.doOnSuccess(...) //report success to your live data aka result.value = ...
.onErrorComplete(...) //report error to your live data and complete
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
public class MyViewModel(....pass MyRepository) extends AndroidViewModel {
//expose live data from repo somehow, may be like this:
public final LiveData<String> abc = myRepository.result;
private final CompositeSubscription compositeSubscription = new CompositeSubscription();
//call this from ui
public void delete(int recordId) {
compositeSubscription.add(
myRepository
.deleteDraftById(recordId)
.subscribe()
)
}
#Override
protected void onCleared() {
super.onCleared();
compositeSubscription.clear();
}
}
I am new to ReactiveX and I have a case where I want my observable to emit data to a late subscriber(whenever the observer subscribes, observable should emit the same data that it emitted previously). I made this Observable class that provide ReplaySubject's same instance to all observers (it is singleton class).
public class AccountsObservable {
private static ConnectableObservable<String> hotObservable;
private static AccountsObservable accountsObservable;
public static AccountsObservable getObject() {
if (accountsObservable == null) {
accountsObservable = new AccountsObservable();
}
return accountsObservable;
}
public ConnectableObservable<String> getObservable() {
if (hotObservable == null) {
Observable<String> observable = ReplaySubject.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("XYZ");
emitter.onComplete();
}
});
hotObservable = observable.replay();//publish
}
return hotObservable;
}
}
Similarly, this is the observer class that creates new observer instance.
public class AccountsObserver {
AccountsFetchListener listener;
public AccountsObserver(AccountsFetchListener listener) {
this.listener = listener;
}
public Observer<String> getObserver() {
return new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String accounts) {
listener.onSuccess(accounts);
}
#Override
public void onError(Throwable e) {
listener.onFailure();
}
#Override
public void onComplete() {
}
};
}
public interface AccountsFetchListener {
void onSuccess(String accounts);
void onFailure();
}
}
Here is the function where I test these observables
private void testObs() {
ConnectableObservable<String> observable = AccountsObservable.getObject().getObservable();
Observer<String> observer = new AccountsObserver(new AccountsObserver.AccountsFetchListener() {
#Override
public void onSuccess(String accounts) {
Log.e("DATA -> ", accounts);
}
#Override
public void onFailure() {
}
}).getObserver();
observable.subscribe(observer);
observable.connect();
}
I called this function "testObs()" 5 times but it emitted data only 2 times. The problem seems to be in AccountsObservable class where I provide ReplaySUbject's instance. Thanks
Your code runs fine as it is, your logs are being suppressed in logcat as per this:
We declared an application as too chatty once it logs more than 5 lines a second. Please file a bug against the application's owner that is producing this developer-verbose-debug-level class logging spam. The logs are 256KB, that means the application is creating a DOS attack and shortening the logs timepan to 6 seconds(!) making it useless for all others.
You can avoid this behaviour by whitelisting your app for logcat:
adb logcat -P '<pid or uid of your app>'
I am a beginner for rxjava,room and mvvm architecture.I am trying to fetch a user from room database, using Rxjava.
when I get user successfully I want to show a toast and start another activity. and in case of failure, I will show an error message in text input layout.
I have try to do that using following code.
in my Activity, I have a method authorizeUser() which is called on button click.
private void authorizeUser() {
loginViewModel.checkInDb()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Log.e("Action", "Complete");
showToast();
startAnotherActivity();
}
#Override
public void onError(Throwable e) {
Log.e("Action", "error");
showError();
}
});
}
In my ViewModel
public Completable checkInDb() {
Completable completable= Completable.fromAction(() ->
userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
return completable;
}
in UserDataSource class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
Using above code, onComplete method is always called in activity, weather in view model, user fetched successfully or not
But what I want to do is, I want to throw an error or send a notification to the activity when there is error thrown (or on Error method is called in viewmodel). so that I can display error on my activity.
May be my question can be silly, but I am new to this. Please help me.
Although it would be better to maybe wrap the response in LiveData so you would get the subscription/lifecycle "for free", unless there is a specific need to have a completable on the UI, which I don't really any reason for it).
I would instead change your Dao to return Observable instead of Single, and then (one again this is not the optimal solution, should wrap into LiveData) you can return that Observable to the UI:
public Observable checkInDb() {
return userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
}
And change your Daos
in UserDataSource class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
So now your view can checkInDb() and handle these cases.
PS: I'm assuming this daoAccess is not an API call rather a local DB (probably Room).
I'm new to RxJava. My requirement is to do 3 retrofit call at start and wait until all of theme executed. Here is what i had implemented and Its working perfectly but I want know, is this code could be better than this and I implemented schedulers correctly or not.
public class CombinedGroupProductPage {
private List<Product> groupProductList;
private List<Product> relatedProductList;
private List<Product> upsellProductList;
//constructor and getter setters here
.....
.....
}
Here is my implementation
private void getAllData() {
loadingProgress.setVisibility(View.VISIBLE);
ApiInterface apiService = ApiClient.getRxClient().create(ApiInterface.class);
Observable<List<Product>> call = apiService.getRelatedProduct1(gson.toJson(model.getGroupedProducts()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<List<Product>> call1 = apiService.getRelatedProduct1(gson.toJson(model.getRelatedIds()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<List<Product>> call2 = apiService.getRelatedProduct1(gson.toJson(model.getUpsellIds()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<CombinedGroupProductPage> combined = Observable.zip(call, call1, call2, new Function3<List<Product>, List<Product>, List<Product>, CombinedGroupProductPage>() {
#Override
public CombinedGroupProductPage apply(List<Product> list, List<Product> list2, List<Product> list3) throws Exception {
return new CombinedGroupProductPage(list, list2, list3);
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());
combined.subscribe(new Observer<CombinedGroupProductPage>() {
#Override
public void onSubscribe(Disposable d) {
// loadingProgress.setVisibility(View.VISIBLE);
}
#Override
public void onNext(CombinedGroupProductPage combinedGroupProductPage) {
Log.e("Tag", combinedGroupProductPage.toString());
Log.e("Tag", combinedGroupProductPage.getGroupProductList().get(0).getName());
loadingProgress.setVisibility(View.GONE);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
}
Please tell me this code can be reduced or not? Any help would be appreciated thanks.
Use Schedulers.io() instead of Schedulers.newThread(). Schedulers.io() uses a thread pool whereas Schedulers.newThread() doesn't. Creating threads is expensive and should be avoided if possible.
Using .subscribeOn(Schedulers.io()) for your different calls also allow you to remove the now useless .observeOn(Schedulers.io()).
I have started learning RxAndroid and below is the code I wrote to iterate over a model object (Results) that contains data fetched from the server. I'm iterating over the model object in the observable and providing a newly created object in the observer. I'm trying to take subscription of the observer to unsubscribe the task upon Orientation changes of the fragment. However the subscribe() returns VOID instead of subscription object.
Questions:
Does the latest version of RxAndroid handle unsubscription itself upon configuration/orientation change?
In case configuration change happens before the task is complete, the only way to restart this task that I can think of is, I persist the server response in onSavedInstance() and retrieve it from bundle when the fragment is recreated. It'll require booleans to figure out if the configuration change happened before the configuration change or not. Is there a graceful and cleaner way of coping with this?
private void createComicList(final List<Result> marvelResults) {
final MarvelComics marvelComics = new MarvelComics();
Observable marvelObservable2 = Observable.create(new ObservableOnSubscribe<MarvelComic>() {
#Override
public void subscribe(ObservableEmitter<MarvelComic> e) throws Exception {
for(Result result : marvelResults) {
MarvelComic marvelComic = new MarvelComic();
marvelComic.setDescription(result.getDescription());
marvelComic.setTitle(result.getTitle());
marvelComic.setPageCount(result.getPageCount());
marvelComic.setThumbnailUrl(result.getThumbnail().getPath());
marvelComic.setId(result.getId());
e.onNext(marvelComic);
}
e.onComplete();
}
});
marvelObservable2.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MarvelComic>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(MarvelComic comic) {
marvelComics.getMarvelComicList().add(comic);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
showToast();
}
});
}
The Observable.subscribe(Observer<? super T>) method returns void in the 2.x since the Observer.onSubscribe(Disposable) is there to get the cancellation support that used to be Subscription in 1.x.
final CompositeDisposable composite = new CompositeDisposable();
Observable<Integer> source = Observable.just(1)
source.subscribe(new Observer<Integer>() {
#Override public void onSubscribe(Disposable d) {
composite.add(d); // <---------------------------------------------
}
#Override public void onNext(Integer t) {
System.out.println(t);
}
#Override public void onError(Throwable e) {
e.printStackTrace();
}
#Override public void onComplete() {
System.out.println("Done");
}
});
composite.add(source
.subscribeWith( // <-----------------------------------------------
new DisposableObserver<Integer>() {
#Override public void onNext(Integer t) {
System.out.println(t);
}
#Override public void onError(Throwable e) {
e.printStackTrace();
}
#Override public void onComplete() {
System.out.println("Done");
}
});
subscribe() method of Observable returns Subscription object in earlier versions of RxJava and current version returns an object of Disposble class which you can unsubscribe by invoking dispose() method.
For your second question you may check this answer Best practice: AsyncTask during orientation change