visible true

技術的なメモを書く

Kotlin in Action はとてもいいのでとりあえずAndroidエンジニアは全員読むのがいいと思います

献本頂きました。ありがとうございます。

Kotlin in Actionとは

Kotlin in ActionはJetBrains社でKotlinの開発に参加しているDmitry Jemerov氏とSvetlana Isakova氏がJava開発者向けに書いたKotlin1.0の解説書です。

Kotlinが生まれた経緯、Kotlinの言語機能に関する網羅的な解説、高度な利用法の解説がどっさり400Pくらいに渡って書かれています。

原著が2017年2月発売なので、ちょうどKotlin 1.1がリリースされる直前です。ですのでKotlin 1.1以降に関する内容は含まれていません。 1.0と1.1や1.2はそこまで差分はないので、本書で十分Kotlinを学べると思います。

Kotlin in Actionのここがいい

本書のいいところはKotlinのリード設計者のAndrey Breslav氏が寄せた前書きと、序文(これはだれが書いたか明記されてないので不明)に詰まってるなぁと感じます。 以下引用します。

前書き

2010年の春、私が初めてJetBrainsを訪れたときには、世界はもう汎用目的のプログラミング言語を必要としていないだろうということを全く疑っていませんでした。 既存のJVM言語は十分優れていると考えており、それなのに誰かが責任を持って新しい言語を作るというのでしょうか?しかし、1時間ほど大規模なコードベースにおける生産上の課題について議論したあと、私はそうではないと確信しました。そして、のちにKotlinの一部になる最初のアイデアがホワイトボードに書かれました。私はこの言語の設計をリードするため、そしてコンパイラの開発に取り組むために、すぐにJetBrainsに入社しました。・・・

序文

Kotlinのアイデアは、2010年にJetBrainsで考案されました。その頃のJetBrainsは、JavaC#JavaScriptPythonRubyPHPなどの多くの言語に対応した開発ツールのベンダーとして地位を確立していました。「IntelliJ IDEA」(我々の主力製品であるJavaIDE)は、GroovyやScalaプラグインも含んでいます。 このような多種多様な言語のツールを開発した経験は、言語設計の空間の全体の見方とユニークな理解を我々に与えてくれました。とはいえ、IntelliJ IDEAを含むIntelliJプラットフォームベースのIDEは、まだJavaで開発されていました。モダンで強力で素早い進化をする言語であるC#で開発をしている.NETチームの同僚に対して、若干のうらやましさがありました。しかし、我々はJavaの代わりに使用できる言語を知りませんでした。 そのような言語の要件はなんだったでしょうか?まず最初の、そして最もわかりやすい要件は、静的型付けです。発狂せずに長年に渡って数百万行のコードベースを開発するには、これ以外の方法は考えられません。第二に、既存のJavaコードとの完全な互換性が必要でした。JavaのコードベースはJetBrainsにとって非常に貴重な資産であり、これを失うこと、あるいは相互運用性の困難さによって価値を下げることは回避しなければなりません。第三に、道具としての品質の面で、いかなる妥協も受け入れたくありませんでした。開発者の生産性は、JetBrainsという会社にとって最も重要な価値を持っています。そして、優れた道具は高い生産性の達成に不可欠です。最後に学びやすく論理的に考えやすい言語が必要でした。我々の会社にとっていまだに満たされていないニーズがあるならば、それは他社でも同様でしょう。この問題の解決策が、JetBrains以外でも多くのユーザーを満足させるはずです。この点を考慮して、新しい言語であるKotlinの開発プロジェクトに踏み切る決心をしました。・・・

現実の課題を解決するために言語を作っていることが伝わってきます。 Kotlinを気に入った人たちが口にする「簡潔に書けて強力で最高」とか「導入も簡単で相互運用も難しくなくて良い」とか、そもそもそれを狙ってたんやで!という事がわかります。完全に計画通りですね。そんな本書はどこを食べてもおいしく役立ちます。

感想

これからも便利な道具として色んなアイデアを取り込みながら進化していくんだろうな〜と思うと楽しみですね。 1人1冊買おう。読もう。書こう。Have a nice Kotlin!

Android Architecture ComponentsのViewModelとDialogFragment

