Right place to offer or send Channel in MVI pattern

风流意气都作罢 提交于 2021-01-29 06:41:04

问题


I load data in recycleView in advance. In order to do that I have following code in onCreate() of Activity :

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupUI()
        setupViewModel()
        observeViewModel()
        if (savedInstanceState == null) {
            mainViewModel.userIntent.offer(MainIntent.FetchUser)
        }
    }

As you see I offer() when savedInstanceState is null, The problem is when we have process death ( you can simply create it by activating Do not keep activities in developer option), reload of data will not be triggered.

another option is to use it inside init block of ViewModel, but problem is I want to have bellow unit test which I can verify all three states :

    @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()))
    }

If I use the init block option as soon as ViewModel initialized, send or offer will be called while observeForever did not be used for LiveData in the above unit test.

Here is my 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)
            }
        }
    }
}

What could be the solution for the above scenarios?


回答1:


The only solution that I found is moving fetchUser method and another _state as MutableStateFlow to Repository layer and observeForever it in Repository for local unit test, as a result I can send or offer userIntent in init block off ViewModel.

I will have following _state in ViewModel :

val userIntent = Channel<MainIntent>(Channel.UNLIMITED)
    private val _state = repository.state
    val state: StateFlow<MainState>
        get() = _state


来源:https://stackoverflow.com/questions/65388332/right-place-to-offer-or-send-channel-in-mvi-pattern

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!