日々是好日

プログラミングについてのあれこれ、ムダ知識など

MVVM 完全に理解した - 9

前回は、Activity 1枚と Fragment 1個の構成における Data Binding の基本的な構造について確認しました。

所感において「次は Navigator インターフェースを定義して、テキストのクリア機能と大文字化、小文字化機能を追加してみようと思います。」と書いていましたが、BluePrints を読み返したところ重大な勘違いをしていました。

Navigator インターフェースは、移動先の Activity の生成(startActivityForResult)や Activity を閉じる(finish)メソッド、すなわち Activity で行わなければならない処理を実装するためのインターフェースでした。

そのため、今回は Navigator インターフェースを MainActivity に実装し、ViewModel を介してサブ画面を呼び出す実装を行います。

動作

MainActivity に配置された Floating Action Button (FAB) を押すとサブ画面が表示される

ViewModel とのやりとりを学ぶためのテスト実装なので、サブ画面に表示する項目は特になし(´・ω・`)

Navigator インターフェース

実装するメソッドは次のとおり。

interface MainNavigator {
    fun onStartNewActivity()
}

単純に新しい画面を生成し表示するだけとします。Navigator インターフェースは MainActivity に実装します。
また、ついでに MainViewModel で MainNavigator への参照を保持するようにします。ここの部分でユーザの操作と Activity との橋渡しを行うようにします。

class MainActivity : AppCompatActivity(), MainNavigator {

    override fun onCreate( ... ) {
        //ViewModel に Navigator(実体はMainActivity)の参照を保持する
        viewmodel.setNavigator(this)
    }

    //FABへの ClickListener 登録は MainFragment にて行う!!
    ...
    override fun onStartNewActivity(){
        //サブ画面の生成と表示を行う
        startActivityForResult( ... )
    }
}

class MainViewModel : ViewModel() {
    private lateinit var _navigator: MainNavigator

    //MainNavigatorの参照を保持
    fun setNavigator(navigator: MainNavigator) {
        this._navigator = navigator
    }

    fun startNewActivity() {
        //MainNavigatorに処理を委譲
        _navigator.onStartNewActivity()
    }
}

MainActivityにてActivity#startActivityForResultをコールし、新規 Activity を生成するようにします。
さて、コメントにて「FABへの ClickListener 登録は MainFragment にて行う」と書きましたが、Activity#onCreateではsetOnClickListener実行せずFragment#onCreateViewにて FAB に Listener を登録します。

「動作」の項でちらっと書いたように、FAB は MainActivity のレイアウトファイルに配置されています。
その気になればActivity#onCreateで ClickListener を登録することは可能です。
しかし MVVM を取り入れる場合、次のような考え方が必要になります。

アクティビティでオブジェクトの生存期間(ライフサイクル)を管理し、フラグメントで画面(UIコンポーネント)に対するアクションやイベントの組み立てを管理します。このような役割分担は慣れるまで複雑に見えますがアプリの秩序を保つ上で重要な意味を持っています。

Peaks - Androidアプリ設計パターン入門

この原則に従うと、Fragment 側に次のように実装されます(setupFabメソッド)。

class MainFragment : Fragment() {
    override fun onCreateView (...) : View? {
        ...
        setupFab()
        ...
    }

    
    private fun setupFab() {
        val fab = (activity as MainActivity).findViewById(R.id.fab)

        fab.setOnClickListener {
            //ViewModelを介してonStartNewActivityを呼ぶ
            viewmodel.startNewActivity()
        }
    }
}

setupFabにて Listener をセットし、viewmodel.startNewActivityメソッドをコールするようにします。

超簡単なフロー

次のような感じ。

FAB をクリック

viewmodel.startNewActivity() のコール

viewmodel にて、_navigator.onStartNewActivity() のコール

MainActivity で実装した onStartNewActivity を読む

サブ画面が表示される

ViewModel で View のイベントを受け、インターフェースを介して Activity で行うべき処理を実行している形です。

所感

期せずして MVVM っぽい実装が出てきました。個人的推しポイント。

  • UI のアクションやイベントは Fragment で管理し、ViewModel で仲介(橋渡し)をする

次はいい加減 Model とのやりとりを説明したい。依存性の注入(DI : Dependency Injection)とか少しやっていきたい。