RxJava2 Flowable that emits results of multiple network calls without using create? - kotlin

I have a generic screen that subscribes to an RxJava2 flowable that returns a List. It then displays the content in the list.
I have a use case now though where I need to collect data from multiple endpoints, and emit data once some complete, and then emit data again once the remaining ones complete.
I'm doing this using Flowable.create() but I've seen a lot of posts saying that there's usually a better and safer way to do so than using create? I seem to believe that is the case since I need to subscribe to an observable within the observable which ideally I wouldn't want to do?
Because I subscribe within, I know the emitter can become cancelled within the observable while other network calls are completing so I've added checks to ensure it doesn't throw an error after its disposed which do work (at least in testing...) [I also just remembered I have the code available to dispose of the inner subscription if I kept it like this, when the outer is disposed]
The first 2 calls may be incredibly fast (or instant) which is why i want to emit the first result right away, and then the following 4 network calls which rely on that data may take time to process.
It looks roughly like this right now...
return Flowable.create<List<Object>>({ activeEmitter ->
Single.zip(
single1(),
single2(),
BiFunction { single1Result: Object, single2result: Object ->
if (single1result.something || single2Result.somethingElse) {
activeEmitter.onNext(function(single1result, single2result) //returns list
}
Single.zip(
single3(single1result),
single4(single2result),
single5(single1result),
single6(single2result),
Function4 { single3Result: Object,
single4Result: Object,
single5Result: Object,
single6Result: Object ->
ObjectHolder(single1Result, single2Result, single3Result, single4Result, single5Result, single6Result)
}
)
}
).flatMap { objectHolder ->
objects.flatMap { objectHolder ->
Single.just(parseObjects(objectHolder))
}
}.subscribeBy(
onError = { error ->
if (!activeEmitter.isCancelled) {
activeEmitter.onError(error)
}
},
onSuccess = { results ->
if (!activeEmitter.isCancelled) {
activeEmitter.onNext(results)
activeEmitter.onComplete()
}
}
)
}, BackpressureStrategy.BUFFER)
I can't figure out another way to return a Flowable that emits the results of multiple different network calls without doing it like this?
Is there a different/better way I can't find?
I worked this out given ctranxuan response. Posting so he can tweak/optimize and then I accept his answer
return Single.zip(single1(), single2(),
BiFunction { single1result: Object, single2result: Object ->
Pair(single1result, single2result)
}
).toFlowable()
.flatMap { single1AndSingle2 ->
if (isFirstLoad) {
createItemOrNull(single1AndSingle2.first, single1AndSingle2.second)?.let { result ->
Single.just(listOf(result)).mergeWith(proceedWithFinalNetworkCalls(single1AndSingle2))
}.orElse {
proceedWithFinalNetworkCalls(single1AndSingle2).toFlowable()
}
} else {
proceedWithFinalNetworkCalls(single1AndSingle2).toFlowable()
}
}.doOnComplete {
isFirstLoad = false
}
fun proceedWithFinalNetworkCalls(): Flowable<List> {
return Single.zip(
single3(single1result),
single4(single2result),
single5(single1result),
single6(single2result),
Function4 { single3Result: Object,
single4Result: Object,
single5Result: Object,
single6Result: Object ->
ObjectHolder(single1Result, single2Result, single3Result, single4Result, single5Result, single6Result)
}
)

Sorry, it's in Java but from what I've understood, something like that may be a possible solution?
public static void main(String[] args) {
final Single<String> single1 = single1().cache();
single1.map(List::of)
.mergeWith(single1.zipWith(single2(), Map::entry)
.flatMap(entry -> Single.zip(
single3(entry.getKey()),
single4(entry.getValue()),
single5(entry.getKey()),
single6(entry.getValue()),
(el3, el4, el5, el6) -> objectHolder(entry.getKey(), entry.getValue(), el3, el4, el5, el6))))
.subscribe(System.out::println,
System.err::println);
Flowable.timer(1, MINUTES) // Just to block the main thread for a while
.blockingSubscribe();
}
private static List<String> objectHolder(final String el1,
final String el2,
final String el3,
final String el4,
final String el5,
final String el6) {
return List.of(el1, el2, el3, el4, el5, el6);
}
static Single<String> single1() {
return Single.just("s1");
}
static Single<String> single2() {
return Single.just("s2");
}
static Single<String> single3(String value) {
return single("s3", value);
}
static Single<String> single4(String value) {
return single("s4", value);
}
static Single<String> single5(String value) {
return single("s5", value);
}
static Single<String> single6(String value) {
return single("s6", value);
}
static Single<String> single(String value1, String value2) {
return Single.just(value1).map(l -> l + "_" + value2);
}
This outputs:
[s1]
[s1, s2, s3_s1, s4_s2, s5_s1, s6_s2]

Related

Kotlin Coroutines, decompiled code of a suspend function

I'm reading Kotlin Coroutines by Tutorials, Chapter 4 - Suspending functions here by Ray Wenderlich
However, I'm really struggling to understand the author's explanation of the decompiled code. Would someone be able to explain/describe the path of execution in the decompiled code as it pertains to suspend functions/continuations?
suspend fun getUserSuspend(userId: String): User {
delay(1000)
return User(userId, "Filip")
}
#Nullable
public static final Object getUserSuspend(
#NotNull String userId,
#NotNull Continuation var1) {
Object $continuation;
label28: {
if (var1 instanceof < undefinedtype >) {
$continuation = (<undefinedtype>)var1;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label28;
}
}
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
Object L $0;
#Nullable
public final Object invokeSuspend (#NotNull Object result) {
this.result = result;
this.label | = Integer.MIN_VALUE;
return MainKt.getUserSuspend((String)null, this);
}
};
}
Object var2 =((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt . getCOROUTINE_SUSPENDED ();
switch(((<undefinedtype>)$continuation).label) {
case 0:
if (var2 instanceof Failure) {
throw ((Failure) var2).exception;
}
((<undefinedtype>)$continuation).L$0 = userId;
((<undefinedtype>)$continuation).label = 1;
if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
userId = (String)((<undefinedtype>)$continuation).L$0;
if (var2 instanceof Failure) {
throw ((Failure) var2).exception;
}
break;
default:
throw new IllegalStateException ("call to ’resume’ before ’invoke’ with coroutine");
}
return new User (userId, "Filip");
}
I found this video particularly useful. I've linked the relevant section in the video here: https://youtu.be/YrrUCSi72E8?t=393
at roughly 6:33, where he talks about continuations and labels.
Essentially, when a coroutine is suspended, the continuation is used to save the coroutine state. Once the method is no longer suspended (e.g a result was returned, or the task is done etc.), the continuation's "resume" method recalls the method with the continuation.

Java reactor and variable scope

I am trying to get my head around variable propagation using reactor. I have a function as follow where I am trying to pass a variable named requestName from outside the map as follow:
public Mono<ResponseEntity<? extends Object>> myFunction(
final Object request, final String requestName) {
return this.client
.create(request)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("{}", requestName);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest().body(null);
}
});
})
.onErrorResume(ex -> Mono.just(buildErrorFromException(requestName, ex)));
}
or another example would be:
String myvar = "test"
return this.
.login()
.flatMap(
response ->
this.myservice(myvar))
.flatMap(
response2 ->
this.myservice2(myvar))
Is it appropriate ? Or would i need to wrap this function around a Mono.deferContextual and apply a contextView ?
Thanks a lot for your help.

