69

I have the List of SourceObjects and I need to convert it to the List of ResultObjects.

I can fetch one object to another using method of ResultObject:

convertFromSource(srcObj);

of course I can do it like this:

public void onNext(List<SourceObject> srcObjects) {
   List<ResultsObject> resObjects = new ArrayList<>();
   for (SourceObject srcObj : srcObjects) {
       resObjects.add(new ResultsObject().convertFromSource(srcObj));
   }
}

but I will be very appreciate to someone who can show how to do the same using rxJava.

Yura Buyaroff
  • 1,718
  • 3
  • 18
  • 31

9 Answers9

85

If your Observable emits a List, you can use these operators:

  • flatMapIterable (transform your list to an Observable of items)
  • map (transform your item to another item)
  • toList operators (transform a completed Observable to a Observable which emit a list of items from the completed Observable)

    Observable<SourceObjet> source = ...
    source.flatMapIterable(list -> list)
          .map(item -> new ResultsObject().convertFromSource(item))
          .toList()
          .subscribe(transformedList -> ...);
    
Community
  • 1
  • 1
dwursteisen
  • 11,435
  • 2
  • 39
  • 36
  • 29
    This looks really clean, though has some disadvantages. 1. If source emits more than one list, they are all going to flattened by `flatMapIterable` into a single stream of objects and `toList()` will collect them in a single list. So if you care about multiple emissions and keeping them in separate lists, then unfortunately you lose this. 2. `toList()` waits for the source observable to complete, so if you do that on an infinite observable (like one bound to UI events), you will never get anything in your `subscribe()`. – Marcin Koziński May 22 '16 at 21:03
  • @MarcinKoziński how would you solve nr. 2? I am having the situation where .toList does not return anything. Thanks – Javier Mendonça Jun 22 '16 at 06:54
  • 5
    I think you just need to use a different approach. If you're trying to transform each element of a list, you might have to just do `.map(list -> /* build a new list using plain old for loop */)`. Or use any other approach to transform your list inside the `.map()`. Otherwise you could use operators like `.take(itemCount)` on your infinite observable to make it finite. Note that this obviously drops all elements after `itemCount`, so that might not be what you want. – Marcin Koziński Jun 23 '16 at 08:58
  • "some disadvantages" - rather, I'd say this is almost always not what is wanted. This answer should be down-voted and Noel's answer should be upvoted. – methodsignature Apr 11 '18 at 14:37
  • Thank you so much sir, for the jargon free explanation. Super helpful. – Pedro Rodrigues Aug 21 '19 at 15:02
73

If you want to maintain the Lists emitted by the source Observable but convert the contents, i.e. Observable<List<SourceObject>> to Observable<List<ResultsObject>>, you can do something like this:

Observable<List<SourceObject>> source = ...
source.flatMap(list ->
        Observable.fromIterable(list)
            .map(item -> new ResultsObject().convertFromSource(item))
            .toList()
            .toObservable() // Required for RxJava 2.x
    )
    .subscribe(resultsList -> ...);

This ensures a couple of things:

  • The number of Lists emitted by the Observable is maintained. i.e. if the source emits 3 lists, there will be 3 transformed lists on the other end
  • Using Observable.fromIterable() will ensure the inner Observable terminates so that toList() can be used
Noel
  • 7,350
  • 1
  • 36
  • 26
11

The Observable.from() factory method allows you to convert a collection of objects into an Observable stream. Once you have a stream you can use the map operator to transform each emitted item. Finally, you will have to subscribe to the resulting Observable in order to use the transformed items:

// Assuming List<SourceObject> srcObjects
Observable<ResultsObject> resultsObjectObservable = Observable.from(srcObjects).map(new Func1<SourceObject, ResultsObject>() {
    @Override
    public ResultsObject call(SourceObject srcObj) {
        return new ResultsObject().convertFromSource(srcObj);
    }
});

resultsObjectObservable.subscribe(new Action1<ResultsObject>() { // at this point is where the transformation will start
    @Override
    public void call(ResultsObject resultsObject) { // this method will be called after each item has been transformed
        // use each transformed item
    }
});

The abbreviated version if you use lambdas would look like this:

