iT邦幫忙

2021 iThome 鐵人賽

DAY 21
1
Mobile Development

認真學 Compose - 對 Jetpack Compose 的問題與探索系列 第 21

D21/ 怎麼結合 ViewModel 和 Compose? - ViewModel

今天大概會聊到的範圍

  • viewModel in Compose

今天的主題很單純:如果專案中有使用到 Compose 又有用到 Android Architecture Component 的 ViewModel 與 LiveData 的話,要怎麼讓資料在 Compose 內外傳遞呢?

首先,先建立今天的主角 ViewModel

class FooViewModel : ViewModel() {
    
    val buzzLiveData: LiveData<List<String>>
        get() = _buzzLiveData
    
    private val _buzzLiveData = MutableLiveData<List<String>>(emptyList())
    
    fun addData(param: String) {
        _buzzLiveData.value = _buzzLiveData.value?.let { it + listOf(param) }
    }
}

ViewModel 可以在 Activity 層建立,方法就和一般的 ViewModel 一樣:

class BarActivity : ComponentActivity() {
    
    private val vm: FooViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            FooComposable(vm) // <-- 
        }
    }
}


@Composable
fun FooComposable(vm: FooViewModel) { ... }

在 Activity 建立 ViewModel 後,可以將其當成一般的參數傳入 Composable。

@Composable
fun FooComposable(model: FooViewModel) {
    
    val data by model.buzzLiveData.observeAsState(initial = emptyList())  // <--- 1
    
    Column {
        LazyColumn {
            items(data) {        // <--- 2
                Text(it)
            }
        }
        
        Button({ model.addData("one") }) {    // <--- 3
            Text("add data")
        }
    }
}

observeAsState 需要 implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"

  1. 取得 viewModel 中的 LiveData 之後,可以透過 observeAsState 來將 LiveData 轉換成 State
  2. 在 composable 中,就可以使用這個 State 當成資料運作
  3. 一般對 ViewModel 的 function 呼叫也可以正常運作

注意!呼叫 ViewModel function 的部分若是放在 composable function 中,會變成 side-effect 。若真的要執行需要特別處理 ( 上一篇的主題 )
Button 的 onClick callback 不是 composable function

@Composable
fun FooComposable(model: FooViewModel = viewModel()) { ... }

若不一定要外部的使用者提供 ViewModel,也可以透過 viewModel() 來現場建立一個 viewModel。

observeAsState Under the hood

observeAsState 到底怎麼把 LiveData 轉成 State 的呢?我們可以來研究一下 observeAsState 的實作:

@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
    val lifecycleOwner = LocalLifecycleOwner.current      // < --- 1
    val state = remember { mutableStateOf(initial) }      // < --- 2
    DisposableEffect(this, lifecycleOwner) {                           // < --- 5
        val observer = Observer<T> { state.value = it }   // < --- 3
        observe(lifecycleOwner, observer)                
        onDispose { removeObserver(observer) }                         // < --- 6
    }
    return state                                          // < --- 4
}
  1. 先透過 LocalLifecycleOwner 可以取得目前 Composable 所在 scope 的 LifecycleOwner
  2. 一樣是透過 remember { mutableStateOf() } 將資料存放起來
  3. 透過一般的 LiveData.observe 去觀察 LiveData 的資料,若有改動就將資料同步到 state
  4. 將 state 回傳,給外部使用
  5. 為了確保當 LiveData or LifecycleOwner 有異動的時候能重新做 observe ,且除此之外不要做異動。使用之前提到過的 DisposableEffect
  6. onDispose 時,將 observer 回收

想不到 ViewModel 和 LiveData 在 Compose 中的使用其實非常的簡單。在 LiveData 背後,又剛好是使用到之前所研究的 Side Effect API,感覺整個技術都串起來了!


Reference:


上一篇
D20/ 怎麼在 compose 與 non-compoe 間傳資料 - Compose Side-Effect part 2
下一篇
D22/ 怎麼在 Compose 中用 Material Theme? - Theme
系列文
認真學 Compose - 對 Jetpack Compose 的問題與探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言