Android Architecture ComponentsのViewModelとHolderFragmentとActivity-Fragment間通信と。 - visible trueを書いたあと、

ということで試したら行けました。

Source

https://github.com/sys1yagi/aac-viewmodel-with/tree/master/fragment-dialog

MainViewModel

コールバック的な値をLiveDataで定義する。今回はUnitにしてるけどなんでもよさそう。

class MainViewModel : ViewModel() {
    val dialogOk = MutableLiveData<Unit>()
    val dialogCancel = MutableLiveData<Unit>()
}

HelloDialog

DialogでMainViewModelを取り出して対応するアクションの値を更新する。

class HelloDialog : DialogFragment() {
    companion object {
        fun newInstance() = HelloDialog()
    }
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = AlertDialog.Builder(activity)
        val viewModel = ViewModelProviders.of(activity).get(MainViewModel::class.java)
        builder.setMessage("Hello")
                .setPositiveButton("Yes", { _, _ ->
                    viewModel.dialogOk.value = Unit
                })
                .setNegativeButton("Cancel", { _, _ ->
                    viewModel.dialogCancel.value = Unit
                })
        return builder.create()
    }
}

MainActivity

DialogのためのLiveDataはonCreateでobserveしないと、process killレベルのActivity破棄が起こった時にはずれてしまうので注意。

class MainActivity : AppCompatActivity(), LifecycleRegistryOwner {
    override fun getLifecycle() = registry
    val registry = LifecycleRegistry(this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.button).setOnClickListener {
            HelloDialog.newInstance().show(supportFragmentManager, "hello")
        }

        val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        viewModel.dialogOk.observe(this, Observer {
            Toast.makeText(this, "OK", Toast.LENGTH_SHORT).show()
        })
        viewModel.dialogCancel.observe(this, Observer {
            Toast.makeText(this, "Cancel", Toast.LENGTH_SHORT).show()
        })
    }
}

シュッ

f:id:sys1yagi:20170824125211p:plain

雑感

悪くない。

Android Architecture ComponentsのViewModelとHolderFragmentとActivity-Fragment間通信と。

Android Architecture ComponentsのViewModel周りの実装を読んでいくとふーんってなったのでActivity-Fragment間通信やれそうだしやってみたらいけたなーそりゃそうだねみたいな話

Android Architecture ComponentsのViewModelとViewModelProviders

Android Architecture ComponentsのViewModelは次のような抽象クラスである。なーんにもない。

public abstract class ViewModel {
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

もう一つApplicationを安全に保持したAndroidViewModelがある。

public class AndroidViewModel extends ViewModel {
    private Application mApplication;
    public AndroidViewModel(Application application) {
        mApplication = application;
    }
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

ViewModelかAndroidViewModelを継承した上で、ViewModelProvidersを通してインスタンスを作る。

val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

ViewModelProvidersにはFactoryをセットできる。

val viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)

デフォルトではDefaultFactoryが使われる。DefaultFactoryではAndroidViewModelかそれ以外かを判定してインスタンスを作ってる。 立て込んだViewModelを作るときはFactoryを実装することになる。

ViewModelとHolderFragment

抽象クラスであるViewModelはなーんにもしてないからわざわざインスタンスを作るためにViewModelProvidersを通す意味がわからないと思うが、 Configuration ChangeでのActivity再生成に備えてViewModelProvidersはガンバってViewModelの保持機能を備えている。

内部を追っかけるとActivityやFragmentをkeyとしてViewModelを保持するViewModelStoresというクラスが見つかる。

public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(activity.getApplication());
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}

ViewModelStoresはさらにholderFragmentFor関数でHolderFragmentというFragmentを取り出している。

public static ViewModelStore of(FragmentActivity activity) {
  return holderFragmentFor(activity).getViewModelStore();
}

HolderFragmentはふつーのFragmentである。メンバにViewModelStoreを持っている。 で、コンストラクタでsetRetainInstance(true)してる。

public class HolderFragment extends Fragment {
  private ViewModelStore mViewModelStore = new ViewModelStore();
  public HolderFragment() {
    setRetainInstance(true);
  }
  // ...
}

ViewModelStoreはHashMapでViewModelを保持している。

public class ViewModelStore {
  private final HashMap<String, ViewModel> mMap = new HashMap<>();
  // ...
}

