6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android ViewModelにおける文字リソースの扱い方に関する提案

Last updated at Posted at 2019-01-09

ViewModelにおける文字リソースの扱いと改めて向き合ってみる中で、幾つかネット上で選択肢が見つかったものの、どれもしっくりこなかったので自分なりに解決策を考えてみました。

見つけた方法たち

AndroidViewModelを使う

class MainViewModel(application: Application) : AndroidViewModel(application) {
    val text: LiveData<String> = Transformations.map(this.name) { name ->
        this.getApplication<Application>().getString(R.string.hello, name)
    }
}

今まではこの方法でやっていました。
しかしこの方法だと、application contextなので、例えばユーザーが言語設定を変更したとき、再起動するまで反映されません[1]
そういうものだと思えばそれでも良いかもしれませんが、できればactivity contextを適用してあげたい。

StringProvider

class StringProvider(val context: Context) {
    fun getString(resId: Int, vararg formatArgs: Any) = this.context.getString(resId, formatArgs)
}

class MainViewModel(stringProvider: StringProvider) : ViewModel() {
    val text: LiveData<String> = Transformations.map(this.name) { name ->
        this.getApplication<Application>().getString(R.string.hello, name)
    }
}

この方法を採用している人も見かけました[2]
ViewModelの直接的なcontext依存がなくなり、テストの書きやすさは向上しそうですが、StringProviderに対してactivity contextで依存解決するのは少々手間がかかります。Application contextを適用すれば、「AndroidViewModelを使う」と同様の問題が生じます。

XMLで変換する

android:text="@{@string/hello(viewModel.name)}"

ViewModelではgetStringの変数(ここではname)だけを持たせておき、XML側で文字リソース指定するやり方もあります[3]
これなら、「AndroidViewModelを使う」のapplication context問題は回避できます。
ただ、条件によって文字リソースを切り替えたい場合、この方法では分岐をViewModel内で完結させられません。

提案

StringProvider」の流れを反転し、「XMLで変換する」を掛け合わせるイメージです。

internal interface StringResource {
    fun apply(context: Context): String

    companion object {
        fun from(@StringRes resId: Int, vararg formatArgs: Any) = object : StringResource {
            override fun apply(context: Context) = context.getString(resId, formatArgs)
        }
    }
}

class MainViewModel : ViewModel() {
    val text: LiveData<StringResource> = Transformations.map(this.name) { name ->
        StringResource.from(R.string.hello, name)
    }
}
android:text="@{viewModel.text.apply(context)}"

StringResource生成時にresIdと変数を渡しておき、xmlでcontextを適用しています。
改善余地はありそうですが、雰囲気良さげなんじゃないかと。

参考URL

  1. https://www.reddit.com/r/androiddev/comments/7rinro/android_arch_mvvm_where_to_formattransform/dsxmycm
  2. https://www.reddit.com/r/androiddev/comments/7rinro/android_arch_mvvm_where_to_formattransform/dsx57uo
  3. https://github.com/googlesamples/android-sunflower/issues/132#issuecomment-409508336
6
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?