From 641b2f708eb5121835099af70ba38d8aca4614d1 Mon Sep 17 00:00:00 2001 From: mramotar Date: Wed, 18 Oct 2023 10:10:53 -0400 Subject: [PATCH] Clean up Signed-off-by: mramotar_dbx --- .../store/paging5/InitStoreStateFlow.kt | 65 --------- .../store/paging5/KeyProvider.kt | 2 +- .../store/paging5/LaunchStore.kt | 135 ++++++++++++++++++ .../store/paging5/PagingCache.kt | 2 +- .../store/paging5/PagingCacheAccessor.kt | 2 +- .../paging5/{Identifiable.kt => StoreData.kt} | 8 +- .../store/paging5/StoreState.kt | 8 +- .../store/paging5/UpdateStoreState.kt | 89 ------------ ...eStateFlowTests.kt => LaunchStoreTests.kt} | 8 +- .../store/paging5/PagingTests.kt | 16 ++- .../store/paging5/util/PostData.kt | 12 +- 11 files changed, 168 insertions(+), 179 deletions(-) delete mode 100644 paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlow.kt create mode 100644 paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchStore.kt rename paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/{Identifiable.kt => StoreData.kt} (74%) delete mode 100644 paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/UpdateStoreState.kt rename paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/{InitStoreStateFlowTests.kt => LaunchStoreTests.kt} (92%) diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlow.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlow.kt deleted file mode 100644 index cc64a442b..000000000 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlow.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.mobilenativefoundation.store.paging5 - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import org.mobilenativefoundation.store.store5.ExperimentalStoreApi -import org.mobilenativefoundation.store.store5.MutableStore -import org.mobilenativefoundation.store.store5.Store - - -/** - * Initializes and returns a [StateFlow] that reflects the state of the Store, updating by a flow of provided keys. - * @param scope A [CoroutineScope]. - * @param keys A flow of keys that dictate how the Store should be updated. - * @param updateStoreState A lambda that defines how the Store's state should be updated based on the current state and a key. - * @return A read-only [StateFlow] reflecting the state of the Store. - */ -private fun , Output : Identifiable> initStoreStateFlow( - scope: CoroutineScope, - keys: Flow, - updateStoreState: suspend (currentState: StoreState, key: Key) -> StoreState -): StateFlow> { - val stateFlow = MutableStateFlow>(StoreState.Loading) - - scope.launch { - keys.collect { key -> - println("KEY = $key") - println("CURRENT STATE = ${stateFlow.value}") - val updatedState = updateStoreState(stateFlow.value, key) - stateFlow.emit(updatedState) - } - } - - return stateFlow.asStateFlow() -} - -/** - * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. - * @see [initStoreStateFlow]. - */ -fun , Output : Identifiable> Store.initStoreStateFlow( - scope: CoroutineScope, - keys: Flow, -): StateFlow> { - return initStoreStateFlow(scope, keys) { currentState, key -> - this.updateStoreState(currentState, key) - } -} - -/** - * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. - * @see [initStoreStateFlow]. - */ -@OptIn(ExperimentalStoreApi::class) -fun , Output : Identifiable> MutableStore.initStoreStateFlow( - scope: CoroutineScope, - keys: Flow, -): StateFlow> { - return initStoreStateFlow(scope, keys) { currentState, key -> - this.updateStoreState(currentState, key) - } -} diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/KeyProvider.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/KeyProvider.kt index 48f351748..36c7602d9 100644 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/KeyProvider.kt +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/KeyProvider.kt @@ -1,6 +1,6 @@ package org.mobilenativefoundation.store.paging5 -interface KeyProvider> { +interface KeyProvider> { fun from(key: StoreKey.Collection, value: Single): StoreKey.Single fun from(key: StoreKey.Single, value: Single): StoreKey.Collection } \ No newline at end of file diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchStore.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchStore.kt new file mode 100644 index 000000000..8b7d302e6 --- /dev/null +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchStore.kt @@ -0,0 +1,135 @@ +@file:Suppress("UNCHECKED_CAST") + + +package org.mobilenativefoundation.store.paging5 + + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.mobilenativefoundation.store.store5.ExperimentalStoreApi +import org.mobilenativefoundation.store.store5.MutableStore +import org.mobilenativefoundation.store.store5.Store +import org.mobilenativefoundation.store.store5.impl.extensions.fresh + + +typealias LoadedCollection = StoreState.Loaded.Collection, StoreData.Collection>> + + +/** + * Initializes and returns a [StateFlow] that reflects the state of the Store, updating by a flow of provided keys. + * @param scope A [CoroutineScope]. + * @param keys A flow of keys that dictate how the Store should be updated. + * @param fresh A lambda that invokes [Store.fresh]. + * @return A read-only [StateFlow] reflecting the state of the Store. + */ +private fun , Output : StoreData> launchStore( + scope: CoroutineScope, + keys: Flow, + fresh: suspend (currentState: StoreState, key: Key) -> StoreState +): StateFlow> { + val stateFlow = MutableStateFlow>(StoreState.Loading) + + scope.launch { + keys.collect { key -> + val nextState = fresh(stateFlow.value, key) + stateFlow.emit(nextState) + } + } + + return stateFlow.asStateFlow() +} + +/** + * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. + * @see [launchStore]. + */ +fun , Output : StoreData> Store.launchStore( + scope: CoroutineScope, + keys: Flow, +): StateFlow> { + return launchStore(scope, keys) { currentState, key -> + this.freshAndInsertUpdatedItems(currentState, key) + } +} + +/** + * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. + * @see [launchStore]. + */ +@OptIn(ExperimentalStoreApi::class) +fun , Output : StoreData> MutableStore.launchStore( + scope: CoroutineScope, + keys: Flow, +): StateFlow> { + return launchStore(scope, keys) { currentState, key -> + this.freshAndInsertUpdatedItems(currentState, key) + } +} + + +/** + * Updates the Store's state based on a provided key and a retrieval mechanism. + * @param currentState The current state of the Store. + * @param key The key that dictates how the state should be updated. + * @param get A lambda that defines how to retrieve data from the Store based on a key. + */ +private suspend fun , Output : StoreData> freshAndInsertUpdatedItems( + currentState: StoreState, + key: Key, + get: suspend (key: Key) -> Output, +): StoreState { + return try { + if (key !is StoreKey.Collection<*>) throw IllegalArgumentException("Invalid key type") + + val lastOutput = when (currentState) { + is StoreState.Loaded.Collection<*, *, *> -> (currentState as LoadedCollection).data + else -> null + } + + val nextOutput = get(key) as StoreData.Collection> + + val output = (lastOutput?.insertItems(key.loadType, nextOutput.items) ?: nextOutput) + StoreState.Loaded.Collection(output) as StoreState + + } catch (error: Exception) { + StoreState.Error.Exception(error) + } +} + +/** + * Updates the [Store]'s state based on a provided key. + * @see [freshAndInsertUpdatedItems]. + */ +private suspend fun , Output : StoreData> Store.freshAndInsertUpdatedItems( + currentState: StoreState, + key: Key +): StoreState { + return freshAndInsertUpdatedItems( + currentState, + key + ) { + this.fresh(it) + } +} + +/** + * Updates the [MutableStore]'s state based on a provided key. + * @see [freshAndInsertUpdatedItems]. + */ +@OptIn(ExperimentalStoreApi::class) +private suspend fun , Output : StoreData> MutableStore.freshAndInsertUpdatedItems( + currentState: StoreState, + key: Key +): StoreState { + return freshAndInsertUpdatedItems( + currentState, + key + ) { + this.fresh(it) + } +} + diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCache.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCache.kt index f9737a264..5455e525f 100644 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCache.kt +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCache.kt @@ -10,7 +10,7 @@ import org.mobilenativefoundation.store.cache5.Cache * Depends on [PagingCacheAccessor] for internal data management. * @see [Cache]. */ -class PagingCache, StoreOutput : Identifiable, Collection : Identifiable.Collection, Single : Identifiable.Single>( +class PagingCache, StoreOutput : StoreData, Collection : StoreData.Collection, Single : StoreData.Single>( private val keyProvider: KeyProvider, ) : Cache { diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCacheAccessor.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCacheAccessor.kt index 83e6f67fb..23b531211 100644 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCacheAccessor.kt +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/PagingCacheAccessor.kt @@ -6,7 +6,7 @@ import org.mobilenativefoundation.store.cache5.CacheBuilder * Intermediate data manager for a caching system supporting pagination. * Tracks keys for rapid data retrieval and modification. */ -class PagingCacheAccessor, Single : Identifiable.Single> { +class PagingCacheAccessor, Single : StoreData.Single> { private val collections = CacheBuilder, Collection>().build() private val singles = CacheBuilder, Single>().build() private val keys = mutableSetOf>() diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/Identifiable.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreData.kt similarity index 74% rename from paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/Identifiable.kt rename to paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreData.kt index 55871e763..bc42ee553 100644 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/Identifiable.kt +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreData.kt @@ -3,23 +3,23 @@ package org.mobilenativefoundation.store.paging5 /** * An interface that defines items that can be uniquely identified. - * Every item that implements the [Identifiable] interface must have a means of identification. + * Every item that implements the [StoreData] interface must have a means of identification. * This is useful in scenarios when data can be represented as singles or collections. */ -interface Identifiable { +interface StoreData { /** * Represents a single identifiable item. */ - interface Single : Identifiable { + interface Single : StoreData { val id: Id } /** * Represents a collection of identifiable items. */ - interface Collection> : Identifiable { + interface Collection> : StoreData { val items: List /** diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreState.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreState.kt index 9c321c490..b8d1943d5 100644 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreState.kt +++ b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/StoreState.kt @@ -3,7 +3,7 @@ package org.mobilenativefoundation.store.paging5 /** * An interface that defines various states of data-fetching operations. */ -sealed interface StoreState> { +sealed interface StoreState> { /** * Represents the initial state. @@ -19,17 +19,17 @@ sealed interface StoreState> { /** * Represents successful fetch operations. */ - sealed interface Loaded> : StoreState { + sealed interface Loaded> : StoreState { /** * Represents a successful fetch of an individual item. */ - data class Single>(val data: Output) : Loaded + data class Single>(val data: Output) : Loaded /** * Represents a successful fetch of a collection of items. */ - data class Collection, CO : Identifiable.Collection>(val data: CO) : + data class Collection, CO : StoreData.Collection>(val data: CO) : Loaded } diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/UpdateStoreState.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/UpdateStoreState.kt deleted file mode 100644 index 743cd4e54..000000000 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/UpdateStoreState.kt +++ /dev/null @@ -1,89 +0,0 @@ -@file:Suppress("UNCHECKED_CAST") - -package org.mobilenativefoundation.store.paging5 - -import org.mobilenativefoundation.store.store5.ExperimentalStoreApi -import org.mobilenativefoundation.store.store5.MutableStore -import org.mobilenativefoundation.store.store5.Store -import org.mobilenativefoundation.store.store5.impl.extensions.fresh - - -typealias LoadedCollection = StoreState.Loaded.Collection, Identifiable.Collection>> - - -/** - * Updates the Store's state based on a provided key and a retrieval mechanism. - * @param currentState The current state of the Store. - * @param key The key that dictates how the state should be updated. - * @param get A lambda that defines how to retrieve data from the Store based on a key. - */ -private suspend fun , Output : Identifiable> updateStoreState( - currentState: StoreState, - key: Key, - get: suspend (key: Key) -> Output, -): StoreState { - return try { - if (key !is StoreKey.Collection<*>) throw IllegalArgumentException("Invalid key type") - - val lastOutput = when (currentState) { - is StoreState.Loaded.Collection<*, *, *> -> { - val data = (currentState as LoadedCollection).data - println("DATA = $data") - data - } - - else -> { - println("NULL") - null - } - } - - val nextOutput = get(key) as Identifiable.Collection> - - val output = (lastOutput?.insertItems(key.loadType, nextOutput.items) ?: nextOutput) - - println("OUTPUT * = $lastOutput $output") - StoreState.Loaded.Collection(output) as StoreState - - } catch (error: Exception) { - StoreState.Error.Exception(error) - } -} - -/** - * Updates the [Store]'s state based on a provided key. - * @see [updateStoreState]. - */ -suspend fun , Output : Identifiable> Store.updateStoreState( - currentState: StoreState, - key: Key -): StoreState { - return updateStoreState( - currentState, - key - ) { - this.fresh(it) - } -} - -/** - * Updates the [MutableStore]'s state based on a provided key. - * @see [updateStoreState]. - */ -@OptIn(ExperimentalStoreApi::class) -suspend fun , Output : Identifiable> MutableStore.updateStoreState( - currentState: StoreState, - key: Key -): StoreState { - return updateStoreState( - currentState, - key - ) { - val output = this.fresh(it) - println("KEY = $key") - println("OUTPUT = $output") - println("CURRENT STATE = $currentState") - output - } -} - diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlowTests.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchStoreTests.kt similarity index 92% rename from paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlowTests.kt rename to paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchStoreTests.kt index 208e5443f..484016ae6 100644 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/InitStoreStateFlowTests.kt +++ b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchStoreTests.kt @@ -13,7 +13,7 @@ import kotlin.test.assertEquals import kotlin.test.assertIs @OptIn(ExperimentalStoreApi::class) -class InitStoreStateFlowTests { +class LaunchStoreTests { private val testScope = TestScope() private val userId = "123" @@ -33,7 +33,7 @@ class InitStoreStateFlowTests { fun `state transitions from Loading to Loaded Collection for valid Cursor key`() = testScope.runTest { val key = PostKey.Cursor("1", 10) val keys = flowOf(key) - val stateFlow = store.initStoreStateFlow(this, keys) + val stateFlow = store.launchStore(this, keys) stateFlow.test { val state1 = awaitItem() @@ -49,7 +49,7 @@ class InitStoreStateFlowTests { val key1 = PostKey.Cursor("1", 10) val key2 = PostKey.Cursor("11", 10) val keys = flowOf(key1, key2) - val stateFlow = store.initStoreStateFlow(this, keys) + val stateFlow = store.launchStore(this, keys) stateFlow.test { val state1 = awaitItem() @@ -69,7 +69,7 @@ class InitStoreStateFlowTests { fun `state remains consistent if the same key is emitted multiple times`() = testScope.runTest { val key = PostKey.Cursor("1", 10) val keys = flowOf(key, key) - val stateFlow = store.initStoreStateFlow(this, keys) + val stateFlow = store.launchStore(this, keys) stateFlow.test { val state1 = awaitItem() diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/PagingTests.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/PagingTests.kt index 9622795b9..1731940cc 100644 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/PagingTests.kt +++ b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/PagingTests.kt @@ -1,5 +1,6 @@ package org.mobilenativefoundation.store.paging5 +import app.cash.turbine.test import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope @@ -24,11 +25,18 @@ class PagingTests { val key1 = PostKey.Cursor("1", 10) val key2 = PostKey.Cursor("11", 10) + val keys = flowOf(key1, key2) - flowOf(key1, key2).collect { key -> - val state = store.updateStoreState(StoreState.Initial, key) - assertIs>(state) - assertEquals(10, state.data.posts.size) + val stateFlow = store.launchStore(this, keys) + stateFlow.test { + val loadingState = awaitItem() + assertIs(loadingState) + val loadedState1 = awaitItem() + assertIs>(loadedState1) + assertEquals(10, loadedState1.data.posts.size) + val loadedState2 = awaitItem() + assertIs>(loadedState2) + assertEquals(20, loadedState2.data.posts.size) } val cached = store.stream(StoreReadRequest.cached(key1, refresh = false)) diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostData.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostData.kt index 4a12bad09..db4e2dd34 100644 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostData.kt +++ b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostData.kt @@ -1,17 +1,17 @@ package org.mobilenativefoundation.store.paging5.util -import org.mobilenativefoundation.store.paging5.Identifiable +import org.mobilenativefoundation.store.paging5.StoreData import org.mobilenativefoundation.store.paging5.StoreKey -sealed class PostData : Identifiable { - data class Post(val postId: String, val title: String) : Identifiable.Single, PostData() { +sealed class PostData : StoreData { + data class Post(val postId: String, val title: String) : StoreData.Single, PostData() { override val id: String get() = postId } - data class Feed(val posts: List) : Identifiable.Collection, PostData() { + data class Feed(val posts: List) : StoreData.Collection, PostData() { override val items: List get() = posts - override fun copyWith(items: List): Identifiable.Collection = copy(posts = items) - override fun insertItems(type: StoreKey.LoadType, items: List): Identifiable.Collection { + override fun copyWith(items: List): StoreData.Collection = copy(posts = items) + override fun insertItems(type: StoreKey.LoadType, items: List): StoreData.Collection { return when (type) { StoreKey.LoadType.APPEND -> {