ようするにUIなしFragmentじゃねーの

Activity-Fragment間通信

ActivityをkeyにViewModelインスタンスを取り出せるので、Activity-Fragment間で通信ができる。

たとえば2タブで子Fragmentからunread countをもらってタブに出すやつとか。 次のように更新の通知を受けたい値をLiveDataで用意する。

class MainViewModel : ViewModel() {
    val left: MutableLiveData<Int> = MutableLiveData()
    val right: MutableLiveData<Int> = MutableLiveData()
}

で、こういう感じでobserveしておいて、

// MainActivity
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.left.observe(this, Observer {
  tab.count.text = it.toString()
})

Fragment側で取り出して、更新すると

// Fragment
val viewModel = ViewModelProviders.of(activity).get(MainViewModel::class.java)
viewModel.left.value = 10

シュッ

f:id:sys1yagi:20170822221233p:plain

Source Code

詳細はソースを見てください。

GitHub - sys1yagi/aac-viewmodel-with

雑感

へーって思った

Androidのアーキテクチャ本をクラウドファンディングで執筆します - 共著者3人の紹介 #peaks_cc

peaks.cc Android アプリ設計パターン入門

ある日

f:id:sys1yagi:20170703130312p:plain

こんなこと書きます

個人的な思想としてアーキテクチャはチームのためにあると思っています。なのでそういう感じの章を書きます。章の紹介文を引用しておきます。

アーキテクチャはチームのために存在しています。チームとは人です。すなわち人がプロダクトを正しく作るためにアーキテクチャは存在しています。人を支えないアーキテクチャは意味がありません。Androidが出た当初は、限られたリソースの中でうまく動作させるために、組み込みアプリケーションの方法論が重要でした。 時代は進み、いまやAndroid端末のスペックは一昔前のPCと遜色がありません。提供する機能は複雑化し、画面も増加し、品質の要求は上がり、開発規模が増大して関係者も増えました。今こそアーキテクチャの出番がやってきたというわけです。 本章では中長期的にチームでAndroidアプリケーションを開発するにあたって直面した課題と、解決するために考えたこと、実際に行ったことを紹介します。取り扱うソースコードGitHubで公開しているMastodonAndroidクライアントアプリケーション「DroiDon」です。DroiDonは個人で開発を進めていますが、これまでのチームでの経験をすべて詰め込んで設計し、VIPERアーキテクチャを採用しています。これに近いアーキテクチャを著者の所属するトクバイのアプリでも採用してます。アーキテクチャ設計の議論のたたき台として活用できることでしょう。

皆さん

共著者を紹介します。皆さんマジはんぱねー人たちです。

日高 正博さん

  • 第1章 Androidアプリの基本構成
  • 第2章 MVPパターンを使ったアプリ構成
  • 第3章 MVVMパターンを使ったアプリ構成
  • 第4章 差分開発にみる設計アプローチ

ひつじさん。夏コミとかで2,30人技術者を集めて400P-500P(100Px5冊)書いたりするテクブの主催者。DroidKaigiとか技術書典とかの発起人でもある。peaks自体のアイデアにも確か関与していたような?とにかく彼が書くと言ったら書くんです。

今回は本書の方向づけをする序盤を担当しています。わかる!ドメイン駆動設計 ~もちこちゃんの大冒険~【C91新刊】などを見てもわかる通り、何をどのようにどの順番で伝えるべきかについての分解力は半端ないのですごくいい感じになるのではと今から期待しています。

共著者としては、原稿のビルド環境や、校正、編集作業にも精通しているので、安心して初稿をぶつけられます。

小西 裕介さん

  • 第5章 OSSにおける設計者の役割

こにふぁーさん。DroidKaigiアプリを始めた人!ブログでは技術的な話のほかにチーム課題とか技術者としてのふるまいについてなどもアウトプットしていらっしゃいます。さらにandroid-material-design-icon-generator-pluginなど1k超えのプラグインを公開してたりして色々すごい方です。

本書ではDroidKaigiアプリの設計の「なぜ」について解説します。OSSでは不特定多数の人が参加します。この時に気をつけるべきことは何か?暗黙知をいかに減らすかとか妥協点とか色々あるんじゃないかと思います。小西さんが当時何を考えていたか!めっちゃ気になります。

