0

I'm playing with RxJava in Android. I'm trying to get the response of places from the server. Everything works fine:

Here is my Retrofit interface:

@GET("/some/path")
Observable<Response> search(@QueryMap Map map);

Here is how I handle this:

    endpointService.search(requestMap)
            .onErrorReturn(throwable -> {
                L.e(TAG, "problems");
                return null;
            })
            .subscribe(rxBus::send);

As you may see, I post a event (RxBus) to interested parties, in this case, a fragment:

    rxBus.register(Response.class)
            .subscribe(event -> {
                L.d(TAG, "Received search response");
            });

For now it works. I'm not shure, this "idea" of handling REST calls won't finish in a disaster (I'm new into RxJava), but for now it looks allright...

Anyways - this response is huge. I only need a list of names and addresses. And this response gives me a huuuge JSON with a lot of additional data.

My idea is to change this with map (or flatMap, I still have problems with knowing the difference). I've implemented something like

    .map(event -> {
             List<SearchItem> list = new ArrayList<>();
             for (Response r : event.getItems) {
                  list.add(new Item(r.getName, r.getAddress));
              }
                 return list;
            }
         )

And it works, however I'm not sure if foreach-ing isn't a bit too "oldschool". IDE says that I cant use collect call however I've never used it, and the autocorrection makes things even worse :)

So - am I handling this whole thing in a right way?

EDIT:

I modified my code:

rxBus.register(Response.class)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .map(event -> event.getResponse().getList())
            .flatMap(Observable::from)
            .map(v -> new SearchItem(v.getName(), v.getAddress())
            .toList()
            .subscribe(event -> {
                L.w(TAG, "Stuff happens");
            });

I have no idea what happened, but this code no longer works. Well - kinda. Without .toList() it launches that Log a number of times. However with - nothing happens. Am I doing something wrong?

a_dzik
  • 927
  • 2
  • 11
  • 24
  • What would *not* work if you just initialilzed your `ArrayAdapter` with the return value of `event.getItems()`? Regarding `flatMap` vs. `map` - you *can* alway use `flatMap` where you could use `map` (just with a different `Func1`), but the reverse is not true. Also: http://stackoverflow.com/questions/22847105/when-do-you-use-map-vs-flatmap-in-rxjava https://www.reddit.com/r/androiddev/comments/2y8exc/what_is_the_difference_between_map_and_flatmap_in/ http://stackoverflow.com/questions/33830063/call-a-function-after-retrofix-and-rxjava-completes/33860711#33860711 – david.mihola Jan 30 '16 at 18:36
  • Oh, sorry, I just now saw that you are creating new `Item`s inside the loop... nevermind. In that case, what you are doing is fine, I think. You **could** however, go Rx all the way and (1) `map` the `List` out of the event, (2) use `flatMap` to transform the `Observable>` to an `Observable`, and (3) `map` each of them to an `Item`. – david.mihola Jan 30 '16 at 18:40

1 Answers1

2

I think the general approach is fine. You can slightly improve the handling of items by something like:

...
.map(Event::getItems)
.flatMap(Observable::from)
.map(response -> new Item(response.getName(), response.getAddress()))
.toList()
...

I assume you use retrolambda. In this case what the IDE suggests ("collect") is to apply Java 8 Streams API syntax to the for-loop, which won't work in Android.

EDIT

I didn't realize the first option wouldn't work for the hot observable case that never emits onCompleted event, which is requiered by toList() operator to know when to stop assembling the list.

Thanks to @david.mihola, there's a different option for this case:

...
.flatMap(event -> Observable.from(event.getResponse().getList())
                            .map(item -> new SearchItem(v.getName(), v.getAddress()))
                            .toList())
...
AndroidEx
  • 15,524
  • 9
  • 54
  • 50
  • Thanks, I thought about something like that, but I newer knew about `.toList()` :) – a_dzik Jan 30 '16 at 18:47
  • Well, I managed to do something wrong, and this code no longer works. I have no idea why, I have edited my post. – a_dzik Jan 30 '16 at 19:40
  • @a_dzik I just realized in your case `onCompleted` event never comes through `rxBus` and `toList()` needs this terminal event to know when to stop collecting items. Right now it adds up to the list infinitely. – AndroidEx Jan 30 '16 at 20:11
  • Well... probably. How should I handle it? – a_dzik Jan 30 '16 at 20:15
  • 1
    You could try putting the `Observable.from(event.getResponse().getList()).map(...).toList()` inside the first `map` - that should make sure that *one `Event`* is mapped to *one `List`* . – david.mihola Jan 30 '16 at 20:25
  • 1
    @david.mihola thank you, only we'll need `flatMap` instead. I'll modify the answer – AndroidEx Jan 30 '16 at 20:32
  • @AndroidEx - now works like a charm, however with small fix on my side - I had to cast `item` to that `ResponseItem` in order to make it see `.getName()` and such :) – a_dzik Jan 30 '16 at 20:49
  • 1
    @a_dzik oh, probably the compiler wasn't able to figure out generics in this case. Another way to handle this issue is to call `Observable.from` like `Observable.from(...)` – AndroidEx Jan 30 '16 at 20:54
  • @AndroidEx: Yeah, of course `flatMap` - though a `map` with `toList().toBlocking().first()` should also work, I think. – david.mihola Jan 31 '16 at 09:28