Observable.from(srcObjects)
  .map(srcObj -> new ResultsObject().convertFromSource(srcObj))
  .subscribe(resultsObject -> ...);
murki
  • 879
  • 12
  • 21
5

Don't break the chain, just like this.

Observable.from(Arrays.asList(new String[] {"1", "2", "3", }))
.map(s -> Integer.valueOf(s))
.reduce(new ArrayList<Integer>, (list, s) -> {
    list.add(s);
    return list;
})
.subscribe(i -> {
    // Do some thing with 'i', it's a list of Integer.
});
AtanL
  • 125
  • 5
  • 1
    Instead of `reduce` you could use `toList()` for a cleaner code. – Anyonymous2324 Mar 02 '16 at 08:58
  • Actually there are problems with `toList` like described in this comment above. reduce solves them http://stackoverflow.com/questions/35734060/rxjava-how-to-convert-list-of-objects-to-list-of-another-objects#comment62270275_35750170 – tasomaniac Aug 08 '16 at 15:31
  • 1
    Nope. My comment is not true. :) The same problems are also with `reduce` because `reduce` also waits for completion. – tasomaniac Aug 08 '16 at 15:33
2

Non blocking conversion using nested map function

val ints: Observable<List<Int>> = Observable.fromArray(listOf(1, 2, 3))

val strings: Observable<List<String>> = ints.map { list -> list.map { it.toString() } }
tomrozb
  • 25,773
  • 31
  • 101
  • 122
1

You can use map operator. For example if you have lists of integers and you want to convert to lists of doubles:

    List<Integer> integers = new ArrayList<>();
    Observable.just(integers).map(values -> {
        List<Double> doubles = new ArrayList<Double>();
        for(Integer i: values) {
            doubles.add(i.doubleValue());
        }
        return doubles;
    });

But all it's a lot more natural if you can control the observable and change it to observe single elements instead of collections. Same code that converts single integer elements into double ones:

Observable.just(1,2,3).map(element -> element.doubleValue())
sud007
  • 5,824
  • 4
  • 56
  • 63
lujop
  • 13,504
  • 9
  • 62
  • 95
  • Wouldn't `just(li)` emit a single item (the list), not every element from the list like `from(li)`? – OneCricketeer Mar 02 '16 at 01:53
  • @cricket_007 Yes, but as my understand of the problem Yura wants to emit lists no elements of the list (It uses onNext(List)). As I said in my response it's not the more natural, but sometimes can be what you want. – lujop Mar 02 '16 at 08:45
1

As an extension to Noel great answer. Let's say transformation also depends on some server data that might change during subscription. In that case use flatMap + scan.

As a result when id list changes, transformations will restart anew. And when server data changes related to a specific id, single item will be retransformed as well.

fun getFarmsWithGroves(): Observable<List<FarmWithGroves>> {

        return subscribeForIds() //may change during subscription
                .switchMap { idList: Set<String> ->

                    Observable.fromIterable(idList)
                            .flatMap { id: String -> transformId(id) } //may change during subscription
                            .scan(emptyList<FarmWithGroves>()) { collector: List<FarmWithGroves>, candidate: FarmWithGroves ->
                                updateList(collector, candidate)
                            }
                }
    }
Andrew
  • 2,438
  • 1
  • 22
  • 35
1

Using toList() waits for the source observable to complete, so if you do that on an infinite observable (like one bound to UI events and Database calls), you will never get anything in your subscribe().

The solution to that is to Use FlatMapSingle or SwitchMapSingle.

Observable<List<Note>> observable =   noteDao.getAllNotes().flatMapSingle(list -> Observable.fromIterable(list).toList());

Now,everytime your list is updated, you can make that event behave as an observable.

Usman Zafer
  • 1,261
  • 1
  • 10
  • 15
0

If what you need is simply List<A> to List<B> without manipulating result of List<B>.

The cleanest version is:

List<A> a = ... // ["1", "2", ..]
List<B> b = Observable.from(a)
                      .map(a -> new B(a))
                      .toList()
                      .toBlocking()
                      .single();
saiday
  • 1,290
  • 12
  • 25