藤原 聖さん

ふじわらさん。普段はそこまで接点はなくて、DroidKaigi 2017辺りからちょくちょく絡んでいます。 実は同い年だという事がわかったんですが、貫禄が違う。落ち着きとでもいうんだろうか。見習いたい。

サイバーエージェントAndroid開発といえばfluxをAndroidに取り入れている事でわりと有名ではないかと思います。 QiitaでもRxJava + Flux (+ Kotlin)によるAndroidアプリ設計 - Qiitaとかを書かれてますね。 fluxのAndroidでの利用周りは「ふーん」くらいしか理解していなかったのがガッツリ解説めっちゃ楽しみです。

まとめ

なにこれめっちゃ読みたいんやけど。

Clean Architectureを理解するための補助的なコンポーネント図のようなもの

Clean Architectureを雰囲気でしか理解していなかったんだけど、なんでだろうな〜って考えるとあの図とか説明文がややこしいからだな〜と思った。 抽象的なやつはええねん、具体をくれ具体を〜、と思ったので、Android-CleanArchitectureのサンプルコードをコンポーネント図のようなものにおこした。

Android-CleanArchitecture Sample Code

実際にサンプルのソースを眺めると、レイヤの接続のための実装やAndroid固有(NavigatorなどIntentを処理するようなやつ)の実装などが混ざっていて、概念図とソースだけでは結構分かりづらい。しかもFragmentでActivityをキャストして使ってたり、UserCaseのコールバックをPresenterのインナークラスのObserverでやってたり、DataSourceは呼び出し毎に実装をnewしてたりわりとトリッキーだったり雑だったりするのでノイズが多い。

という事で概念が伝えている要素だけを抽出して関係だけを書いたコンポーネント図のようなもの*1を描いた。

f:id:sys1yagi:20170624213749p:plain

登場人物が少ないのでちょっとレイヤー化っていうイメージはつかみにくいなぁとは思うがこれはこれで理解の助けにはなるんじゃないかなと。

もうちょっと機能を足してみる

層感がないんでちょっと適当に機能を足してみた。新たな要素としてViewModelが加わっている。これは単純にViewのデータ群を管理するための要素として書いている。

f:id:sys1yagi:20170624213755p:plain

こういう図がいいのは実装を考えずにこうして機能を追加できたりする点だな〜とか思った。UML様〜。

それで

こういうのは各レイヤの責務が相互に漏れ出さないってのが重要なので、何かを足す時に気をつけないといけない。 たいていの場合Data層は固まるのが早い。初期のAPIセットを実装したら一旦終わるし、追加変更は大体同じようなことを繰り返すだけだからだ。 で、次にPresentation層。固まるっていうか表示要素とデータ、発生するイベントが決まれば大体OKだからどっちかっていうとレイアウトXMLの実装のほうが大変なくらいだ。 一番むずかしいのがDomain層で、ここが一番変化すると思う。特定のPresentation層に特化しすぎてたり、汎用的にしたけど結局共有できないから分離したり、試行錯誤が発生する。

まぁとりあえず概念の理解の助けになれば〜〜。個人的にはVIPERが好きだ。

*1:これって実際は何図って言うんだろう?

もうAndroidの非同期処理はasync/awaitでいいんじゃないかなぁと思った

Rx Ja Night Vol.2 - connpassで「 Androidの非同期処理をKotlinコルーチンで行う」という話をしてきました。

スライドで使っているコードは次のリポジトリに置いています。

github.com

今回取り扱った非同期処理の範囲

スライドやリポジトリのREADME.mdに大体書いているのですがコチラにも載せときます。 詳細な説明はスライドやリポジトリを参照してください。 次の非同期処理をコルーチンで実現します。

単発の実行
直列の実行
並列の実行
+
エラーハンドリング
キャンセル

環境

すべてKotlinが提供する標準の機能を用います。

implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.2-4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.16"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.16"

単発の実行

コルーチン(async/await)ではこういう風に書けます。

val job = launch(UI) {
    try {
        val shop = async(CommonPool) { shopApi.getShop(10) }.await()
        // success
    } catch (e: Exception) {
        // error
    }
}

