@Provides メソッドはすべて static に - Dagger2

👉 Dagger 2 on Android: The Official Guidelines You Should Be Following 

any module whose @Provides methods are all static, the implementation doesn’t need an instance at all.

すべてのモジュールの @Provides メソッドはすべて static。実装部分にインスタンスは必要ない。

👉 User's Guide 

モジュールの @Provides メソッドが static であれば、Daggerはそれをインスタンス化する必要がない。

もし、そうできないのならそのモジュールは「状態」を持っているので修正するべき。

Warning: it’s discouraged for modules to have state; this can lead to unpredictable behavior. Moreover, module instances in general are rarely useful. We have considered removing them.

警告: モジュールが「状態」を持つことは推奨されません。これにより、予期しないふるまいが発生する可能性があります。

👉 Dagger Core Semantics 

Kotlin で static なメソッドを書く場合2つの方法があります。

トップレベルで object を使う方法。


@Module
object DataModule {
  @JvmStatic @Provides fun provideDiskCache() = DiskCache()
}

companion object を使う方法。


@Module
abstract class DataModule {
  @Binds abstract fun provideCache(diskCache: DiskCache): Cache

  @Module
  companion object {
    @JvmStatic @Provides fun provideDiskCache() = DiskCache()
  }
}

👉 Kotlin+Dagger best practices/documentation/pain points · Issue #900 · google/dagger 

この2つの方法に対してJakeさんがコメントしています。

First of all, this speed benefit will be extraordinarily minor. If you are looking to make your app faster then use a profiler. You will find 100 easier optimization targets.
Beyond that, don't use companion object for modules. Use object. In that case the instance will be unused and its initialization code will be removed by R8 and the methods will be truly static and can also be inlined just like Java.

まず第一に、この速度の利点は非常にわずかです。アプリをより高速にしたい場合は、プロファイラーを使用してください。 100の簡単な最適化ターゲットがあります。
さらに、モジュールには「コンパニオンオブジェクト」を使用しないでください。オブジェクトを使用します。その場合、インスタンスは使用されず、その初期化コードはR8によって削除され、メソッドは真に静的であり、Javaと同様にインライン化することもできます。

👉 Kotlin+Dagger best practices/documentation/pain points · Issue #900 · google/dagger 

インスタンスが不要な場合、R8の静的化によりオブジェクトのメソッドを静的にすることができますが、Daggerはコンパイル時にそれらを静的にする必要があるため、@JvmStaticアノテーションは必要です。


Related Categories :  AndroidDevelopmemtKotlin


Dagger2 Dependency Graph を視覚化する「Daggraph」

クローズドな一般国内環境にて、

他人の書いたコードというのは、

非常に読みづらいものです。

Dagger のようなバイトコードを生成するコンパイルタイムなフレームワークであればさらにその特性は増してしまいます。

図で視覚化して概要を捉えましょう。



👉 dvdciri/daggraph: Dagger dependency graph generator for Android Developers 

シンプルなコマンドラインツールなので入れてみて損はないでしょう。


Tracing the Origins of 'Presentation Model' in MVVM — and Martin Fowler’s Seven-Year-Old Insight

While researching things around “MVVM,” I kept seeing the term “Presentation Model,” so I decided to look into it.

Martin Fowler mentioned it — about seven years ago.


MVVM is the Microsoft's world's name for the same pattern as Presentation Model. I haven't updated the article since.

マイクロソフトの「MVVM」パターンは「Presentation Model」と同じなので、記事は更新していません。

👉 Martin Fowler on Twitter: "@HerberthAmaral MVVM is the Microsoft's world's name for the same pattern as Presentation Model. I haven't updated the article since." / Twitter 

I looked for the sources for each of them.

Presentation Model (Martin Fowler, 2004)

Represent the state and behavior of the presentation independently of the GUI controls used in the interface

👉 Presentation Model 

MVVM (Microsoft, 2005)

Model/View/ViewModel is a variation of Model/View/Controller (MVC) that is tailored for modern UI development platforms where the View is the responsibility of a designer rather than a classic developer. The designer is generally a more graphical, artistic focused person, and does less classic coding than a traditional developer.

👉 Introduction to Model/View/ViewModel pattern for building WPF apps – Tales from the Smart Client 

Conclusion

2012年4月現在、XAMLを使用するWPFなどのテクノロジ以外で使用されるMVVMは実質Presentation Modelと変わらず、Viewの抽象化などはできない。

👉 Model View ViewModel - Wikipedia (ja) 

MVVM is a variation of Martin Fowler's Presentation Model design pattern. MVVM abstracts a view's state and behavior in the same way, but a Presentation Model abstracts a view (creates a view model) in a manner not dependent on a specific user-interface platform.

👉 Model–view–viewmodel - Wikipedia (en) 


coroutine の使い方 - Android Architecture Blueprints v2 #1

気がついたら「v2」です。MVVM のみとなって、 RxJava は姿を消していますが。

👉 googlesamples/android-architecture: A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. 



現在、4つのバリエーションが公開されていますが、以下は共通。


- kotlin
- coroutine
- single activity
- architecture component
- navigation component + fragment
- presentetion layer(per page) = fragment + view model
- reactive ui = live data + data binding
- data layer = repositpory + local(room) + remote
- datalayer one shot operations(no listener or data streams)

非同期処理には、どのバリエーションも「coroutine」使う。

どのように coroutine を使っているか、を定型化しておきたい。

関数 coroutineScope

