あなたは Android Architecture Component をどう思いますか?

ある人々のTwitter上の会話。ザクッと翻訳サービスを利用して眺めてみます。

Frankly, the expectation is that all applications of a considerable size already use some form of DI. Thus providing yet another way to pass dependencies via context is an overkill. All "non-DI" facilities are just gimmicks/workarounds for cases when there is no DI.

率直に言って、かなりの数のアプリケーションがすでにDIを使用している。したがって、コンテキストを介して依存関係を渡すためのさらに別の方法を提供するのはやり過ぎです。すべての「非DI機能」は、DIがない場合のギミック/回避策にすぎません。

On Android this sadly isn't the case. We seem to relish in bad architecture.

It's either "Why do I have to pass an executor/scheduler/dispatcher? So much boilerplate!" or "Why can't I test on the JVM? This library is poorly designed!"

Maybe KEEP-87 will save us from ourselves?

Androidでは、これは悲しいことではありません。悪いアーキテクチャーで大喜びしているようです。

「executor / scheduler / dispatcher を渡す必要があるのはなぜですか? かなりのボイラープレートです。」、「なぜJVMでテストできないのですか?このライブラリは適切に設計されていません。」

多分KEEP-87は私達を私達自身から救うのだろうか?

👉 [Kotlin] KEEP87 brings compiler-driven dependency injection without frameworks : androiddev 

On unrelated note. (disclaimer: I'm not an Android developer) I have a feeling that something is broken in testing or architecture approaches here. I've been writing huge (1M+ LOCs) UI apps for more than a decade and never had to use either DI or statics to make them testable.

無関係なメモについて。 (免責事項:私はAndroidの開発者ではありません)何かがここでテストやアーキテクチャのアプローチで壊れていると感じています。私は10年以上にわたって巨大な(1M + LOC)UIアプリを書いてきました。そしてそれらをテストするために DI や statics を使う必要は決してありませんでした。

Android never had architecture guidelines and the docs encouraged doing all the wrong things to make the tutorials easy. Basically equivalent to writing everything in main(). Even now almost all of the architecture offerings treat symptoms of this legacy and not the disease.

Androidはアーキテクチャのガイドラインがなかったので、チュートリアルを簡単にするためにすべての間違ったことをすることをドキュメントを奨めてきました。基本的にmain() ですべてを書くのと同じです。現在でも、ほとんどのアーキテクチャがこの遺産の症状を治療していて、疾患を治療していません。

👉 Roman Elizarov on Twitter: "@JakeWharton Frankly, the expectation is that all applications of a considerable size already use some form of DI. Thus providing yet another way to pass dependencies via context is an overkill. All "non-DI" facilities are just gimmicks/workarounds for cases when there is no DI." / Twitter 

みなさんはどう思っていますか?

Roman Elizarov
@relizarov
Team Lead @JetBrains, working on @Kotlin coroutines and libs, sports programming/ICPC, concurrency & algorithms, math/quantitative finance; formerly @Devexperts

👉 Roman Elizarov (@relizarov) / Twitter 

Jake Wharton
@JakeWharton
Opinions expressed here are my own, not those of my company. They made me write this because I complain about Inbox going away so much.

👉 Jake Wharton (@JakeWharton) / Twitter 


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