launch(UI)async(CommonPool)はコルーチンのビルダー関数と呼び、続いて渡すブロックをコルーチンにしてくれます。 ここではlaunch(UI)はコルーチン内をUIスレッドで実行する、async(CommonPool)はコルーチン内をスレッドプールで実行する、という理解でいいと思います。 毎度書くと冗長なので、次のようなパッケージレベル関数を用意して簡潔に書けるようにしたりできます。

fun <T> async(context: CoroutineContext = CommonPool, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T)
        = kotlinx.coroutines.experimental.async(context, start, block)

fun ui(start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit)
        = launch(UI, start, block)

スッキリ

val job = ui {
    try {
        val shop = async { shopApi.getShop(10) }.await()
        // success
    } catch (e: Exception) {
        // error
    }
}

エラーについてもtry-catch式なので、様々な例外をよしなに扱えます。 uiの中でawait()して待ち合わせていて大丈夫かって気になりますが、 Kotlinコンパイラがよしなにコルーチン内のコードを状態毎に分解していて、 await()の呼び出しの所で処理を中断しているので大丈夫です。 await()が完了すると結果がdispatchされ、Handler.post()で続きを実行するといった具合です。

直列の実行

await()を後続のasyncの前か、その中で呼び出せばOK。

val job = ui {
    try {
        val userJob = async { userApi.me() }
        val subscriptionShopsJob = async { subscriptionShopApi.getSubscriptionShops(userJob.await().id) }
        val subscriptionShops = subscriptionShopsJob.await()
        // success
    } catch (e: Exception) {
        // error
    }
}

並列の実行

並列で実行したいasyncを呼び出したあとにawaitすればよい。

val job = ui {
    try {
        val userJob = async { userApi.me() } // start immediately
        val shopJob = async { shopApi.getShop(10L) } // start immediately
        val user = userJob.await()
        val shop = shopJob.await()
        // success
    } catch (e: Exception) {
        // error
    }
}

キャンセル

launch(UI)から返るJobcancel()を呼び出せばよい。

job.cancel()

するとコルーチンの中でCancellationExceptionが飛ぶ。

job = ui {
    try {
        val shop = async { shopApi.getShop(10) }.await()
        // success
    } catch(e: CancellationException) {
        // cancel
    } catch (e: Exception) {
        // error
    }
}

感想

特にデメリットもなさそうだし圧倒的に簡潔だし、もう非同期処理はasync/awaitでいいやって思った。 なんてったって非同期処理のための実装だからね。 このほかproducerやactorなどを使うと更にUIイベントのストリームとかイベントバスみたいなこともできそうだけど、 この辺はそのために用意されているかでいうと微妙な気もするのでどちらでもいい気もしている。 一応experimentalなんだけど、裏側の実装が変わる事はあっても利用コード側はそこまで影響受けないだろうしいいんじゃないかなとか。 プロダクトでも入れ始めているしそうしようみたいな気持ち。

参考

これ読んだらもう使ってよしだと思う。

KotlinでViewDataBindingをシュッとinflateするやつ

RecyclerViewなどでViewDataBindingを使う時に次のように書くのめんどくさくて。

class ViewHolder(val binding:ListItemCommentBinding)
: RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, type: Int) = 
  ViewHolder(ListItemCommentBinding
    .inflate(LayoutInflater.from(parent.context), parent, false))

こういう感じにViewGroupに関数生やすと、

ViewExtensions.kt

inline fun <reified T : ViewDataBinding> 
  ViewGroup.inflateBinding(): T {
    return T::class.java
            .getDeclaredMethod(
              "inflate",
              LayoutInflater::class.java, 
              ViewGroup::class.java, 
              Boolean::class.javaPrimitiveType
            )
            .invoke(null, LayoutInflater.from(context), this, false) as T
}

良さそう。

override fun onCreateViewHolder(parent: ViewGroup, type: Int) =
  ViewHolder(parent.inflateBinding()) 

ViewHolderが複数種類のViewDataBindingを取り扱う場合は型引数が要る。

override fun onCreateViewHolder(parent: ViewGroup, type: Int) =
  ViewHolder(parent.inflateBinding<ListItemCommentBinding>()) 

追記 2017/05/23

proguardで死ぬので、使うときは次の設定が必要になります。ひょえ〜

-keep class * extends android.databinding.ViewDataBinding {
    public static ** inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
}