以 MVI 模式提供或发送频道的正确位置
Right place to offer or send Channel in MVI pattern
我提前在recycleView中加载了数据。为了做到这一点,我在 Activity 的 onCreate() 中有以下代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupUI()
setupViewModel()
observeViewModel()
if (savedInstanceState == null) {
mainViewModel.userIntent.offer(MainIntent.FetchUser)
}
}
如您所见,我 offer()
当 savedInstanceState 为 null 时,问题是当我们有进程死亡时(您可以通过激活开发人员选项中的不保留活动来简单地创建它),不会重新加载数据触发。
另一种选择是在 ViewModel 的 init
块中使用它,但问题是我想进行下面的单元测试,我可以验证所有三种状态:
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
runBlockingTest {
`when`(apiService.getUsers()).thenReturn(emptyList())
val apiHelper = ApiHelperImpl(apiService)
val repository = MainRepository(apiHelper)
val viewModel = MainViewModel(repository)
viewModel.state.asLiveData().observeForever(observer)
viewModel.userIntent.send(MainIntent.FetchUser)
}
verify(observer, times(3)).onChanged(captor.capture())
verify(observer).onChanged(MainState.Idle)
verify(observer).onChanged(MainState.Loading)
verify(observer).onChanged(MainState.Users(emptyList()))
}
如果我在 ViewModel 初始化后立即使用 init 块选项,send
或 offer
将被调用,而 observeForever
在上述单元测试中未用于 LiveData。
这是我的 ViewModel class :
class MainViewModel(
private val repository: MainRepository
) : ViewModel() {
val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state: StateFlow<MainState>
get() = _state
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
userIntent.consumeAsFlow().collect {
when (it) {
is MainIntent.FetchUser -> fetchUser()
}
}
}
}
private fun fetchUser() {
viewModelScope.launch {
_state.value = MainState.Loading
_state.value = try {
MainState.Users(repository.getUsers())
} catch (e: Exception) {
MainState.Error(e.localizedMessage)
}
}
}
}
以上情况的解决方案是什么?
我找到的唯一解决方案是将 fetchUser
方法和另一个 _state
作为 MutableStateFlow 移动到 Repository 层,然后 observeForever
它在 Repository 中用于本地单元测试,因此我可以在 ViewModel 的初始化块中发送或提供 userIntent
。
我将在 ViewModel 中有以下 _state
:
val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
private val _state = repository.state
val state: StateFlow<MainState>
get() = _state
我提前在recycleView中加载了数据。为了做到这一点,我在 Activity 的 onCreate() 中有以下代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupUI()
setupViewModel()
observeViewModel()
if (savedInstanceState == null) {
mainViewModel.userIntent.offer(MainIntent.FetchUser)
}
}
如您所见,我 offer()
当 savedInstanceState 为 null 时,问题是当我们有进程死亡时(您可以通过激活开发人员选项中的不保留活动来简单地创建它),不会重新加载数据触发。
另一种选择是在 ViewModel 的 init
块中使用它,但问题是我想进行下面的单元测试,我可以验证所有三种状态:
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
runBlockingTest {
`when`(apiService.getUsers()).thenReturn(emptyList())
val apiHelper = ApiHelperImpl(apiService)
val repository = MainRepository(apiHelper)
val viewModel = MainViewModel(repository)
viewModel.state.asLiveData().observeForever(observer)
viewModel.userIntent.send(MainIntent.FetchUser)
}
verify(observer, times(3)).onChanged(captor.capture())
verify(observer).onChanged(MainState.Idle)
verify(observer).onChanged(MainState.Loading)
verify(observer).onChanged(MainState.Users(emptyList()))
}
如果我在 ViewModel 初始化后立即使用 init 块选项,send
或 offer
将被调用,而 observeForever
在上述单元测试中未用于 LiveData。
这是我的 ViewModel class :
class MainViewModel(
private val repository: MainRepository
) : ViewModel() {
val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state: StateFlow<MainState>
get() = _state
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
userIntent.consumeAsFlow().collect {
when (it) {
is MainIntent.FetchUser -> fetchUser()
}
}
}
}
private fun fetchUser() {
viewModelScope.launch {
_state.value = MainState.Loading
_state.value = try {
MainState.Users(repository.getUsers())
} catch (e: Exception) {
MainState.Error(e.localizedMessage)
}
}
}
}
以上情况的解决方案是什么?
我找到的唯一解决方案是将 fetchUser
方法和另一个 _state
作为 MutableStateFlow 移动到 Repository 层,然后 observeForever
它在 Repository 中用于本地单元测试,因此我可以在 ViewModel 的初始化块中发送或提供 userIntent
。
我将在 ViewModel 中有以下 _state
:
val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
private val _state = repository.state
val state: StateFlow<MainState>
get() = _state