abemart
abemart

Reputation: 80

RXJava/Kotlin - Chaining Single results in one

I have a problem and I don't know how to resolve it with a better aproach. The problem is that I'm requesting to Spotify Web API and in some methods the artist image is returned and in others only basic artist info is obtained.

I have this two methods:

fun getAlbum(albumId: String): Single<SpotifyAlbumDTO>

fun getArtist(artistId: String): Single<SpotifyArtistDTO>

When I get an album, the artist info doesn't contains the artist image url. So, I need to call the getAlbum() method and use the result to obtain the artistId and then call to getArtist() method.

I have the following method to do all this stuff:

fun getAlbum(albumId: String): Single<Album>

On this method I need to call the previous two to return an Album object (my domain object). The only solution that worked for me it's the following:

fun getAlbum(albumId: String): Single<Album> {
  return Single.create { emitter ->
    _spotifyService.getAlbum(albumId).subscribe { spotifyAlbum ->
      _spotifyService.getArtist(spotifyAlbum.artist.id).subscribe { spotifyArtist ->
        val artistImage = spotifyArtist.imageUrl
        spotifyAlbum.artist.image = artistImage
        emitter.onNext(spotifyAlbum.toAlbum())
      }
    }
  }
}

I think that must exist another better way to do this than concating subscribe calls in other subscribes and creating deeper calls. I also try the following:

_spotifyService.getAlbum(albumId).flatMap { spotifyAlbum ->
  _spotifyService.getArtist(spotifyAlbum.artist.id)
}.flatMap { spotifyArtist ->
  // Here I don't have the album and I can't to asign the image         
}

Upvotes: 3

Views: 3811

Answers (2)

Gomino
Gomino

Reputation: 12347

PARALLEL SOLUTION

To combine several source of data the best operator is zip:

Rx Zip operator

Single.zip(getAlbum(albumId), getArtist(artistId),
    BiFunction<SpotifyAlbumDTO, SpotifyArtistDTO, SpotifyAlbumDTO> { album, artist -> 
        val artistImage = artist.imageUrl
        album.artist.image = artistImage
        album //return value of the merged observable 
    }
).subscribe { album: SpotifyAlbumDTO?, error: Throwable? ->
    emitter.onNext(album.toAlbum())
}

It will run all the observables in parallel, and execute the merging function once every observable has finished.

If you have more observable to zip, you can use Function3, Function4,...

SEQUENTIAL SOLUTION (EDIT)

If parallel execution is not possible because you need the request to be executed sequentially then, you can you the resultSelector of the flatmap function. It takes the item before the flatMap and the grouped collection after the flatmap. This way you will easily be able to create your model group, without any confusing usage of Pair

Flatmap with resultSelector

The only catch is: Single does not support this kind of flatmap. You can workaround the issue either changing your return type from Single to Observable or just convert your Single at runtime with the toObservable operator.

getAlbum(albumId)
    .toObservable()
    .flatMap({album: SpotifyAlbumDTO -> getArtist(album.artist.id).toObservable()},
             { album:SpotifyAlbumDTO, artist:SpotifyArtistDTO ->
                 album.artist.image = artist.imageUrl
                 album
             })
    }?.subscribe { album: SpotifyAlbumDTO ->
        print(album)
    }

Upvotes: 5

Christilyn Arjona
Christilyn Arjona

Reputation: 2283

You can use Pair with flatMap to wrap your results together:

_spotifyService.getAlbum(albumId).flatMap { spotifyAlbum ->
  _spotifyService.getArtist(spotifyAlbum.artist.id)
       .flatMap { artist ->
           Single.just(spotifyAlbum, artist)
       }
}.subscribe { pair: Pair<Album, Artist> ->
  // grab results
  val album = pair.first
  val artist = pair.second      
}

Upvotes: 3

Related Questions