DataSource Use case ViewModel External World User Interface suspend fun (T) suspend fun (T) suspend fun (T) data flow format How can we push multiple states to the View level ???
Activity( ) handle(someInteraction) ViewModel method call FlowConsumer StateMachine fun handle(i : UserInteraction) bind( ) fun bind( ) : Flow fun handle(d : Data) fun showLoading( ) fun showError( ) data flow format Flow
class StateMachine( private val container: StateContainer, private val executor: TaskExecutor ) { fun states() = container.observableStates() fun consume(execution: StateTransition) = executor.execute { wrapWithStates(execution) } // Continue on next slide }
private fun renderState(state: ViewState) = when (state) { is Failed !" handleError(state.reason) is Success !" showFacts(state.value) is Loading.FromEmpty !" startExecution() is Loading.FromPrevious !" showFacts(state.previous) is FirstLaunch !" loadFacts() } } private fun loadFacts() { viewModel.handle(OpenedScreen) } }
State Machine TaskExecutor StateContainer collaborates with class StateMachine( private val container: StateContainer, private val executor: TaskExecutor ) { // Implementation }
State Machine TaskExecutor StateContainer collaborates with The State Container registers the current state processed by the State Machine. The implementation is CoroutineScope-aware and uses a ConflatedBroadcastChannel as communication engine to propagate state to consumers. We can build a StateContainer instance with a CoroutineScope differente than the consumer’s scope
State Machine TaskExecutor StateContainer collaborates with The task executor defines a model of execution for the state transition : either Concurrent or Synchronous
class ConfigChangesAwareStateContainer : StateContainer, ViewModel() { private val broadcaster by lazy { ConflatedBroadcastChannel} override val emissionScope = viewModelScope override fun observableStates() = broadcaster.asFlow() override fun current(): ViewState = broadcaster.value override suspend fun store(state: ViewState) { broadcaster.send(state) } Cold flow over Hot Channel
companion object { operator fun invoke(host: FragmentActivity): StateContainer { val f = object : ViewModelProvider.Factory { override fun create(klass: Class) = ConfigChangesAwareStateContainer() as Model } val keyClazz = ConfigChangesAwareStateContainer()class.java return ViewModelProviders.of(host, f)[keyClazz] as StateContainer } } }
class FetchCategoriesTests { private val categoriesCache = mock() private val remoteFacts = mock() private lateinit var usecase: FetchCategories private val categories = mutableListOf( RelatedCategory.Available("dev"), RelatedCategory.Available("code") ) @Before fun `before each test`() { usecase = FetchCategories(categoriesCache, remoteFacts) } @Test fun `should fetch from cache from available`() { runBlocking { `given that remote service not available`() `given that cache returns categories`() assertThat(usecase.execute()).isEqualTo(categories) } } }