I am trying to get the data from local db by each content asyncrounous,but the issue is that I want to get the data by the order that I retrieve,at first the data for the first conent,than the second etc.,currently I am getting the data in wrong order every time I run the code ,how can I achieve this?
for (Content content : contents) {
scoreCardDisposable = AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(scoreCards -> {
...
});
compositeDisposable.add(scoreCardDisposable);
}
To make an observable stream from a List<> you can use Observable.fromIterable operator:
compositeDisposable.add(Observable.fromIterable(contents)
.flatMap(content -> {
return AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId());
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(scoreCards -> {
...
}));
UPD:
You can use a zip operator to combine content with the getScoreCardsAndUpdate result in custom object:
class Result {
private String content;
private String result;
public Result(String content, String result) {
this.content = content;
this.result = result;
}
public String getContent() {
return content;
}
public String getResult() {
return result;
}
}
public Observable<String> getScoreCardsAndUpdate(String content) {
return Observable.just("result = " + content);
}
#Test
public void test() {
List<String> contents = Arrays.asList("1", "2", "3", "4");
Observable.fromIterable(contents)
.flatMap(content -> {
return Observable.zip(Observable.just(content),
getScoreCardsAndUpdate(content), Result::new);
})
.subscribe(scoreCards -> {
System.out.println("content = " + scoreCards.getContent() +
", " + scoreCards.getResult());
});
}
You will have to process all the items in a single thread. You can achieve this by adding a scheduler in rxjava2
.subscribeOn(Schedulers.single())
Your code should be
for (Content content : contents) {
scoreCardDisposable = AppManagers.getContentManager()
.getScoreCardsAndUpdate(content.getId())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe(scoreCards -> {
...
});
compositeDisposable.add(scoreCardDisposable);
}
Related
I am attempting to developed an Android application based on the following talk presented by Jake Wharton
The State of Managing State with RxJava
21 March 2017 – Devoxx (San Jose, CA, USA)
Jake promised a part 2 and/or GITHUB example which I am unable to find (If indeed either exists)
At a high level I can follow/understand the majority of the above talk.
However I have the following questions.
I can see how employing UiEvent, UiModel, Action, and Result keeps concerns separated.
What I am confused about is the following:-
The diagram on slide 194 shows the "flow/stream" of Observables as
Android Device -----> Observable<UiEvent> -----> <application code> -----> Observable<Action> -----> {Backend}
{Backend} -----> Observable<Result> -----> <application code> -----> Observable<UiModel> -----> Android Device
Slide 210 contains this code snippet, showing how the Result(s) stream is "scan"ned into UiModel
SubmitUiModel initialState = SubmitUiModel.idle();
Observable<Result> results = /* ... */;
Observable<SubmitUiModel> uiModels = results.scan(initialState, (state, result) -> {
if (result == CheckNameResult.IN_FLIGHT
|| result == SubmitResult.IN_FLIGHT)
return SubmitUiModel.inProgress();
if (result == CheckNameResult.SUCCESS)
return SubmitUiModel.idle();
if (result == SubmitResult.SUCCESS)
return SubmitUiModel.success();
// TODO handle check name and submit failures...
throw new IllegalArgumentException("Unknown result: " + result);
});
and the final code snippet on slide 215, the code snippet resembles this:-
ObservableTransformer<SubmitAction, SubmitResult> submit =
actions -> actions.flatMap(action -> service.setName(action.name)
.map(response -> SubmitResult.SUCCESS)
.onErrorReturn(t -> SubmitResult.failure(t.getMessage()))
.observeOn(AndroidSchedulers.mainThread())
.startWith(SubmitResult.IN_FLIGHT));
ObservableTransformer<CheckNameAction, CheckNameResult> checkName =
actions -> actions.switchMap(action -> action
.delay(200, MILLISECONDS, AndroidSchedulers.mainThread())
.flatMap(action -> service.checkName(action.name))
.map(response -> CheckNameResult.SUCCESS)
.onErrorReturn(t -> CheckNameResult.failure(t.getMessage()))
.observeOn(AndroidSchedulers.mainThread())
.startWith(CheckNameResult.IN_FLIGHT));
which illustrates conversion from Action(s) to Result(s)
what am I missing from this talk/slide-deck on how to combine the UiEvent/UiModel to the Action/Result stream?
The stream is driven by UiEvents
How do you complete the flow from UiEvent(s) to Action back to Result then finally UiModel?
UPDATE
Using the Star Wars API I have taken the following approach
I use my UI Events to drive the transformation between UI Events to Results via Actions, then scan the results to map back to UI Model.
Heres my classes and code:-
ACTION CLASSES
==============
public abstract class Action<T> {
Api service = Service.instance();
final T data;
public Action(final T data) {
this.data = data;
}
public T getData() {
return data;
}
public abstract Observable<Response<String>> execute();
}
public class CheckCharacterAction extends Action<String> {
public CheckCharacterAction(final String characterName) {
super(characterName);
}
#Override
public Observable<Response<String>> execute() {
return service.peopleSearch(getData());
}
}
public class CheckFilmAction extends Action<String> {
public CheckFilmAction(final String filmTitle) {
super(filmTitle);
}
#Override
public Observable<Response<String>> execute() {
return service.filmSearch(getData());
}
}
public class SearchAction extends Action<String> {
public SearchAction(final String search) {
super(search);
}
#Override
public Observable<Response<String>> execute() {
return service.filmSearch(getData());
}
}
EVENT CLASSES
=============
public abstract class UiEvent<T> {
private final T data;
public UiEvent(final T data) {
this.data = data;
}
public T getData() {
return data;
}
}
public class CharacterUiEvent extends UiEvent<String> {
public CharacterUiEvent(final String name) {
super(name);
}
}
public class FilmUiEvent extends UiEvent<String> {
public FilmUiEvent(final String title) {
super(title);
}
}
public class SearchUiEvent extends UiEvent<String> {
public SearchUiEvent(final String data) {
super(data);
}
}
UI MODEL CLASSES
================
public class UiModel<T> {
public final boolean isProgress;
public final String message;
public final boolean isSuccess;
public T data;
public UiModel(final boolean isProgress) {
this.isProgress = isProgress;
this.message = null;
this.isSuccess = false;
this.data = null;
}
public UiModel(final T data) {
this.isProgress = false;
this.message = null;
this.isSuccess = true;
this.data = data;
}
public UiModel(final String message) {
this.isProgress = false;
this.message = message;
this.isSuccess = false;
this.data = null;
}
public UiModel(final boolean isProgress, final String message, final boolean isSuccess, final T data) {
this.isProgress = isProgress;
this.message = message;
this.isSuccess = isSuccess;
this.data = data;
}
}
public class CharacterUiModel extends UiModel<JsonData> {
public CharacterUiModel(final boolean isProgress) {
super(isProgress);
}
public CharacterUiModel(final JsonData data) {
super(data);
}
public CharacterUiModel(final String message) {
super(message);
}
public CharacterUiModel(final boolean isProgress, final String message, final boolean isSuccess, final JsonData data) {
super(isProgress, message, isSuccess, data);
}
public static CharacterUiModel inProgress() {
return new CharacterUiModel(true);
}
public static CharacterUiModel success(final JsonData data) {
return new CharacterUiModel(data);
}
public static CharacterUiModel failure(final String message) {
return new CharacterUiModel(message);
}
}
public class FilmUiModel extends UiModel<JsonData> {
public FilmUiModel(final boolean isProgress) {
super(isProgress);
}
public FilmUiModel(final JsonData data) {
super(data);
}
public FilmUiModel(final String message) {
super(message);
}
public FilmUiModel(final boolean isProgress, final String message, final boolean isSuccess, final JsonData data) {
super(isProgress, message, isSuccess, data);
}
public static FilmUiModel inProgress() {
return new FilmUiModel(true);
}
public static FilmUiModel success(final JsonData data) {
return new FilmUiModel(data);
}
public static FilmUiModel failure(final String message) {
return new FilmUiModel(message);
}
}
public class SearchUiModel extends UiModel<JsonData> {
private SearchUiModel(final boolean isProgress) {
super(isProgress);
}
private SearchUiModel(final JsonData data) {
super(data);
}
private SearchUiModel(final String message) {
super(message);
}
private SearchUiModel(final boolean isProgress, final String message, final boolean isSuccess, final JsonData data) {
super(isProgress, message, isSuccess, data);
}
public static SearchUiModel idle() {
return new SearchUiModel(false, null, false, null);
}
public static SearchUiModel inProgress() {
return new SearchUiModel(true);
}
public static SearchUiModel success(final JsonData data) {
return new SearchUiModel(data);
}
public static SearchUiModel failure(final String message) {
return new SearchUiModel(message);
}
}
RESULT CLASSES
==============
public abstract class Result<T> {
public enum LIFECYCLE {
DEPARTURE_LOUNGE,
IN_FLIGHT,
LANDED_SAFELY,
CRASHED_BURNED
}
final LIFECYCLE lifecycle;
final T data;
final String errorMessage;
public Result(final LIFECYCLE lifecycle, final T data, final String errorMessage) {
this.lifecycle = lifecycle;
this.data = data;
this.errorMessage = errorMessage;
}
public T getData() {
return data;
}
public String getErrorMessage() {
return errorMessage;
}
public LIFECYCLE getLifecycle() {
return lifecycle;
}
}
public class CharacterResult extends Result<JsonData> {
private CharacterResult(final LIFECYCLE lifecycle, final JsonData data, final String errorMessage) {
super(lifecycle, data, errorMessage);
}
private CharacterResult(final LIFECYCLE lifecycle) {
super(lifecycle, null, null);
}
public static CharacterResult departureLounge() {
return new CharacterResult(LIFECYCLE.DEPARTURE_LOUNGE);
}
public static CharacterResult inflight() {
return new CharacterResult(LIFECYCLE.IN_FLIGHT);
}
public static CharacterResult landedSafely(final JsonData data) {
return new CharacterResult(LIFECYCLE.LANDED_SAFELY, data, null);
}
public static CharacterResult crashedBurned(final String errorMessage) {
return new CharacterResult(LIFECYCLE.CRASHED_BURNED, null, errorMessage);
}
}
public class FilmResult extends Result<JsonData> {
private FilmResult(final LIFECYCLE lifecycle, final JsonData data, final String errorMessage) {
super(lifecycle, data, errorMessage);
}
private FilmResult(final LIFECYCLE lifecycle) {
super(lifecycle, null, null);
}
public static FilmResult departureLounge() {
return new FilmResult(LIFECYCLE.DEPARTURE_LOUNGE);
}
public static FilmResult inflight() {
return new FilmResult(LIFECYCLE.IN_FLIGHT);
}
public static FilmResult landedSafely(final JsonData data) {
return new FilmResult(LIFECYCLE.LANDED_SAFELY, data, null);
}
public static FilmResult crashedBurned(final String errorMessage) {
return new FilmResult(LIFECYCLE.CRASHED_BURNED, null, errorMessage);
}
}
public class SearchResult extends Result<JsonData> {
private SearchResult(final LIFECYCLE lifecycle, final JsonData data, final String errorMessage) {
super(lifecycle, data, errorMessage);
}
private SearchResult(final LIFECYCLE lifecycle) {
super(lifecycle, null, null);
}
public static SearchResult departureLounge() {
return new SearchResult(LIFECYCLE.DEPARTURE_LOUNGE);
}
public static SearchResult inflight() {
return new SearchResult(LIFECYCLE.IN_FLIGHT);
}
public static SearchResult landedSafely(final JsonData data) {
return new SearchResult(LIFECYCLE.LANDED_SAFELY, data, null);
}
public static SearchResult crashedBurned(final String errorMessage) {
return new SearchResult(LIFECYCLE.CRASHED_BURNED, null, errorMessage);
}
}
I then set up my Rx Streams as follows from my Activity onCreate() method:-
final Observable<SearchUiEvent> searchEvents = RxView.clicks(activityMainBinding.searchButton)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.map(ignored -> new SearchUiEvent(activityMainBinding.filmTitle.getText().toString()));
final Observable<FilmUiEvent> filmEvents = RxTextView.afterTextChangeEvents(activityMainBinding.filmTitle)
.skipInitialValue()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.delay(1000, MILLISECONDS, AndroidSchedulers.mainThread())
.map(text -> new FilmUiEvent(text.view().getText().toString()));
final Observable<CharacterUiEvent> characterEvents = RxTextView.afterTextChangeEvents(activityMainBinding.people)
.skipInitialValue()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.delay(200, MILLISECONDS, AndroidSchedulers.mainThread())
.map(text -> new CharacterUiEvent(text.view().getText().toString()));
/**
*
*/
final Observable<UiEvent> uiEvents = Observable.merge(searchEvents, filmEvents, characterEvents);
/*********
*
*/
final ObservableTransformer<SearchUiEvent, SearchResult> searchAction =
events -> events.flatMap(event -> new SearchAction(event.getData()).execute().subscribeOn(Schedulers.io()))
.map(response -> SearchResult.landedSafely(new JsonData(response.body())))
.onErrorReturn(throwable -> SearchResult.crashedBurned(throwable.getMessage()))
.startWith(SearchResult.inflight());
final ObservableTransformer<FilmUiEvent, FilmResult> filmAction =
events -> events.flatMap(event -> new CheckFilmAction(event.getData()).execute().subscribeOn(Schedulers.io()))
.map(response -> FilmResult.landedSafely(new JsonData(response.body())))
.onErrorReturn(throwable -> FilmResult.crashedBurned(throwable.getMessage()))
.startWith(FilmResult.inflight());
final ObservableTransformer<CharacterUiEvent, CharacterResult> characterAction =
events -> events.flatMap(event -> new CheckCharacterAction(event.getData()).execute().subscribeOn(Schedulers.io()))
.map(response -> CharacterResult.landedSafely(new JsonData(response.body())))
.onErrorReturn(throwable -> CharacterResult.crashedBurned(throwable.getMessage()))
.startWith(CharacterResult.inflight());
final ObservableTransformer<UiEvent, ? extends Result> whatever = events -> events.publish(shared -> Observable.merge(
shared.ofType(SearchUiEvent.class).compose(searchAction),
shared.ofType(CharacterUiEvent.class).compose(characterAction),
shared.ofType(FilmUiEvent.class).compose(filmAction)));
/**
*
*/
final UiModel initialState = SearchUiModel.idle();
final Observable<? extends Result> results = uiEvents.compose(whatever).doOnSubscribe(COMPOSITE_DISPOSABLE::add);
final Observable<UiModel> models = results.scan(initialState, (state, result) -> {
Log.e(TAG, "scan() state = " + state + " result = " + result);
if (result.getLifecycle().equals(SearchResult.LIFECYCLE.DEPARTURE_LOUNGE) ||
result.getLifecycle().equals(CharacterResult.LIFECYCLE.DEPARTURE_LOUNGE) ||
result.getLifecycle().equals(FilmResult.LIFECYCLE.DEPARTURE_LOUNGE)) {
return SearchUiModel.idle();
}
if (result.getLifecycle().equals(SearchResult.LIFECYCLE.IN_FLIGHT) ||
result.getLifecycle().equals(CharacterResult.LIFECYCLE.IN_FLIGHT) ||
result.getLifecycle().equals(FilmResult.LIFECYCLE.IN_FLIGHT)) {
return SearchUiModel.inProgress();
}
if (result.getLifecycle().equals(SearchResult.LIFECYCLE.LANDED_SAFELY) ||
result.getLifecycle().equals(CharacterResult.LIFECYCLE.LANDED_SAFELY) ||
result.getLifecycle().equals(FilmResult.LIFECYCLE.LANDED_SAFELY)) {
return SearchUiModel.success((JsonData) result.getData());
}
if (result.getLifecycle().equals(SearchResult.LIFECYCLE.CRASHED_BURNED) ||
result.getLifecycle().equals(CharacterResult.LIFECYCLE.CRASHED_BURNED) ||
result.getLifecycle().equals(FilmResult.LIFECYCLE.CRASHED_BURNED)) {
return SearchUiModel.failure(result.getErrorMessage());
}
return null;
});
models.doOnSubscribe(COMPOSITE_DISPOSABLE::add).subscribe(model -> report(model), throwable -> error(throwable));
As soon as my activity displays I get the following logs:-
2018-10-09 14:22:33.310 D/MainActivity: report() called with: model = [UiModel{isProgress=false, message='null', isSuccess=false, data=null}]
2018-10-09 14:22:33.311 E/MainActivity: scan() state = UiModel{isProgress=false, message='null', isSuccess=false, data=null} result = SearchResult{lifecycle=IN_FLIGHT, data=null, errorMessage='null'}
2018-10-09 14:22:33.311 D/MainActivity: report() called with: model = [UiModel{isProgress=true, message='null', isSuccess=false, data=null}]
2018-10-09 14:22:33.313 E/MainActivity: scan() state = UiModel{isProgress=true, message='null', isSuccess=false, data=null} result = CharacterResult{lifecycle=IN_FLIGHT, data=null, errorMessage='null'}
2018-10-09 14:22:33.313 D/MainActivity: report() called with: model = [UiModel{isProgress=true, message='null', isSuccess=false, data=null}]
2018-10-09 14:22:33.313 E/MainActivity: scan() state = UiModel{isProgress=true, message='null', isSuccess=false, data=null} result = FilmResult{lifecycle=IN_FLIGHT, data=null, errorMessage='null'}
2018-10-09 14:22:33.313 D/MainActivity: report() called with: model = [UiModel{isProgress=true, message='null', isSuccess=false, data=null}]
Im guessing I get these IN FLIGHT results due to my .startWith() statements.
When I either click my Search button or enter any text in my EditText views I see the following logs:-
2018-10-09 14:55:19.463 E/MainActivity: scan() state = UiModel{isProgress=false, message='null', isSuccess=true, data=com.test.model.JsonData#5e0b6f1} result = FilmResult{lifecycle=LANDED_SAFELY, data=com.test.model.JsonData#8ae4d86, errorMessage='null'}
2018-10-09 14:55:19.463 D/MainActivity: report() called with: model = [UiModel{isProgress=false, message='null', isSuccess=true, data=com.test.model.JsonData#8ae4d86}]
Why do I not see "IN FLIGHT" then "LANDED SAFELY"?
I only get "LANDED SAFELY"
Is my approach to transforming between UI Event -> Action -> Result -> UI Model anywhere close to what is described by Mr J Wharton?
Where have I gone wrong?
UPDATE (II)
My mistake was to not include all my downstream Rx within the .flatmap() operation.
CLARIFICATION
Does this pattern of UI Event ---> Action ---> Result ---> UI Model still apply for cases where there is no "Backend" as such? e.g. a Home screen could present the user with a number of options (buttons) to navigate to lower level screens within the application. The UI Event would be "Button Click" the UI Model would return with the associated Activity class to employ with the startActivity() method call.
How can I amalgamate the UI input events of a login screen into a single stream of UI events where I have two EditText fields (User Name and Password) and a Login Button.
I would want the button click UI event to contain the user name and user password entered. If I was using RxBinding to process the EditTexts and the Login button click I cannot see how I can combine these three Observables into my UI event stream and have the EditTexts validated to ensure they have data entered and then pass this user entered data to my back end login API (or maybe Google Sign In for example)
(I was adding a comment but it was too long)
I cannot help with the talks and so on presented by Jake. But regarding your last question:
Does this pattern of UI Event ---> Action ---> Result ---> UI Model
still apply for cases where there is no "Backend" as such?
It does, it is just that the backend is your application state repository.
In this kind of architecture there should only be one place of truth for your application: be it a backend, a local database, a combination of both or whatever solution is appropriate for your usecase.
Having that in mind your Action streams should modify the state either by doing calls to the backend, posting changes to a database or writing elements in the sharedSetting. Similarly, changes in your state should trigger sending Results down your streams.
The specific details would depend on what you use as a source of truth for your application.
DataFlow and State
It uses mainly Paco and Jake Wharton RxState idea plus added some more stuff.
To use UiEvent → Action, Result → UiModel transformers and always act
on a single state with the help of RxJava operators (Forming a single
stream of events, then based on their types handling actions with
different transformers, then combine results again, modifying the
state and then finally render it on the UI.
or not to use transformers and make it a little bit “simpler”.
So here is the “full” view model code without using any transformers:
class SomeViewModel(private val someRepository: SomeRepository): ViewModel() {
val uiEvents: PublishSubject<UiEvent> = PublishSubject.create()
val outputState: MutableLiveData<Result<UiState>> = MutableLiveData()
init {
uiEvents.subscribe {
when (it) {
is FirstEvent -> getSomeResultsFromRepo(it.id)
is SecondEvent -> handleSecondEvent()
}
}
}
fun getSomeResultsFromRepo(id: String) {
someRepository.getResult(id)
.map { UiState(it) }
.map { Result.success(it) }
.startWith(Result.loading())
.onErrorReturn { handleError(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
outputState.postValue(it)
})
}
fun handleSecondEvent() {
/* do something here */
someRepository.getSomeOtherResult()
.map { UiState(it) }
.map { Result.success(it) }
.startWith(Result.loading())
.onErrorReturn { handleError(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
outputState.postValue(it)
})
}
private fun handleError(error: Throwable): Result<UiState> {
return if (error is RetrofitException) {
when (error.kind) {
RetrofitException.Kind.NETWORK -> Result.failure(NetworkError(error))
RetrofitException.Kind.HTTP -> Result.failure(ServerError(error))
RetrofitException.Kind.UNEXPECTED -> Result.failure(UnknownError(error))
else -> Result.failure(UnknownError(error))
}
} else {
Result.failure(UnknownError(error))
}
}
class Factory #Inject constructor(private val someRepo: SomeRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
#Suppress("UNCHECKED_CAST")
return SomeViewModel(someRepo) as T
}
}
}
As you can see here 2 streams: a uiEvents (1st stream) which gets all the input events from the UI. As long as the UI exists it will be catching those events. Based on the event types it calls some repository functions (use cases) that are returning some response and then they are updating the model (2nd stream) with one of the possible outcomes: Success, Error or Loading.
Also transform the errors in API to so called RetrofitErrors, and based on their type it can show different error messages to the User.
There is some duplication as well that can be avoided easily, but what I wanted to show here is that it always start with a Loading result, then either a Success or an Error.
One of the most important thing is that this way "To keep state in the stream, which is a LiveData."
One benefit of this setup (just like using a BehaviourSubject) is that it will always return the last state — on orientation change it is very useful as it just loads the last available state.
Also it is highly testable as each piece can be tested in separation with providing mocked repo or view and it is also very easy to debug as we always have a current state in the stream.
I am new to RxJava. I have a scenario where I want to call first login webservice (getLoginObservable) and on success, want to call another webservice (getFetchDataObservable) to get user information.
I have following code working if login is success. But I am unable to figure out how to code failure case.
private void doLogin() {
emailAddress = editTextUsername.getText().toString();
final String password = editTextPassword.getText().toString();
showProgress(null, getString(R.string.loggingInPleaseWait));
getLoginObservable(editTextUsername.getText().toString(), password)
.map(response -> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
}
return response;
})
.flatMap(response -> {
return getFetchDataObservable();
})
.subscribe(res -> {
dismissProgress();
if (res.result) {
saveInformation(password, res);
} else {
ConstantsMethods.showOkButtonDialog(getContext(), res.message, null);
}
}, e -> {
dismissProgress();
if (e instanceof NoInternetConnectionException) {
ConstantsMethods.showOkButtonDialog(getContext(), getString(R.string.noInternetConnection), null);
}
Log.e(LoginFragment.class.getSimpleName(), e.getMessage());
});
}
private Observable<WsResponse<SecurityQuestion>> getLoginObservable(String userName, String password) {
return Observable.<WsResponse<SecurityQuestion>>create(subscriber -> {
getPresenter().doLogin(getActivity(), userName, password, appType,
new Callback<Void, WsResponse<SecurityQuestion>>() {
#Override
public Void callback(final WsResponse<SecurityQuestion> param) {
subscriber.onNext(param);
return null;
}
});
});
}
private Observable<WsResponse<PatientDataProfile>> getFetchDataObservable() {
return Observable.create(subscriber -> {
new AfPatientsPresenter().fetchPatientData(getContext(), emailAddress, "", new Callback<Void, WsResponse<PatientDataProfile>>() {
#Override
public Void callback(WsResponse<PatientDataProfile> param1) {
subscriber.onNext(param1);
subscriber.onComplete();
return null;
}
});
});
}
As much i know RxJava, I can figure out that getLoginObservable(editTextUsername.getText().toString(), password) observable send response to map (map(response -> { ... }) and this map return response to flatmap (flatMap(response -> { ... }) and its response is sent to subscriber. Here i am just lost that how can i skip (second network call)flatmap flatMap(response -> { ... } to send response directly to subscriber in case of login failure.
instead of:
.map(response -> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
}
return response;
})
you can use:
flatMap(response-> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
return Observable.just(response);
} else {
return Observable.error(new Exception("Login failed")); // or maybe some LoginFailedException() you can reuse
}
})
I have the following methods
Document createDocument(String url);
List<MediaContent> getVideo(Document doc);
List<MediaContent> getImages(Document doc);
List< MediaContent> will be consumed by
void appendToRv(List<MediaContent> media);
I like to use RxJava2 such that
CreateDocument -> getVideo ->
-> appendToRv
-> getImages ->
(also, the video output should be ordered before images).
How would I go about doing that? I tried flatMap, but it seems to only allow a single method to be used
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
// . ?? ..
// this is the part i am lost with
// how do i feed document to -> getVideo() and getImage()
// and then merge them back into the subscriber
//
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription);
The DisposableSingleObserver
parseImageSubscription = new DisposableSingleObserver<List<MediaContent>>() {
#Override
public void onSuccess(List<MediaContent> media) {
if(media!=null) {
appendToRv(media);
}
}
#Override
public void onError(Throwable error) {
doSnackBar("error loading: '" + q + "'");
}
};
the single observables for getVideos and getImages
Single<List<MediaContent>> SingleGetImage(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getImage(document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No images found"));
}
});
}
Single<List<MediaContent>> singleGetVideo(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getVideo( document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No videos found"));
}
});
}
assuming you want to execute in parallel the getVideos and getImages requests, you can use flatMap() with zip(), zip will collect the 2 emissions from both Singles, and you can combine the 2 results to a new value, meaning you can sort the videos MediaContent list , and combine it with the images MediaContent list, and return unified list (or whatever other object you'd like):
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
.flatMap(document -> Single.zip(singleGetVideo(document), SingleGetImage(document),
(videoMediaContents, imageMediaContents) -> //here you'll have the 2 results
//you can sort combine etc. and return unified object
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription)
Observable.zip() could implement it perfect. The Observer will receive a merged result by this method.
public void zip() {
Observable<Integer> observable1 = Observable.just(1);
Observable<Integer> observable2 = Observable.just(2);
Observable.zip(observable1, observable2, new Func2<Integer, Integer, Integer>() {
#Override
public Integer call(Integer integer, Integer integer2) {
return integer + integer2;
}
}).subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer o) {
Logger.i(o.toString());
//Here will print 3.
}
});
}
I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.
Trying to understand all that RxJava stuff. I was doing following example:
private Observable<List<String>> query1() {
List<String> urls = new ArrayList<>();
urls.add("1");
urls.add("2");
urls.add("3");
urls.add("4");
return Observable.just(urls);
}
private Observable<List<String>> query2() {
List<String> urls = new ArrayList<>();
urls.add("A");
urls.add("B");
urls.add("C");
urls.add("D");
return Observable.just(urls);
}
and then tried to join two lists:
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, Observable<String>>() {
#Override
public Observable<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return Observable.from(list);
}
})
.subscribe(new Action1<String>() { // <-- It says, cannot resolve method subscribe
#Override
public void call(String string) {
String text = testTextView.getText().toString();
testTextView.setText(text + "\n" + string);
}
});
What I'm doing wrong? I was expecting to get in my view
1
2
3
4
A
B
C
D
EDIT1 I ended with the following answer:
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, List<String>>() {
#Override
public List<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return list;
}
})
.flatMap(new Func1<List<String>, Observable<String>>() {
#Override
public Observable<String> call(List<String> urls) {
return Observable.from(urls);
}
})
.subscribe(new Action1<String>() {
#Override
public void call(String string) {
String text = testTextView.getText().toString();
testTextView.setText(text + "\n" + string);
}
});
EDIT2 concat solution as suggested by ihuk would be much better in this case. Appreciate for all the answers.
I believe the operators you are looking for are concat or merge.
Concat will emit the emissions from two or more Observables without interleaving them.
Merge on the other hand will combine multiple observables by merging their emissions.
For example:
String[] numbers = {"1", "2", "3", "4"};
String[] letters = {"a", "b", "c", "d"};
Observable<String> query1 = Observable.from(numbers).delay(1, TimeUnit.SECONDS);
Observable<String> query2 = Observable.from(letters);
Observable
.concat(query1, query2)
.subscribe(s -> {
System.out.printf("-%s-" + s);
});
Will print -1--2--3--4--a--b--c--d-. If you replace concat with merge the result will be -a--b--c--d--1--2--3--4-.
Zip operator will combine multiple Observables together via specified function. For example
Observable
.zip(query1, query2, (String n, String l) -> String.format("(%s, %s)", n, l))
.subscribe(s -> {
System.out.printf("-%s-", s);
});
Will output -(1, a)--(2, b)--(3, c)--(4, d)-.
thats because you are trying to return Observable from zip function, but then you pass Action<String>
Observable.zip(
query1(),
query2(),
new Func2<List<String>, List<String>, List<String>>() {
#Override
public List<String> call(List<String> a1, List<String> a2) {
List<String> list = new ArrayList<>();
list.addAll(a1);
list.addAll(a2);
return list;
}
})
.subscribe(
(string)-> System.out.println(string)
);
Observable<List<String>> query1(){
List<String> s = new ArrayList<>();
s.add("1");s.add("1");s.add("1");
return Observable.just(s);
}
Observable<List<String>> query2(){
List<String> s = new ArrayList<>();
s.add("1");s.add("1");s.add("1");
return Observable.just(s);
}
void HelloRx(){
Map<String,String> map2=new LinkedHashMap<>();//pick the result you want to return Here !
Observable.zip(query1(),//Observable Method 1
query2(),//Observable Method 2
(result1,result2)->{
for(String s : result1){//result1 is the value returned by query1 , result2 ...u know.
//do whatever you want
//map.put(......)
}
return null;
})
.subscribeOn(BackgroundSchedulers.getMultiThreadInstance())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(() -> {
//Do Something when finish for example transmit data to your adapter
})
.subscribe();
}
Apparently to join two lists into one list, you can do Observable.concat() on their Observable.from()s and then call Observable.toList().
RealmResults<Cat> equalTo;
RealmResults<Cat> beginsWith;
#Override
public void onViewRestored() {
compositeSubscription = new CompositeSubscription();
equalTo = realm.where(Cat.class).equalTo("field", filterString).findAllSorted("field");
beginsWith = realm.where(Cat.class).beginsWith("field", filterString).findAllSorted("field");
compositeSubscription.add(realm.asObservable()
.switchMap(new Func1<Realm, Observable<Cat>>() {
#Override
public Observable<Cat> call(Realm realm) {
return Observable.concat(Observable.from(equalTo), Observable.from(beginsWith));
}
})
.toList()
.subscribe(cats -> {
// update adapter with List<Cat>
}));