Architecture Blueprints の非同期処理実装にみる Android SDK の方向性

MVP、MVVM、Clean Architecture、Dagger2、Data Binding、Archtecture Components などいろいろな組み合わせの実装例が ToDoアプリにて公開されています。

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

非同期処理部分を見てみると現在はすべて(todo-mvp-rxjavaを除く)が以下の実装となり、非同期処理の主役であった AsyncTask/Loader API を利用した記述は消え去っています。

まず、java.util.concurrent.Executor(s) を使って、AppExecutors を作っておいて、


open class AppExecutors constructor(
    val diskIO: Executor = DiskIOThreadExecutor(),
    val networkIO: Executor = Executors.newFixedThreadPool(THREAD_COUNT),
    val mainThread: Executor = MainThreadExecutor()
) {

  private class MainThreadExecutor : Executor {

    private val mainThreadHandler = Handler(Looper.getMainLooper())

    override fun execute(command: Runnable) {
      mainThreadHandler.post(command)
    }
  }
}

AppExecutors.kt

それに対応するデータベースやストレージ向けのExecutorを作ります。


class DiskIOThreadExecutor : Executor {

  private val diskIO = Executors.newSingleThreadExecutor()

  override fun execute(command: Runnable) { diskIO.execute(command) }
}

DiskIOThreadExecutor.kt

これらを使って以下のようにして非同期処理を実装します。


appExecutors.diskIO.execute {

  // IOスレッドで実行する
  // ...

  appExecutors.mainThread.execute {

    // メインスレッドで実行する
    // ...

  }

}

実装例では、コールバックを使ってPresenterまで伝達しています。


override fun getTasks(callback: TasksDataSource.LoadTasksCallback) {

  appExecutors.diskIO.execute {

    // IOスレッドで実行する
    val tasks = tasksDao.getTasks()

    appExecutors.mainThread.execute {

      // メインスレッドで実行する
      if (tasks.isEmpty()) {
        callback.onDataNotAvailable()
      } else {
        callback.onTasksLoaded(tasks)
      }

    }

  }
}

TasksLocalDataSource.kt

AsyncTask/Loader APIs の排除の方向性は、「Deprecated(廃止予定) samples」に移動されたブランチからも認識できます。

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

この流れについては、droidcon NYC 2017 - Android Architecture Round Table でも、話が挙がっていました。

it looks like Google is abandoning old API is like loaders and recommending patterns there are much less coupled with the framework, which is good but what happens with these API is are we abundant in then and I'm talking about classes like sync adapters loader async tasks etc

Google はLoaderのような古いAPI や フレームワークと関係の薄いパターンを推奨することをやめているように見えます。 それはいいことですが、それら古いAPIを捨てることは何を引き起こすか、AsyncAdapter や Loaderなどについて話したいと思います。

今後の、Android SDKは、フレームワークを意識したAPIが増えていくのでしょう。


すばやく理解する「Room x RxJava 」

いい記事があったので。

Room 🔗 RxJava – Google Developers – Medium

まずは、Room で Dao.


@Query(“SELECT * FROM Users WHERE id = :userId”)
User getUserById(String userId);

ここまでで問題なのは、

1. 同期呼び出しでブロッキング。
2. データ変更時に再度呼び出す必要がある。

ということで、RxJava を使いたくなります。

Room は RxJava2.x に対応しています。

Adding Components to your Project | Android Developers

どのように使うのか?

Maybe


@Query(“SELECT * FROM Users WHERE id = :userId”)
Maybe<User> getUserById(String userId);

1. 該当ユーザがなければ、何も返さずに complete。
2. 該当ユーザがあれば、onSuccess となり complete。
3. Maybe が complete されたあとにユーザー情報が更新されても何もしない。

Single


@Query(“SELECT * FROM Users WHERE id = :userId”)
Single<User> getUserById(String userId);

1. 該当ユーザがなければ、何も返さず onError(EmptyResultException)。
2. 該当ユーザがあれば、onSuccess。
3. Single が complete されたあとにユーザー情報が更新されても何もしない。

Flowable


@Query(“SELECT * FROM Users WHERE id = :userId”)
Flowable<User> getUserById(String userId);