「kotlinx-coroutines-core」の関数の「coroutineScope」 が目についたので見ておく。


suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() → R
): R (source)

CoroutineScope を作成し、このスコープで指定された suspend ブロックを呼び出します。その外側のスコープから coroutineContext を継承しますが、そのコンテキストのJobをオーバーライドします。

この関数は、処理の並列分解用に設計されています。 このスコープ内のいずれかの子コルーチンが失敗すると、このスコープは失敗し、残りのすべての子はキャンセルされます(supervisorScope との違いを参照してください)。 与えられたブロックとそのすべての子コルーチンが完了するとすぐに戻ります。
スコープの使用例は次のようになります。


suspend fun showSomeData() = coroutineScope {

  val data = async(Dispatchers.IO) { // <- extension on current scope
     // ... load some UI data for the Main thread ...
  }

  withContext(Dispatchers.Main) {
    doSomeWork()
    val result = data.await()
    display(result)
  }
}

この例のスコープの意味は次のとおりです。

1. showSomeDataは、データがロードされてUIに表示されるとすぐに戻ります。
2. doSomeWorkが例外をスローすると、非同期タスクはキャンセルされ、showSomeDataはその例外を再スローします。
3. showSomeData の外部スコープが取り消されると、開始された async ブロックとwithContext ブロックの両方が取り消されます。
4. 非同期ブロックが失敗すると、withContext はキャンセルされます。

現在のジョブが外部でキャンセルされた場合、メソッドは CancellationException をスローします。このスコープ内に未処理の例外がある場合(たとえば、このスコープ内の起動で開始されたクラッシュコルーチンから)は、対応する未処理のThrowableをスローします。

coroutineScope - kotlinx-coroutines-core

実際の記述

coroutine を使った非同期処理は 各 ViewModel を起点に記述されています。


class TasksViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() {

    fun loadTasks(forceUpdate: Boolean) {
        viewModelScope.launch {

            // 対応する Repository メソッドをコール
            // val tasksResult = tasksRepository.getTasks(forceUpdate)

        }
    }

}

👉 android-architecture/TasksViewModel.kt at master · googlesamples/android-architecture 

Repository を展開して、スコープ周りを抜き出してみると、


class TasksViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() {

    fun loadTasks(forceUpdate: Boolean) {
        viewModelScope.launch {

            withContext(ioDispatcher) {
            
                // ...            

            }

        }
    }

}

というこれまで通り withContext() を使った dispatcher の切り換え。

他には、

前述の coroutineScope 関数を使った記述が、単独、並列、入れ子の3種類。


override suspend fun saveTask(task: Task) {
    coroutineScope {
        launch { tasksRemoteDataSource.saveTask(it) }
        launch { tasksLocalDataSource.saveTask(it) }
    }
}


override suspend fun clearCompletedTasks() {
    coroutineScope {
        launch { tasksRemoteDataSource.clearCompletedTasks() }
        launch { tasksLocalDataSource.clearCompletedTasks() }
    }
    withContext(ioDispatcher) {
        cachedTasks?.entries?.removeAll { it.value.isCompleted }
    }
}


override suspend fun deleteAllTasks() {
    withContext(ioDispatcher) {
        coroutineScope {
            launch { tasksRemoteDataSource.deleteAllTasks() }
            launch { tasksLocalDataSource.deleteAllTasks() }
        }
    }
}

👉 android-architecture/DefaultTasksRepository.kt at master · googlesamples/android-architecture 

今後、さらに簡潔に記述されていくのだろうが、意図は今のが理解しやすいと思いメモ。




Android Paging Library と Retrofit

例えば、このパターン、Network only。

ここでは、PositionalDataSource を拡張するが、他のタイプのDataSource拡張でも同じ。

compositeDisposable のように viewModelScope をはるばる持ってきたにもかかわらず、


class RemoteDataSource(
  private val coroutineScope: CoroutineScope,
  private val service: RemoteService,
  private val s: String
) : PositionalDataSource<Item>() {

  @ExperimentalCoroutinesApi
  override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
    Timber.d("RemoteDataSource#loadInitial: ${Thread.currentThread().name}")
    ...

loadInitial() 内は、メインスレッドではなく、別スレッドで実行されている。


Timber.d("RemoteDataSource#loadInitial: ${Thread.currentThread().name}")


D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_0
D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_1
D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_3

これらは、Android Architecture Component が作成した独自スレッド。

よって、同期なRetrofitの実行処理で良い、となる。

If I use enqueue with Retrofit 2.3 it will doesn't work but if i do a .execute() the LiveData is correctly triggered

Retrofit 2.3で、enqueue() を使用しても機能しませんが、execute() を実行するとLiveDataが正しくトリガーされます。

Android Paging Library LiveData> is triggered before the end of the api call

公式リファレンスにも記述はあるという。

To display data from a backend server, use the synchronous version of the Retrofit API to load information into your own custom DataSource object.

バックエンドサーバーからのデータを表示するには、同期バージョンのRetrofit APIを使用して、独自のカスタムDataSourceオブジェクトに情報をロードします。

Network only - Paging library overview

しかし、DiffUtilを使ったリフレッシュなアニメーションが実行されない。

「手間がかかる」でなく「沼にハマる」ことが最近は多くなった。

APIの仕様がおせっかいすぎやしないか。

しきいを下げようとして、余計に混乱させるばかり。

👉 あなたは Android Architecture Component をどう思いますか? 
👉 Fragment と Toolbar の歴史の話 - Qiita