/** * Sample usage for screen with search bar at the top, a filter icon that launches a modal/dialog with advanced filter * options, and displays results as a list. * * This lives in a common module that both the android and iOS common modules depend on. */ class SearchStateMachine( scope: CoroutineScope, private val exitDb: KExitDatabase ) { private val stateMachine = StateMachine( scope = scope, initialState = Search.State(Search.StateType.InitialState, emptyList(), null), reducer = ::reducer, sideEffects = listOf( ::generateFilterOptions, ::queryDatabase, ::filterDatabase ) ) val viewState = stateMachine.viewState val dispatchAction = stateMachine::dispatchAction // Needed for android so we can generate diffResult. var lastList: AtomicRef> = atomic(listOf()) private fun generateFilterOptions(input: Flow, state: () -> Search.State): Flow = input.filterIsInstance() .map { val defaultOptions = state().filterOptions val filterOptions = withContext(Dispatchers.Default) { val countryList = exitDb.exitEntityQueries.listAllCountries().executeAsList() defaultOptions.copy(filterCountryList = countryList) } Search.Action.FilterOptionsLoadedAction(filterOptions = filterOptions) } private fun filterDatabase(input: Flow, state: () -> Search.State): Flow = input.filterIsInstance() .map { action -> performFilterSearch(action.filterState, action.query) } private fun queryDatabase(input: Flow, state: () -> Search.State): Flow = input.filterIsInstance() .map { action -> performQuerySearch(action.filterState, action.query) } private suspend fun performFilterSearch(filterState: FilterState, query: String) = withContext(Dispatchers.Default) { val searchResultList = KSearchQueryExecutor(exitDb).queryDatabase(filterState, query) val prevList = lastList.value val diffResult = KDiffUtil.calculateDiff( SearchResultItemDiffHelper( newList = searchResultList, oldList = prevList ) ) Search.Action.SearchLoadedAction(searchResultList, diffResult) } private suspend fun performQuerySearch(filterState: FilterState, query: String) = withContext(Dispatchers.Default) { val newList = KSearchQueryExecutor(exitDb).queryByName(filterState, query) val prevList = lastList.value val diffResult = KDiffUtil.calculateDiff( SearchResultItemDiffHelper( newList = newList, oldList = prevList ) ) Search.Action.SearchLoadedAction(newList, diffResult) } private suspend fun reducer(state: Search.State, action: Search.Action): Search.State { //kprint("reducer: curState=$state action=$action") return when (action) { is Search.Action.InitializeFiltersAction -> state is Search.Action.FilterOptionsLoadedAction -> { state.copy(type = Search.StateType.FilterOptionsLoaded, filterOptions = action.filterOptions) } is Search.Action.TapFilterCancelBtn -> state.copy( type = Search.StateType.CloseFilterState, isFilterWindowVisible = false ) is Search.Action.TapOpenFilterIcon -> { kprint("returning OpenFilterState") state.copy(type = Search.StateType.OpenFilterState, isFilterWindowVisible = true) } is Search.Action.QueryChangeAction -> state is Search.Action.BackButtonTapAction -> { if (state.isFilterWindowVisible) { state.copy(type = Search.StateType.CloseFilterState, isFilterWindowVisible = false) } else { state.copy(type = Search.StateType.ClosePageState, isFilterWindowVisible = false) } } is Search.Action.FilterUpdateAction -> state.copy( type = Search.StateType.CloseFilterState, isFilterWindowVisible = false ) is Search.Action.SearchLoadedAction -> { lastList.value = action.items state.copy(type = Search.StateType.ShowResultsState, items = action.items, diffResult = action.diffResult) } } } }