1. 該当ユーザはなければ、何も返さず emit もされない。当然、onNext も onError も呼ばれない。
2. ユーザが存在すれば、onNext。
3. ユーザ情報が更新されるたびに、自動で emit されるので、UI上を最新データに更新させることが可能になる。

 

まとめ

これだけ数行でデータベース、非同期処理を簡潔明快に説明できる Room x RxJava の組み合わせ。

おまけに Observable から細分化された RxJava2.x の主役たちの使い方も理解することができます。

素晴らしいですよね。


「アプリにActivityはひとつでいい」という神のお告げ

左からの NavigationDrawer が初回に起動する Activity にある場合, 気持ち悪いと思ってましたよね.

あのAndroidの神と言われている Jake Warthon さんが言い切ってます.

アプリにActivity一つで複数のFragmentを使う。ただFragmentのバックスタックは使わない。クソなので。

UI周りでいえば Activity起動時のコストを考えてみれば理にかなってるようにも思えます.

確かに, 「Fragment のバックスタック」周りで混乱する様子はだれもが見てきました。

Reddit でも話題になっており, この意見に同意する人も多い雰囲気.

In Droidcon NYC 2017, Jake Wharton says you should use a single-activity for the whole app, and you can use fragments but don't use the fragment backstack because it's bad : androiddev

で, いまどきのストラクチャーでどのような構成にするのか.

Android: the Single Activity, Multiple Fragments pattern | One Activi…

このスライドでは, 画面の数だけ「Presenter + View(Fragment)」のペアを用意する という形の記述となっていますが, Fragmentの特性上これが自然な気がしていますが.


JetBrains社の「Edu Tools Plugin」で Kotlin に入門せよ

Kotlin Edu: Learn and teach Kotlin with easy and professional tools

Learning Kotlin with EduTools plugin - Kotlin Programming Language

Android Studio上でプラグインを検索して追加したあと

再起動ですぐに開始できます.

右の説明を読みながらエディタ上でコードを記述していきます.

チェックボタンを押すと, 記述後のチェックだけでなく, ヒントとなるメッセージが表示されます.

左のプロジェクトウィンドウを上から順番に進んでいくと基本をマスターできます.

できるのかな.

(現在, チャレンジ中・・・)


Java から Kotlin 移行の進捗を「cloc」で確認する

これを確認したかったのです.

特定のディレクトリのファイルやコード行数の確認.

googlesamples/android-architecture at todo-mvp-rxjava

「cloc」てやつを使います.

AlDanial/cloc: cloc counts blank lines, comment lines, and physical lines of source code in many programming languages.

こんな感じで結果表示されます.


for d in ./*/ ; do (cd "$d" && echo "$d" && cloc --vcs git); done

Android プロジェクトの場合, アプリのルートディレクトリでやると,


$ pwd
/Users/maochanz/projects/PlainTemplates
$ cloc .
     674 text files.
     567 unique files.
     140 files ignored.

github.com/AlDanial/cloc v 1.74  T=10.63 s (50.4 files/s, 8239.6 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
JSON                            253              0              0          30758
XML                             223           1749            233          17707
Java                             36             38          24757          10428
Kotlin                           18            245            324            993
Bourne Again Shell                1             19             20            121
DOS Batch                         1             24              2             64
Groovy                            3              8              3             55
Prolog                            1              3              0             18
--------------------------------------------------------------------------------
SUM:                            536           2086          25339          60144
--------------------------------------------------------------------------------

となって,

build ディレクトリもカウントされるので,

src ディレクトリ以下で実行するといいでしょう.


$ pwd
/Users/maochanz/projects/PlainTemplates/app/src
$ cloc .
      77 text files.
      76 unique files.
       1 file ignored.

github.com/AlDanial/cloc v 1.74  T=0.44 s (171.9 files/s, 7442.6 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
XML                             58            177             89           1463
Kotlin                          18            245            324            993
-------------------------------------------------------------------------------
SUM:                            76            422            413           2456
-------------------------------------------------------------------------------

Kotlin による冗長なコードの削減具合もよく分かるはずです.

CLOC -- Count Lines of Code