はじめに
今回は ViewModel
, LiveData
, DataBinding
について書いていきます。もうこの3つは常にセットで使うと言っても過言ではないと思います。(。。。たぶん)色々サイト見てみましたが小難しいのでとりあえず使えるようになるのを目標にして詳細はもっと成熟してから追記したいと思います。
Android のデファクトスタンダードは MVVM っぽいのでほぼ全てのアプリで利用すると思っていいはず。
色々見ても混乱するだけなので今回は主に下記の動画を参考にしています。なのでとりあえず ViewModel
使いたければ下記の動画見ればいいと思います!
ゆるっとAndroid ViewModel開発の解説ライブコーディング
GitHubにサンプルつくったのでよかったらどうぞ↓↓↓
GitHub のサンプル
ViewModel
MVVM における ViewModel だと思って問題ないはず。Activity
に直接 String
などで値を持っている場合は画面回転時など onSaveInstanceState
で値を保持してあげないといけないが ViewModel
を利用すればそういったことは気にする必要がなくいい感じに値を保持してくれる。
ViewModel
の詳細は下記をどうぞ
LiveData
ライフサイクルに応じたデータ監視ができるやつ。ViewModel
の値を View が監視するために使う。
LiveData
の詳細は下記をどうぞ
DataBinding
XML の View と ViewModel
をバインディングするためのやつ。各種設定をしているとビルド時に 〜Binding というクラスを自動生成してくれる。
DataBinding
の詳細は下記をどうぞ
使い方
1.build.gradleの修正
build.gradle に DataBinding
用に下記を追記する
1 2 3 4 5 6 7 8 9 10 |
apply plugin: 'kotlin-kapt' android { . . . dataBinding { enabled true } } |
2.Fragmentの作成
New -> Fragment -> Fragment (with ViewModel) で Fragment を追加する (MainFragmeng.kt)
build.gradle に下記が追加されているはず
1 2 3 |
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' |
3.Bindingクラスの作成
Fragment の XML のルートを <layout></layout>
に変更して下記を追加する
1 2 3 |
<data> <variable name="viewModel" type="com.example.viewmodelsample.MainViewModel"/> </data> |
これでビルドすれば MainFragmentBinding
クラスができているはず
4.Fragmentの修正
Fragment を下記のように修正する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class MainFragment : Fragment() { companion object { fun newInstance() = MainFragment() } private lateinit var viewModel: MainViewModel private lateinit var dataBinding: MainFragmentBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) dataBinding = MainFragmentBinding.inflate(inflater, container, false).also { it.lifecycleOwner = this.viewLifecycleOwner it.viewModel = this.viewModel } return dataBinding.root } } |
5.ViewModelの修正
ViewModel を下記のように修正する
1 2 3 4 5 6 7 8 |
class MainViewModel : ViewModel() { var message: MutableLiveData<String> = MutableLiveData("ABC") fun reverseMessage() { message.value?.let { message.postValue(it.reversed()) } } } |
6.FragmentのXMLの修正
Fragment の XML を下記のように修正する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainFragment"> <data> <variable name="viewModel" type="com.example.viewmodelsample.MainViewModel"/> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="@{viewModel.message}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="hello, world"/> <Button android:id="@+id/button_reverse" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:text="Reverse" android:onClick="@{() -> viewModel.reverseMessage()}" app:layout_constraintBottom_toTopOf="@+id/button_next" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/button_next" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:text="Next" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> |
7.値の監視
Fragment に下記のように書けば message
を監視できます
1 2 3 4 5 |
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.message.observe(viewLifecycleOwner, Observer { Log.d("TAG", it) }) } |
これで簡単な使い方は完璧なはず!
ViewModel の MutableLiveData
外部の公開する必要がないので下記のように MutableLiveData
は private にして LiveData
を公開する方がよさそう(ReactiveSwift とかと同じ感じ)
1 2 3 |
private var mutableMessage: MutableLiveData<String> = MutableLiveData("ABC") val message: LiveData<String> get() = mutableMessage |
mutableMessage
じゃなくて _message
とする方が一般的かもしれないがわからない。。。
LiveDataをイベントで利用する場合の注意
参考動画でも言及されていますがボタンのクリックイベントなどで LiveData を利用して Observe する場合は注意が必要なようです。
ViewModel を参照すると Observe してたら毎回値が流されるらしい。。。動画のように Navigation で Fragment 切り替えをやるとバックキーで戻るとまた値が流れてしまうらしい。詳しくは動画でどうぞ。(画面回転時とかも値が流れてしまうはず)
対策としては google/iosched/Event.kt を使う方法があるみたいです。
Event.kt をプロジェクトに追加して下記のように Event
を使うようにします。
1 2 3 4 5 6 7 8 9 10 11 12 |
// ViewModel側 private var mutableNavigateNextAction = MutableLiveData<Event<Unit>>() val navigateNextAction: LiveData<Event<Unit>> get() = mutableNavigateNextAction fun onClickNextButton() { mutableNavigateNextAction.postValue(Event(Unit)) } // Fragment側 viewModel.navigateNextAction.observe(viewLifecycleOwner, EventObserver { Log.d("TAG", "Next") }) |
これで値に変更があった場合にしか post されないようです。
ViewModelProvidersが非推奨
どうやら ViewModelProviders
が非推奨のようなので ViewModel
の生成部分を修正する必要があります。
1 |
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) |
単純なのはこう
1 |
viewModel = ViewModelProvider(this).get(MainViewModel::class.java) |
[Android]lifecycleライブラリ2.2.0からViewModelProviders.ofが非推奨になっちゃった件を参考にすると
build.gradle に下記を追加して
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
android { . . . // Keep the following configuration in order to target Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } } implementation "androidx.fragment:fragment-ktx:1.2.5" |
下記のようにする方法もあるようです。
1 |
private val viewModel: MainViewModel by viewModels() |
どっちがいいのかはちょっとわからないです。(どう違うのかもよくわからないですが val にできる後者の方が個人的に好きです)
おわりに
バインディングが簡単にできるので MVVM 構成がさくっとできるのはいいですね!ViewModel 使えばめんどくさい画面回転とかのライフサイクル関連もあまり気にしなくてよさそうなので積極的に使っていきたいですね。
ViewModel と Navigation を使えばわりと iOS と同じ感覚で実装できそうな気もします。
とりあえず ViewModel
使いたければ下記の動画見ればいいと思います!
ゆるっとAndroid ViewModel開発の解説ライブコーディング
参考
- ゆるっとAndroid ViewModel開発の解説ライブコーディング
- Androidデベロッパー:ViewModel の概要
- Androidデベロッパー:LiveData の概要
- Androidデベロッパー:ビュー バインディング
- Androidデベロッパー:データ バインディング ライブラリ
- Androidデベロッパー:ViewModel
- Androidデベロッパー:LiveData
- [Android]lifecycleライブラリ2.2.0からViewModelProviders.ofが非推奨になっちゃった件
- archのViewModelProvider(s)から卒業するには
コメント