2020/03/11

by ViewModels() を使って ViewModel を取得する方法

以前は以下のように ViewModelProviders を使用して ViewModel を取得していましたが、この方法は Deprecated になりました。

1
val myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

ドキュメントを見てみると、代わりに kotlin の委譲 by viewModels()、もしくは ViewModelProvider1 を使ってね。と書いてあります。

ところが、by viewModels() はどう使うのかが、何処にも書いてありません2

そこで、ネットの情報とコードを参考に使ってみました。

依存関係

viewModelsfragment-ktx というパッケージ内に入っているので3、これを implementation に加えます。

build.gradle in app
1
2
3
4
dependencies {
    ...
    implementation 'androidx.fragment:fragment-ktx:1.2.2'
}

コンパイラのバージョンを 1.8 互換に

環境によっては以下のように、JVM のターゲットバージョンが合ってない旨のエラーがでてしまいます。

"Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option"

これを解決するには、以下のように build.gradle に追記します。

build.gradle in app
1
2
3
4
5
6
7
8
9
10
11
android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
 
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

この問題、viewModels() に限ったことではなく、新しめの kotlin ライブラリを使おうとすると起こります。

参考

android - Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6 - Stack Overflow

基本の使い方

以下のように使用します。

1
2
3
4
5
6
7
8
9
10
class MainActivity : AppCompatActivity() {
    // lazy なので最初に使用する時に初期化される
    private val myViewModel by viewModels<MyViewModel>()
 
    fun something() {
        myViewModel.users.observe(this, Observer {
            // Do something
        })
    }
}

Factory 指定

Factory を指定した初期化の方法は以下。

1
private val myViewModel  by viewModels<MyViewModel>{ MyViewModelFactory() }

viewModels の実装を覗いてみると、以下のようになっています。 Factory を返す関数を与えられるようになっているわけですね。

1
2
3
4
5
6
7
8
9
10
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }
 
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

ViewModelLazy() も public なクラスなので、これを直接使えば ViewModelStoreOwner (Activity や Fragment 等)を指定できます。 しかし、これは遅延初期化オブジェクト。どのタイミングで呼ばれるかが定かではありません4。 Activity や Fragment 等、ライフサイクルによっては正しく動作しないものを指定すべきではありません。

そういった用途には以下の ViewModelProvider を使うのが良いでしょう。

【参考】ViewModelProvider クラスを使う

ドキュメントには by viewModels() もしくは ViewModelProvider() を使って。とかいてあります。 こちらは、ViewModelProviders.of() とほぼ同様なので、簡単に使えるかと思います。

1
2
// ViewModelProviders.of(this) とほぼ同じように使える
val myViewModel= ViewModelProvider(this).get(MyViewModel::class.java)
  1. 使い方は最後に参照として書きました 
  2. 少なくとも2020年3月10日時点で公式のドキュメントはなさそう 
  3. これも公式には書いてなさそう 
  4. 正確には、呼び出しを全部網羅して確認すれば不可能ではないけど、それだと lazy を使う意味はもうない 
?

0 件のコメント: