@@ -7,7 +7,7 @@ Github Repos Search - Kotlin Multiplatform Mobile using Jetpack Compose, SwiftUI
77[ ![ iOS Build CI] ( https://github.com/hoc081098/GithubSearchKMM/actions/workflows/ios-build.yml/badge.svg )] ( https://github.com/hoc081098/GithubSearchKMM/actions/workflows/ios-build.yml )
88[ ![ Validate Gradle Wrapper] ( https://github.com/hoc081098/GithubSearchKMM/actions/workflows/gradle-wrapper-validation.yml/badge.svg )] ( https://github.com/hoc081098/GithubSearchKMM/actions/workflows/gradle-wrapper-validation.yml )
99[ ![ API] ( https://img.shields.io/badge/API-23%2B-brightgreen.svg?style=flat )] ( https://android-arsenal.com/api?level=23 )
10- [ ![ Kotlin] ( https://img.shields.io/badge/kotlin-1.8.10 -blue.svg?logo=kotlin )] ( http://kotlinlang.org )
10+ [ ![ Kotlin] ( https://img.shields.io/badge/kotlin-1.8.21 -blue.svg?logo=kotlin )] ( http://kotlinlang.org )
1111[ ![ Hits] ( https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fhoc081098%2FGithubSearchKMM&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false )] ( https://hits.seeyoufarm.com )
1212[ ![ License: MIT] ( https://img.shields.io/badge/License-MIT-purple.svg )] ( https://opensource.org/licenses/MIT )
1313[ ![ codecov] ( https://codecov.io/gh/hoc081098/GithubSearchKMM/branch/master/graph/badge.svg?token=qzSAFkj09P )] ( https://codecov.io/gh/hoc081098/GithubSearchKMM )
@@ -101,44 +101,73 @@ Liked some of my work? Buy me a coffee (or more likely a beer)
101101
102102``` kotlin
103103public sealed interface FlowReduxStore <Action , State > {
104- public val coroutineScope: CoroutineScope
105-
104+ /* *
105+ * The state of this store.
106+ */
106107 public val stateFlow: StateFlow <State >
107108
108- /* * Get streams of actions.
109- *
110- * This [Flow] includes dispatched [Action]s (via [dispatch] function)
111- * and [Action]s returned from [SideEffect]s.
109+ /* *
110+ * @return false if cannot dispatch action (this store was closed).
111+ */
112+ public fun dispatch (action : Action ): Boolean
113+
114+ /* *
115+ * Call this method to close this store.
116+ * A closed store will not accept any action anymore, thus state will not change anymore.
117+ * All [SideEffect]s will be cancelled.
112118 */
113- public val actionSharedFlow : SharedFlow < Action >
119+ public fun close ()
114120
115121 /* *
116- * @return false if cannot dispatch action ([coroutineScope] was cancelled).
122+ * After calling [close] method, this function will return true.
123+ *
124+ * @return true if this store was closed.
117125 */
118- public fun dispatch ( action : Action ): Boolean
126+ public fun isClosed ( ): Boolean
119127}
120128```
121129
122130### Multiplatform ViewModel
131+
123132``` kotlin
124133open class GithubSearchViewModel (
125134 searchRepoItemsUseCase : SearchRepoItemsUseCase ,
135+ private val savedStateHandle : SavedStateHandle ,
126136) : ViewModel() {
137+ private val effectsContainer = GithubSearchSideEffectsContainer (searchRepoItemsUseCase)
138+
127139 private val store = viewModelScope.createFlowReduxStore(
128140 initialState = GithubSearchState .initial(),
129- sideEffects = GithubSearchSideEffects (
130- searchRepoItemsUseCase = searchRepoItemsUseCase,
131- ).sideEffects,
132- reducer = { state, action -> action.reduce(state) }
141+ sideEffects = effectsContainer.sideEffects,
142+ reducer = Reducer (flip(GithubSearchAction ::reduce))
143+ .withLogger(githubSearchFlowReduxLogger())
133144 )
134- private val eventChannel = store.actionSharedFlow
135- .mapNotNull { it.toGithubSearchSingleEventOrNull() }
136- .buffer(Channel .UNLIMITED )
137- .produceIn(viewModelScope)
138-
139- fun dispatch (action : GithubSearchAction ) = store.dispatch(action)
140- val stateFlow: StateFlow <GithubSearchState > by store::stateFlow
141- val eventFlow: Flow <GithubSearchSingleEvent > get() = eventChannel.receiveAsFlow()
145+
146+ val termStateFlow: NonNullStateFlowWrapper <String > = savedStateHandle.getStateFlow(TERM_KEY , " " ).wrap()
147+ val stateFlow: NonNullStateFlowWrapper <GithubSearchState > = store.stateFlow.wrap()
148+ val eventFlow: NonNullFlowWrapper <GithubSearchSingleEvent > = effectsContainer.eventFlow.wrap()
149+
150+ init {
151+ store.dispatch(InitialSearchAction (termStateFlow.value))
152+ }
153+
154+ @MainThread
155+ fun dispatch (action : GithubSearchAction ): Boolean {
156+ if (action is GithubSearchAction .Search ) {
157+ savedStateHandle[TERM_KEY ] = action.term
158+ }
159+ return store.dispatch(action)
160+ }
161+
162+ companion object {
163+ private const val TERM_KEY = " com.hoc081098.github_search_kmm.presentation.GithubSearchViewModel.term"
164+
165+ /* *
166+ * Used by non-Android platforms.
167+ */
168+ fun create (searchRepoItemsUseCase : SearchRepoItemsUseCase ): GithubSearchViewModel =
169+ GithubSearchViewModel (searchRepoItemsUseCase, SavedStateHandle ())
170+ }
142171}
143172```
144173
@@ -150,8 +179,10 @@ Extends `GithubSearchViewModel` to use `Dagger Constructor Injection`.
150179
151180``` kotlin
152181@HiltViewModel
153- class DaggerGithubSearchViewModel @Inject constructor(searchRepoItemsUseCase : SearchRepoItemsUseCase ) :
154- GithubSearchViewModel (searchRepoItemsUseCase)
182+ class DaggerGithubSearchViewModel @Inject constructor(
183+ searchRepoItemsUseCase : SearchRepoItemsUseCase ,
184+ savedStateHandle : SavedStateHandle ,
185+ ) : GithubSearchViewModel(searchRepoItemsUseCase, savedStateHandle)
155186```
156187
157188#### iOS
@@ -169,6 +200,7 @@ class IOSGithubSearchViewModel: ObservableObject {
169200 private let vm: GithubSearchViewModel
170201
171202 @Published private (set ) var state: GithubSearchState
203+ @Published private (set ) var term: String = " "
172204 let eventPublisher: AnyPublisher<GithubSearchSingleEventKs, Never >
173205
174206 init (vm : GithubSearchViewModel) {
@@ -179,11 +211,18 @@ class IOSGithubSearchViewModel: ObservableObject {
179211 .map (GithubSearchSingleEventKs.init )
180212 .eraseToAnyPublisher ()
181213
182- self .state = vm.stateFlow .typedValue ()
183- vm.stateFlow .subscribeNonNullFlow (
214+ self .state = vm.stateFlow .value
215+ vm.stateFlow .subscribe (
184216 scope : vm.viewModelScope ,
185217 onValue : { [weak self ] in self ? .state = $0 }
186218 )
219+
220+ self .vm
221+ .termStateFlow
222+ .asNonNullPublisher (NSString.self )
223+ .assertNoFailure ()
224+ .map { $0 as String }
225+ .assign (to : & $term)
187226 }
188227
189228 @discardableResult
@@ -242,14 +281,14 @@ class IOSGithubSearchViewModel: ObservableObject {
242281--------------------------------------------------------------------------------
243282 Language Files Lines Blank Comment Code
244283--------------------------------------------------------------------------------
245- Kotlin 96 7111 863 398 5850
284+ Kotlin 105 7647 936 439 6272
246285 JSON 7 3938 0 0 3938
247- Swift 16 857 110 98 649
248- Markdown 1 255 47 0 208
249- Bourne Shell 2 245 28 110 107
250- Batch 1 91 21 0 70
251- XML 7 71 6 0 65
286+ Swift 16 903 118 102 683
287+ Markdown 1 294 54 0 240
288+ Bourne Shell 2 250 28 116 106
289+ Batch 1 92 21 0 71
290+ XML 6 69 6 0 63
252291--------------------------------------------------------------------------------
253- Total 130 12568 1075 606 10887
292+ Total 138 13193 1163 657 11373
254293--------------------------------------------------------------------------------
255294```
0 commit comments