Slide 1

Slide 1 text

再考: 監視可能オブジェクト YUMEMI.grow Mobile #12 mikan( 一瀬喜弘)

Slide 2

Slide 2 text

自己紹介 object Mikan { val name = " 一瀬喜弘" val company = "karabiner.tech" val work = Engineer.Android val hobby = listOf( " 漫画", " アニメ", " ゲーム", " 折り紙", "OSS 開発・コントリビュート", ) }

Slide 3

Slide 3 text

Android アプリのGUI アーキテク チャはなにを使っていますか?

Slide 4

Slide 4 text

きっとMVVM と答える方が多い んじゃないでしょうか

Slide 5

Slide 5 text

MVVM をMVVM たらしめてるも のってなんでしょうか?

Slide 6

Slide 6 text

そうです。V とVM 間のデータの 同期ですよね

Slide 7

Slide 7 text

ではデータの同期にはどのよう な技術が使われているでしょ か?

Slide 8

Slide 8 text

そう。監視可能オブジェクトで すよね

Slide 9

Slide 9 text

(Observable) / LiveData / StateFlow / State

Slide 10

Slide 10 text

どれをつかったらいいの?

Slide 11

Slide 11 text

この​ 発表では​ LiveData / StateFlow / State の​ 比較を​ します

Slide 12

Slide 12 text

インスタンスの作り方

Slide 13

Slide 13 text

// LiveData data class MainUiState(val name: String) class MainViewModel : ViewModel() { // バッキングプロパティによってイミュータブルな方を外部に公開する private val _uiModel = MutableLiveData() val uiModel: LiveData = _uiModel }

Slide 14

Slide 14 text

// StateFlow data class MainUiState(val name: String) class MainViewModel : ViewModel() { // バッキングプロパティによってイミュータブルな方を外部に公開する private val _uiModel = MutableStateFlow(MainUiModel("")) // 初期値が必要 val uiModel = _uiModel.asStateFlow() // 型の明示が不要になる }

Slide 15

Slide 15 text

// State data class MainUiState(val name: String) class MainViewModel : ViewModel() { // バッキングプロパティしなくてよい var uiModel by mutableStateOf(MainUiModel("")) private set }

Slide 16

Slide 16 text

更新の仕方

Slide 17

Slide 17 text

// LiveData class MainViewModel : ViewModel() { // ... fun updateName(newName) { // メインスレッドで実行する場合 _uiModel.value = MainUiMode(newName) // バックグラウンドスレッドで実行する場合 _uiModel.postValue(MainUiMode(newName)) } }

Slide 18

Slide 18 text

// StateFlow class MainViewModel : ViewModel() { // ... fun updateName(newName) { // 値を評価 _uiModel.value = _uiModel.value.copy(newName) // 式を評価 viewModelScope.launch { _uiModel.update { it.copy(newName) } } } }

Slide 19

Slide 19 text

// State class MainViewModel : ViewModel() { // ... fun updateName(newName) { uiModel = uiModel.copy(newName) } }

Slide 20

Slide 20 text

監視の仕方 from Activity

Slide 21

Slide 21 text

// LiveData class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { viewModel.uiState.observe(this) { uiState -> // 更新された uiState を使って何かをする } } }

Slide 22

Slide 22 text

// StateFlow class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { viewModel.uiModel.collect { uiModel -> // 更新された uiModel を使って何かをする } } } }

Slide 23

Slide 23 text

// State // Snapshot を使えばいけるけど。 。わざわざ実装する?

Slide 24

Slide 24 text

監視の仕方 from Android View

Slide 25

Slide 25 text

Slide 26

Slide 26 text

android:text="@{viewModel.uiModel.name}" android:onClick="@{() -> viewModel.updateName(name)}"

Slide 27

Slide 27 text

// LiveData // StateFlow class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { binding = DataBindingUtil.setContentView( this, R.layout.activity_main ) binding.lifecycleOwner = this binding.viewmodel = viewModel } }

Slide 28

Slide 28 text

// State // おそらくSnapshot システムを介して監視できるのかもしれない。 。

Slide 29

Slide 29 text

監視の仕方 from Compose

Slide 30

Slide 30 text

// LiveData @Composable fun MainScreen( viewModel: MainViewModel = viewModel() ) { // MainUiModel? val uiModel by viewModel.name.observeAsState() // MainUiModel val uiModel by viewModel.name.observeAsState(MainUiModel("")) }

Slide 31

Slide 31 text

// StateFlow @Composable fun MainScreen( viewModel: MainViewModel = viewModel() ) { // MainUiModel val uiModel by viewModel.name.collectAsState() // バックグラウンドでStateFlow が更新されてもrecomposition を発生させない val uiModel by viewModel.name.collectAsStateWithLifecycle() }

Slide 32

Slide 32 text

// State @Composable fun MainScreen( viewModel: MainViewModel = viewModel() ) { val uiModel = viewModel.uiModel }

Slide 33

Slide 33 text

LiveData StateFlow State インスタン スの作り方 バッキングプロパティを使って 安全に公開する必要がある バッキングプロパティを使って 安全に公開する必要がある setter をprivate にするだけ 更新の仕方 setValue とpostValue を使い分け る setValue とupdate を使い分ける 普通に変数を更新すればよい 監視の仕方 オブザーバーを使う 終端演算子を使う 勝手に監視される 使用難易度 スレッドのことを考えないとい けない null との戦い coroutine をよく知らないといけ ない Compose でしか使えないことを 除けば非常に簡単に使える

Slide 34

Slide 34 text

ご清聴ありがとうございました

Slide 35

Slide 35 text

Appendix

Slide 36

Slide 36 text

// Setter しかもたないState はデータ競合しないのかといわれればそれはNo date class MainUiModel(val name: String, val age: Int) var uiModel by mutableStateOf(MainUiModel("", 0)) private set fun updateName(name: String) { viewModelScope.launch { withContext(Dispatchers.IO) { delay(1000) uiModel = uiModel.copy(name = name) } } } fun updateAge(age: Int) { viewModelScope.launch { withContext(Dispatchers.IO) { delay(1000) uiModel = uiModel.copy(age = age) } } }

Slide 37

Slide 37 text

K2 コンパイラが正式リリースされたらExplicit Backing Fields によってバッキングプロパティはしなくてよくなる https://github.com/Kotlin/KEEP/blob/explicit-backing-fields-re/proposals/explicit-backing-fields.md#underscore-operator-in-type- parameters data class MainUiState(val name: String) class MainViewModel : ViewModel() { val uiModel: LiveData field = MutableLiveData() val uiModel = field.asStateFlow() field = MutableStateFlow(MainUiModel("")) val uiModel: StateFlow<_> field = MutableStateFlow(MainUiModel("")) }

Slide 38

Slide 38 text

複雑ではあるが coroutine を使って処理を実行することが多い ViewModel においては StateFlow を使った ほうが何かと便利 Flow の豊富なAPI が使える SavedStateHandle と相性がよい Compose のsavable API との統合もあるにはある(Snapshot システムについて知る必要があるが)