RXJava/Kotlin - Объединение отдельных результатов в один

У меня есть проблема, и я не знаю, как решить ее с помощью лучшего подхода. Проблема в том, что я запрашиваю веб-API Spotify, и в некоторых методах возвращается изображение исполнителя, а в других получается только основная информация об исполнителе.

У меня есть два метода:

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

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

Когда я получаю альбом, информация об исполнителе не содержит URL-адрес изображения исполнителя. Итак, мне нужно вызвать метод getAlbum() и использовать результат для получения artistId, а затем вызвать метод getArtist().

У меня есть следующий метод, чтобы сделать все это:

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

В этом методе мне нужно вызвать два предыдущих, чтобы вернуть объект альбома (объект моего домена). Единственное решение, которое сработало для меня, это следующее:

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())
      }
    }
  }
}

Я думаю, что должен существовать другой лучший способ сделать это, чем объединение вызовов подписки в другие подписки и создание более глубоких вызовов. Я также пробую следующее:

_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         
}

person abemart    schedule 12.01.2020    source источник


Ответы (2)


ПАРАЛЛЕЛЬНОЕ РЕШЕНИЕ

Для объединения нескольких источников данных лучше всего использовать оператор zip:

Оператор Rx Zip

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())
}

Он будет запускать все наблюдаемые параллельно и выполнять функцию слияния после завершения каждого наблюдаемого.

Если у вас есть более заметные возможности для zip, вы можете использовать Function3, Function4,...

ПОСЛЕДОВАТЕЛЬНОЕ РЕШЕНИЕ (РЕДАКТИРОВАТЬ)

Если параллельное выполнение невозможно, поскольку вам нужно, чтобы запрос выполнялся последовательно, вы можете использовать resultSelector функции плоской карты. Он принимает элемент до flatMap и сгруппированную коллекцию после flatmap. Таким образом, вы легко сможете создать свою модельную группу без какого-либо запутанного использования Pair.

Плоская карта с селектором результатов

Единственная загвоздка: Single не поддерживает этот вид плоской карты. Вы можете обойти эту проблему, либо изменив тип возвращаемого значения с Single на Observable, либо просто преобразовать Single во время выполнения с помощью оператора toObservable.

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)
    }
person Gomino    schedule 12.01.2020
comment
Извините, но я не могу использовать zip, потому что я не знаю artistId, пока не будет выполнен getAlbum() - person abemart; 13.01.2020
comment
Я неправильно понял ваш вопрос, я обновил свой ответ - person Gomino; 13.01.2020
comment
Благодарю вас! Это то, что мне нужно - person abemart; 13.01.2020

Вы можете использовать Pair с flatMap, чтобы объединить результаты:

_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      
}
person Christilyn Arjona    schedule 12.01.2020
comment
Спасибо, это отличный подход! Я хотел бы знать другие решения, я думаю, что должен существовать оператор для накопления предыдущих результатов, потому что, если вы хотите объединить больше вызовов, я думаю, что это не самое лучшее. Это не странный случай, и для этого должны быть лучшие решения! - person abemart; 13.01.2020