Правильное место для предложения или отправки канала в шаблоне MVI

#android #unit-testing #android-livedata #android-viewmodel

#Android #модульное тестирование #android-livedata #android-viewmodel

Вопрос:

Я загружаю данные в RecycleView заранее. Для этого у меня есть следующий код в 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, проблема в том, что когда у нас происходит смерть процесса (вы можете просто создать его, активировав параметр «Не сохранять действия в параметре разработчика»), перезагрузка данных не будет запущена.

другой вариант — использовать его внутри init блока ViewModel, но проблема в том, что я хочу провести модульный тест ниже, в котором я могу проверить все три состояния :

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

Если я использую опцию init block, как только инициализируется ViewModel, send или offer будет вызван, пока observeForever не использовался для LiveData в приведенном выше модульном тесте.

Вот мой класс ViewModel :

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

Каким может быть решение для вышеуказанных сценариев?

Ответ №1:

Единственное решение, которое я нашел fetchUser , — это метод перемещения, а другой _state как MutableStateFlow на уровень репозитория и observeForever в репозиторий для локального модульного тестирования, в результате я могу отправить или предложить userIntent в init заблокировать ViewModel.

У меня будет следующее _state в ViewModel :

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