StateFlowとDatabindingを試してみる

先日、Androud StudioのCanaryリリースでStateFlowがDataBindingに対応したので試してみました。

androidstudio.googleblog.com

今回確認したのは以下のみになります

  • StateFlowを使ってDataBindingが使えることを確認
  • MutableStateFlowを使って双方向DataBindingができることを確認

実際のソース

今回はViewModelを以下のように定義しました。

class MainViewModel : ViewModel() {
    val editTextFirst = MutableStateFlow("")
    val editTextSecond = MutableStateFlow("")
    private val first = editTextFirst.map { it.toFloatOrNull() }
    private val second = editTextSecond.map { it.toFloatOrNull() }
    private val result = first.combine(second) { a, b ->
        a ?: return@combine null
        b ?: return@combine null
        a * b
    }
    private val _text = MutableStateFlow("")
    val text = _text.asStateFlow()

    init {
        viewModelScope.launch {
            result.collect {
                _text.value = it.toString()
            }
        }
    }
}

(result: Flow から text: StateFlow に変換するまでのところをもうすこしうまく書きたい。。。

あとは、LiveDataを使うときと同じようにレイアウトファイルに設定してあげます。

        <TextView
            android:id="@+id/message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{viewModel.text}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/edit_text_first"/>

        <EditText
            android:id="@+id/edit_text_first"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:inputType="numberDecimal"
            android:text="@={viewModel.editTextFirst}"
            app:layout_constraintTop_toBottomOf="@id/message"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/edit_text_second"/>

        <EditText
            android:id="@+id/edit_text_second"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:inputType="numberDecimal"
            android:text="@={viewModel.editTextSecond}"
            app:layout_constraintTop_toBottomOf="@id/message"
            app:layout_constraintStart_toEndOf="@id/edit_text_first"
            app:layout_constraintEnd_toEndOf="parent"/>

これによって、LiveDataと同じようにDataBindingできることが確認できました。

AndroidのNavigationについて調べてみた。

きっかけ

特定の状況下で、画面が遷移するような実装をしていた。そのときに、以下のようなエラーになったので、それについて調べてみた。

navigation destination com.github.ymatoi.navigationsample:id/action_firstFragment_to_secondFragment is unknown to this NavController

状況と解決策

f:id:YMatoi:20190610222152p:plain

このような状況を navigation で定義している。

自動的に画面を遷移させるために、FirstFragmentのonViewCreatedに対して、以下のようなコードを書いている。 すると、起動時には問題なく、画面遷移をして、SecondFragmentに移動するが、戻るボタンを押すとクラッシュしてしまう

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 super.onViewCreated(view, savedInstanceState)

 findNavController().navigate(R.id.action_firstFragment_to_secondFragment)
}

これを解決するには、navigationの遷移部分を以下のようにしてあげればよい。こうすることで、戻るボタンを押しても、瞬時にsecondFragmentに遷移するようになる

 findNavController().navigate(R.id.secondFragment)

調べ中

確証は持てていないが

FirstFragment の onViewCreated で、findNavController().currentDestinationの値を出力すると、戻るボタンを押したあとでは、SecondFragmentのidを出力するので、 R.id.action_firstFragment_to_secondFragmentが動くのはcurrentDestinationがFirstFragmentのidのときのみだと推測している。

R.id.secondFragmentのように直接Fragmentの指定をしてあげる場合は、currentDestinationに依存せずに、遷移できるようです。

上の説、あまり自身がないので、くわしいかたどなたかぁ。。

あと、そうすると、currentDestinationはいつ変わるのかって話題もきになってる。

追記

たとえば、SecondFragmentでfindNavController().popBackStack()で戻ると、クラッシュしないみたいです。 そのときは、findNavController().currentDestination()が期待値なので、BackButtonを押したときのみクラッシュするみたいですね。

AndroidのNavigationのnavArgsが便利そう。

Android + kotlin

最近、仕事で使っている Navigation Architecture Componentのバージョンを 1.0.0-alpha07から一気に2.0.0まで上げました。 そのときに、リリースノートを見ていたときに by navArgs() というものを知ったのでメモ

developer.android.com

これによって、例えば以下のように書いていたものが

val args = HogeFragmentArgs.fromBundle(arguments!!)

のようにfromBundleを噛ませて書いていたのが、Fragmentのクラスのメンバ変数として

private val args: HogeFragment by navArgs()

のようにかけることを知りました。

これを使うためには1.0.0-alpha10以上の-ktxのnavigation architecture componentが必要になります。

これによってメンバ変数として定義し、初期化を使用時に遅らせることができます。

従来でもメンバ変数として定義し、初期化を遅らせることはlateinitを使えばできたのですが、それがより簡単にできるようになりました。

蛇足

さてさて、navArgs()とはなんじゃろなとなったので、Android Studioで実装に飛ぶと

  • FragmentNavArgsLazy.kt
@MainThread
inline fun <reified Args : NavArgs> Fragment.navArgs() = NavArgsLazy(Args::class) {
    arguments ?: throw IllegalStateException("Fragment $this has null arguments")
}

のような実装がされています。ここはkotlinの拡張関数を使って、NavArgsLazyを呼び出しているので、更に実装を見てみると

  • NavArgsLazy.kt
class NavArgsLazy<Args : NavArgs>(
    private val navArgsClass: KClass<Args>,
    private val argumentProducer: () -> Bundle
) : Lazy<Args> {
    private var cached: Args? = null

    override val value: Args
        get() {
            var args = cached
            if (args == null) {
                val arguments = argumentProducer()
                val method: Method = methodMap[navArgsClass]
                    ?: navArgsClass.java.getMethod("fromBundle", *methodSignature).also { method ->
                        // Save a reference to the method
                        methodMap[navArgsClass] = method
                    }

                @Suppress("UNCHECKED_CAST")
                args = method.invoke(null, arguments) as Args
                cached = args
            }
            return args
        }

    override fun isInitialized() = cached != null
}

のような記述が見えることになります。

このクラスが評価されるとget()が呼び出され、キャッシュを持っている場合はキャッシュを返し、 初回呼び出し時には、argumentProducerを使ってFragmentのargumentsが渡されているので、argumentsを型変数Argsに変換して返されることになるようです。

#

  • リリースノート定期的に見よう。
  • これ調べてたらbyが何やってるのか気になったので動作を調べてみたい
  • バージョンを上げたらargumentsがNullableを返すようになっていてfromBundleがNonNullを受け取るので変更が面倒だった。。
  • 上のソースコードのリンクのURLを貼りたいがどこかよくわかっておらず。。(汗)

気づいたら転職して一年が経過しそう

履歴

2015/04/01 From: 機械系大学院 To: O1

2018/04/01 From: O1社 To: 無職

2018/05/01 From: 無職 To: O2

大学院

ロボットやってた

O1

インフラ設計や構築や開発とたまにバックエンドやフロントの開発やってた

無職

就職活動とHorizon Zero Dawn、龍が如く

O2

Androidアプリ開発とたまにweb、インフラ障害対応

最後に

だいぶ久しぶりにはてなにログインしたら、ブログだけ作って放置していることを見つけた。 過去の自分が調べたこと、知ったこと等、細かいことはすぐ忘れてしまうので雑多に記録に残していきたい。 未来の自分が振り返るときに役に立つことを願って。

このブログタイトルをどういう気持ちでつけたのかを忘れてしまった (元ネタはクロスチャンネルだとは覚えている。

未来の自分は他人なのでコメントを残すべきところだ