why dose debug break point not hitting on my lamda function (java8 use idea)?

Version:IDEA 2019.3
JDK8
Tried methods :
I'm trying to wrap it in curly braces
Globally, only one breakpoint is hit,None of them took effect
The program goes directly to the saving method,
public <V0 extends Rmap> Output<V0> prior(Trans<V0, Rmap> conv) {
return saving(ds0 -> {
enqueue((DSdream) ds0.map((Function<Rmap, Rmap>) row -> {
Rmap r = conv.apply((V0) row);
return Rec.of(r, tds.get(r.table().toString()));
}, f));
});
}
How do I stop at the lambda break point?
Thanks
I tried to use an anonymous inner class that would normally stop at the breakpoint,
#Override
public <V0 extends Rmap> Output<V0> prior(Trans<V0, Rmap> conv) {
Output<Rmap> saving = saving(new Consumer<DSdream>() {
#Override
public void accept(DSdream dSdream) {
enqueue((DSdream)
dSdream.map((Function<Rmap, Rmap>) row -> {
Rmap r = conv.apply((V0) row);
return Rec.of(r, tds.get(r.table().toString()));
}, f));
}
});
return (Output<V0>) saving;
}
Presumably, after decompiling, lambda is compiled into inner class,
So the break point cannot be located .
It should be the disadvantage of lambda

Exposing BLOC streams via fields, methods, or getter

