Paging DataSourceFactory toLiveData() toObservable() が見つからない。

Javaのこのコードが、


public class ConcertViewModel extends ViewModel {
  private ConcertDao concertDao;
  public final Observable<PagedList<Concert>> concertList;

  public ConcertViewModel(ConcertDao concertDao) {
    this.concertDao = concertDao;
    concertList = new RxPagedListBuilder<>(
          concertDao.concertsByDate(), 50)
                  .buildObservable();
  }
}

Kotlinでこう書けるはずなのに!


class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
  val concertList: Observable<PagedList<Concert>> =
        concertDao.concertsByDate().toObservable(pageSize = 50)
}

Paging library overview  |  Android Developers

見つからない toObservable()。

- Added DataSourceFactory.toLiveData() as a Kotlin alternative for LivePagedListBuilder
- Added DataSourceFactory.toObservable() and toFlowable() as Kotlin alternatives for RxPagedListBuilder

Paging  |  Android Developers

どうやら -ktx のようです。


implementation "androidx.paging:paging-runtime-ktx:2.1.0"
implementation "androidx.paging:paging-rxjava2-ktx:2.1.0"

Maven Repository: androidx.paging

最近は、同じ処理でも複数の書き方があることが多くなって最初混乱したりします。


SpaceX REST API で試す Retrofit の coroutine 対応

Retrofit の coroutine 対応が進んでいるようです。

Jake Wharton's retrofit2-kotlin-coroutines-adapter has been the go-to solution for bridging the coroutine world with Retrofit for a little while. But now, a much-anticipated PR has finally been merged, officially bringing coroutine support to Retrofit 2.

Retrofit meets coroutines - zsmb.co

JakeWharton/retrofit2-kotlin-coroutines-adapter: A Retrofit 2 adapter for Kotlin coroutine's Deferred type.

SpaceX-API とか公開されていたのですね。

r-spacex/SpaceX-API: Open Source REST API for rocket, core, capsule, pad, and launch data

https://api.spacexdata.com/v3/rockets

対応する最小限のデータクラスを作って、試してみましょう。


data class Rocket(

    val id: Int,

    @field:Json(name = "rocket_name") 
    val name: String

)


val retrofit = Retrofit.Builder()
    .baseUrl("https://api.spacexdata.com/v3/")
    .addConverterFactory(MoshiConverterFactory.create())
    .build()

val api = retrofit.create<SpaceXApi>()

create() は reified type が使えるようになってます。

JSONのシリアライズはお好みのものを。

そして、interface を書いていきますが、

ここを戻り値に別に見てみます。

Call<List<Rocket>>

interface はこれまでと同じ記述。


interface SpaceXApi {

  @GET("rockets")
  fun getRockets(): Call<List<Rocket>>

}

受け側の3つの例。


runBlocking {
  val rockets: List<Rocket> = api.getRockets().await()
  rockets.forEach(::println)
}


runBlocking {
  val rockets: List<Rocket> = try {
    api.getRockets().await()
  } catch (e: Exception) {
    println("Network error :[")
    return@runBlocking
  }
  rockets.forEach(::println)
}


runBlocking {
  val response = api.getRockets().awaitResponse()
  if (response.code() == 200) {
    response.body()?.forEach(::println)
  }
}

結果はすべて同じ。


Rocket(id=1, name=Falcon 1)
Rocket(id=2, name=Falcon 9)
Rocket(id=3, name=Falcon Heavy)
Rocket(id=4, name=Big Falcon Rocket)

これまで非同期処理時に必要だった コールバック や enqueue の記述は必要ありません。

受け側の記述を変化させることで、Exception や レスポンスコードを拾うことができます。

suspend List<Rocket>

suspend を使うことでさらに直感的に書けます。


interface SpaceXApi {

  @GET("rockets")
  suspend fun getRockets(): List<Rocket>

}


runBlocking {
  val rockets = api.getRockets()
  rockets.forEach(::println)
}

結果。


Rocket(id=1, name=Falcon 1)
Rocket(id=2, name=Falcon 9)
Rocket(id=3, name=Falcon Heavy)
Rocket(id=4, name=Big Falcon Rocket)

List<Rocket> に対して Call や Deferred のようなラッパーは必要ありません。

suspend Response<List<Rocket>>

Response を拾います。


interface SpaceXApi {

  @GET("rockets")
  suspend fun getRockets(): Response<List<Rocket>>

}


runBlocking {
  val response = api.getRockets()
  if (response.code() == 200) {
    response.body()?.forEach(::println)
  }
}

結果。


Rocket(id=1, name=Falcon 1)
Rocket(id=2, name=Falcon 9)
Rocket(id=3, name=Falcon Heavy)
Rocket(id=4, name=Big Falcon Rocket)

最も実用的で簡潔な記述と思われます。

まとめ

Kotlin で記述するにしても、多くの記述が可能となりそうです。

RxJava他ライブラリを利用しての非同期実装を含めると様々となります。

ネット上で検索するにも自分の環境や好みに合った記述を探すのにも時間がかかることになり混乱もありそうで。

Jakeも今後はメンテしないと思われます。

Márton Braun on Twitter: "Retrofit is finally receiving the proper coroutine support it deserves! Read more about it in my latest blog post here: https://t.co/8k9EauzzUa #AndroidDev #Kotlin #Coroutines" / Twitter

ちなみに、これらの話は今現在はSNAPSHOTで進行中です。

以下でどうぞ。


repositories {
  maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}

dependencies {
  implementation "com.squareup.retrofit2:retrofit:2.5.1-SNAPSHOT"
}


@ObsoleteCoroutinesApi とは「様子見」?

Coroutine で Channel 辺りをウロウロと。

あれ?

This declaration is experimental and its usage should be marked with '@kotlinx.coroutines.ObsoleteCoroutinesApi' or '@UseExperimental(kotlinx.coroutines.ObsoleteCoroutinesApi::class)'

調べてみる。

From the point of usage `ObsoleteCoroutinesApi` is just like experimental. The different is in intent. When API is experimental it might change or might graduate as is. For obsolete API we already know that there are problems with designs that will force us to deprecated this API in the future when we have a better replacement.

Right now there is no replacement for channel operations, so you have no choice but continue using them. However, we plan to replace them with mechanism based on lazy/cold streams (#254) in the future, which will get you much better performance.

What is the recommended approach for @ObsoleteCoroutinesApi #632

Google翻訳で。

使用の観点からObsoleteCoroutinesApiは実験的なようです。 違いは意図しています。 APIが実験的なものである場合、それは変更されるか、またはそのまま卒業するかもしれません。 時代遅れのAPIについては、より良い代替品があるときに将来的にこのAPIを非推奨にするような設計上の問題があることはすでにわかっています。

今のところチャンネル操作に代わるものはないので、あなたには選択肢がないがそれらを使い続けることができる。 ただし、将来的にはレイジー/コールドストリーム(#254)に基づくメカニズムに置き換える予定です。これにより、パフォーマンスが大幅に向上します。

まとめ


@ExperimentalCoroutinesApi

→ 将来的には、変更される or そのまま。


@ObsoleteCoroutinesApi

→ 設計上の問題がある。

→ 今現在、代替えはない。

→ 将来的には非推奨。

アンダースコアがついてるのはそゆこと?


_events.consumeEach {

チャンネル周りは、今は「様子見」がいいのでしょうね。