I am using the BLOC pattern for my latest Flutter app and I started out using something like this for my output streams:
class MyBloc {
// Outputs
final Stream<List<Todo>> todos;
factory MyBloc(TodosInteractor interactor) {
final todosController = BehaviorSubject<List<Todo>>()
..addStream(interactor.todos);
return MyBloc._(todosController);
}
MyBloc._(this.todos);
}
but slowly I found myself doing something more like this, using a method (or getter) after awhile:
class MyBloc {
final TodosInteractor _interactor;
// Outputs
Stream<List<Todo>> todos(){
return _interactor.todos;
}
MyBloc(this._interactor) { }
}
For people who want to see... getter for todos in TodosInteractor:
Stream<List<Todo>> get todos {
return repository
.todos()
.map((entities) => entities.map(Todo.fromEntity).toList());
}
When I look at the differing code, I see that the first example uses a field versus a method to expose the stream but I couldn't figure out why I would choose one over the other. It seems to me that creating another controller just to push through the stream is a little much... Is there a benefit to this other than being immutable in my todos stream definition? Or am I just splitting hairs?
Well maybe this will not be a best answer but it is a good practice expose your output stream using get methods. Below a example of a bloc class that i have written to a project using RxDart.
class CityListWidgetBloc {
final _cityInput = PublishSubject<List<Cidade>>();
final _searchInput = new PublishSubject<String>();
final _selectedItemsInput = new PublishSubject<List<Cidade>>();
// exposing stream using get methods
Observable<List<Cidade>> get allCities => _cityInput.stream;
Observable<List<Cidade>> get selectedItems => _selectedItemsInput.stream;
List<Cidade> _searchList = new List();
List<Cidade> _selectedItems = new List();
List<Cidade> _mainDataList;
CityListWidgetBloc() {
//init search stream
_searchInput.stream.listen((searchPattern) {
if (searchPattern.isEmpty) {
_onData(_mainDataList); // resend local data list
} else {
_searchList.clear();
_mainDataList.forEach((city) {
if (city.nome.toLowerCase().contains(searchPattern.toLowerCase())) {
_searchList.add(city);
}
});
_cityInput.sink.add(_searchList);
}
});
}
//getting data from firebase
getCity( {#required String key}) {
FirebaseStateCityHelper.getCitiesFrom(key, _onData);
//_lastKey = key;
}
searchFor(String pattern) {
_searchInput.sink.add(pattern);
}
void _onData(List<Cidade> list) {
_mainDataList = list;
list.sort((a, b) => (a.nome.compareTo(b.nome)));
_cityInput.sink.add(list);
}
bool isSelected(Cidade item) {
return _selectedItems.contains(item);
}
void selectItem(Cidade item) {
_selectedItems.add(item);
_selectedItemsInput.sink.add(_selectedItems);
}
void selectItems(List<Cidade> items){
_selectedItems.addAll( items);
_selectedItemsInput.sink.add( _selectedItems );
}
void removeItem(Cidade item) {
_selectedItems.remove(item);
_selectedItemsInput.sink.add(_selectedItems);
}
dispose() {
_cityInput.close();
_searchInput.close();
_selectedItemsInput.close();
}
}

Do an action when an error occurs RxJava

I need to create a folder when it doesn't exist. In my case, the only way to do so is to capture the error and handle it to create the folder wanted.
But all i can find is
public static Observable<Boolean> folderExists(final Context context, final String targetPath, final String currentpath) {
Application application = Application.get(context);
//i browse the folder to get all the items
return browseFolderObservable(context,currentpath)
.subscribeOn(application.defaultSubscribeScheduler())
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
BsSdkLog.d("Error no file found");
}
})
.map(new Func1<ArrayList<Item>, Boolean>() {
#Override
public Boolean call(ArrayList<Item> items) {
if(items.isEmpty()) {
BsSdkLog.d(" No items");
return false;
}else {
for(int i=0;i<items.size();i++)
{
Item item=items.get(i);
BsSdkLog.d(item.toString());
}
BsSdkLog.d("Right-here");
return true;
}
}
});
}
I want to call the method that i have that creates the folder when the error occurs but i don't know how to do that.
I'm new to this so i'd really appreciate the help
Thanks
The basic principe looks like this. I used the Java NIO library for testing.
The method 'createFolder' just wraps creating a folder. The test 'name' invokes the Single and checks for an Exception. If it is an IOException it will return a fallback value. You may do something different in there. You just provide a fallback single. If it is an error different from IOException, it will return the error.
#Test
void name() throws Exception {
final String TO_CREATE = "/home/sergej/Downloads/Wurstbrot";
this.createFolder(TO_CREATE)
.onErrorResumeNext(throwable -> { // handle Exception:
// Validate Exception
if (throwable instanceof IOException) {
// Return fallback
return Single.just(Paths.get("/home/sergej/Downloads/"));
}
return Single.error(throwable);
})
.test()
.await()
.assertValueCount(1)
.assertValue(path -> path.endsWith(TO_CREATE))
.assertNoErrors();
}
private Single<Path> createFolder(String p) {
return Single.defer(() -> { // may throw some IOException
Path path = Paths.get(p);
if (!Files.exists(path)) {
Path createdDirectory = Files.createDirectory(path); // will throw if already exists
return Single.just(createdDirectory);
}
// Or just return Path, because it already exists???
return Single.error(new IOException("Already exists"));
});
}