diff --git a/paging/build.gradle.kts b/paging/build.gradle.kts deleted file mode 100644 index 6174abdd8..000000000 --- a/paging/build.gradle.kts +++ /dev/null @@ -1,125 +0,0 @@ -import com.vanniktech.maven.publish.SonatypeHost -import org.jetbrains.dokka.gradle.DokkaTask - -plugins { - kotlin("multiplatform") - kotlin("plugin.serialization") - id("com.android.library") - id("com.vanniktech.maven.publish") - id("org.jetbrains.dokka") - id("org.jetbrains.kotlinx.kover") - id("co.touchlab.faktory.kmmbridge") - `maven-publish` - kotlin("native.cocoapods") - id("kotlinx-atomicfu") -} - -kotlin { - android() - jvm() - iosArm64() - iosX64() - linuxX64() - iosSimulatorArm64() - js { - browser() - nodejs() - } - cocoapods { - summary = "Store5/Paging" - homepage = "https://github.com/MobileNativeFoundation/Store" - ios.deploymentTarget = "13" - version = libs.versions.store.get() - } - - sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlin.stdlib) - implementation(project(":store")) - implementation(project(":cache")) - api(project(":core")) - implementation(libs.kotlinx.coroutines.core) - } - } - - val androidMain by getting - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(libs.turbine) - implementation(libs.kotlinx.coroutines.test) - } - } - } - - jvmToolchain(11) -} - -android { - namespace = "org.mobilenativefoundation.store.paging5" - - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - compileSdk = 34 - - defaultConfig { - minSdk = 24 - targetSdk = 34 - } - - lint { - disable += "ComposableModifierFactory" - disable += "ModifierFactoryExtensionFunction" - disable += "ModifierFactoryReturnType" - disable += "ModifierFactoryUnreferencedReceiver" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} - -tasks.withType().configureEach { - dokkaSourceSets.configureEach { - reportUndocumented.set(false) - skipDeprecated.set(true) - jdkVersion.set(11) - } -} - -mavenPublishing { - publishToMavenCentral(SonatypeHost.S01) - signAllPublications() -} - -addGithubPackagesRepository() -kmmbridge { - githubReleaseArtifacts() - githubReleaseVersions() - versionPrefix.set(libs.versions.store.get()) - spm() -} - -koverMerged { - enable() - - xmlReport { - onCheck.set(true) - reportFile.set(layout.projectDirectory.file("kover/coverage.xml")) - } - - htmlReport { - onCheck.set(true) - reportDir.set(layout.projectDirectory.dir("kover/html")) - } - - verify { - onCheck.set(true) - } -} - -atomicfu { - transformJvm = false - transformJs = false -} diff --git a/paging/config/ktlint/baseline.xml b/paging/config/ktlint/baseline.xml deleted file mode 100644 index 981420778..000000000 --- a/paging/config/ktlint/baseline.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/paging/docs/design_doc.md b/paging/docs/design_doc.md deleted file mode 100644 index 493a95ffb..000000000 --- a/paging/docs/design_doc.md +++ /dev/null @@ -1,246 +0,0 @@ -# Technical Design Doc: Native Paging Support in Store5 - -## Context and Scope -Feature request: [MobileNativeFoundation/Store#250](https://github.com/MobileNativeFoundation/Store/issues/250) - -This proposal addresses the need for paging support in Store. This enhancement aims to provide a simple, efficient, and flexible way to handle complex operations on large datasets. - -## Goals and Non-Goals -### Goals -- Provide native support for page-based and cursor-based fetches, handling both single items and collections. -- Enable read and write operations within a paging store. -- Support complex loading and fetching operations such as sorting and filtering. -- Ensure thread safety and concurrency support. -- Layer on top of existing Store APIs: no breaking changes! -### Non-Goals -- Integration with Paging3. -- Providing a one-size-fits-all solution: our approach should be flexible to cater to different use cases. - -## The Actual Design - -### APIs -#### StoreKey -An interface that defines keys used by Store for data-fetching operations. Allows Store to load individual items and collections of items. Provides mechanisms for ID-based fetch, page-based fetch, and cursor-based fetch. Includes options for sorting and filtering. - -```kotlin - interface StoreKey { - interface Single : StoreKey { - val id: Id - } - interface Collection : StoreKey { - val insertionStrategy: InsertionStrategy - interface Page : Collection { - val page: Int - val size: Int - val sort: Sort? - val filters: List>? - } - interface Cursor : Collection { - val cursor: Id? - val size: Int - val sort: Sort? - val filters: List>? - } - } - } -``` - -#### StoreData -An interface that defines items that can be uniquely identified. 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. - -```kotlin - interface StoreData { - interface Single : StoreData { - val id: Id - } - interface Collection> : StoreData { - val items: List - fun copyWith(items: List): Collection - fun insertItems(strategy: InsertionStrategy, items: List): Collection - } - } -``` - -#### KeyProvider -An interface to derive keys based on provided data. `StoreMultiCache` depends on `KeyProvider` to: - -1. Derive a single key for a collection item based on the collection’s key and that item’s value. -2. Insert a single item into the correct collection based on its key and value. - -```kotlin - interface KeyProvider> { - fun from(key: StoreKey.Collection, value: Single): StoreKey.Single - fun from(key: StoreKey.Single, value: Single): StoreKey.Collection - } -``` - -### Implementations - -#### StoreMultiCache -Thread-safe caching system with collection decomposition. Manages data with utility functions to get, invalidate, and add items to the cache. Depends on `StoreMultiCacheAccessor` for internal data management. Should be used instead of `MultiCache`. - -```kotlin - class StoreMultiCache, Single : StoreData.Single, Collection : StoreData.Collection, Output : StoreData>( - private val keyProvider: KeyProvider, - singlesCache: Cache, Single> = CacheBuilder, Single>().build(), - collectionsCache: Cache, Collection> = CacheBuilder, Collection>().build(), - ): Cache -``` - -#### StoreMultiCacheAccessor -Thread-safe intermediate data manager for a caching system supporting list decomposition. Tracks keys for rapid data retrieval and modification. - -#### LaunchPagingStore -Main entry point for the paging mechanism. This will launch and manage a `StateFlow` that reflects the current state of the Store. - -```kotlin - fun , Output : StoreData> Store.launchPagingStore( - scope: CoroutineScope, - keys: Flow, - ): StateFlow> - - @OptIn(ExperimentalStoreApi::class) - fun , Output : StoreData> MutableStore.launchPagingStore( - scope: CoroutineScope, - keys: Flow, - ): StateFlow> -``` - -## Usage -### StoreKey Example -```kotlin - sealed class ExampleKey : StoreKey { - data class Cursor( - override val cursor: String?, - override val size: Int, - override val sort: StoreKey.Sort? = null, - override val filters: List>? = null, - override val insertionStrategy: InsertionStrategy = InsertionStrategy.APPEND - ) : StoreKey.Collection.Cursor, ExampleKey() - - data class Single( - override val id: String - ) : StoreKey.Single, ExampleKey() - } -``` - -### StoreData Example -```kotlin - sealed class ExampleData : StoreData { - data class Single(val postId: String, val title: String) : StoreData.Single, ExampleData() { - override val id: String get() = postId - } - - data class Collection(val singles: List) : StoreData.Collection, ExampleData() { - override val items: List get() = singles - override fun copyWith(items: List): StoreData.Collection = copy(singles = items) - override fun insertItems(strategy: InsertionStrategy, items: List): StoreData.Collection { - - return when (strategy) { - InsertionStrategy.APPEND -> { - val updatedItems = items.toMutableList() - updatedItems.addAll(singles) - copyWith(items = updatedItems) - } - - InsertionStrategy.PREPEND -> { - val updatedItems = singles.toMutableList() - updatedItems.addAll(items) - copyWith(items = updatedItems) - } - } - } - } - } -``` - -### LaunchPagingStore Example -```kotlin - @OptIn(ExperimentalStoreApi::class) - class ExampleViewModel( - private val store: MutableStore, - private val coroutineScope: CoroutineScope = viewModelScope, - private val loadSize: Int = DEFAULT_LOAD_SIZE - ) : ViewModel() { - - private val keys = MutableStateFlow(ExampleKey.Cursor(null, loadSize)) - private val _loading = MutableStateFlow(false) - private val _error = MutableStateFlow(null) - - val stateFlow = store.launchPagingStore(coroutineScope, keys) - val loading: StateFlow = _loading.asStateFlow() - val error: StateFlow = _error.asStateFlow() - - init { - TODO("Observe loading and error states and perform any other necessary initializations") - } - - fun loadMore() { - if (_loading.value) return // Prevent loading more if already loading - _loading.value = true - - coroutineScope.launch { - try { - val currentKey = keys.value - val currentCursor = currentKey.cursor - val nextCursor = determineNextCursor(currentCursor) - val nextKey = currentKey.copy(cursor = nextCursor) - keys.value = nextKey - } catch (e: Throwable) { - _error.value = e - } finally { - _loading.value = false - } - } - } - - fun write(key: ExampleKey.Single, value: ExampleData.Single) { - coroutineScope.launch { - try { - store.write(StoreWriteRequest.of(key, value)) - } catch (e: Throwable) { - _error.value = e - } - } - } - - private fun determineNextCursor(cursor: String?): String? { - // Implementation based on specific use case - // Return the next cursor or null if there are no more items to load - TODO("Provide an implementation or handle accordingly") - } - - companion object { - private const val DEFAULT_LOAD_SIZE = 100 - } - } -``` - -## Degree of Constraint -- Data items must implement the `StoreData` interface, ensuring they can be uniquely identified. -- Keys for loading data must implement the `StoreKey` interface. - -## Deprecations -- MultiCache -- Identifiable - -## Alternatives Considered -### Tailored Solution for Paging -#### Direct integration with Paging3 -Paging3 doesn’t have built-in support for: -- Singles and collections -- Write operations -- Sorting and filtering operations - -### Custom `StoreKey` and `StoreData` Structures -#### Loose Typing -#### Annotations and Reflection -#### Functional Programming Approach - -## Cross-Cutting Concerns -- Will Paging3 extensions be a maintenance nightmare? -- Will these APIs be simpler than Paging3? - -## Future Directions -- Bindings for Paging3 (follow-up PR) -- Support for KMP Compose UI (follow-up PR) \ No newline at end of file diff --git a/paging/gradle.properties b/paging/gradle.properties deleted file mode 100644 index 75852bdb3..000000000 --- a/paging/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -POM_NAME=org.mobilenativefoundation.store -POM_ARTIFACT_ID=paging5 -POM_PACKAGING=jar \ No newline at end of file diff --git a/paging/kover/coverage.xml b/paging/kover/coverage.xml deleted file mode 100644 index a23845937..000000000 --- a/paging/kover/coverage.xml +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/paging/src/androidMain/AndroidManifest.xml b/paging/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 8072ee00d..000000000 --- a/paging/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStore.kt b/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStore.kt deleted file mode 100644 index 3fee41c26..000000000 --- a/paging/src/commonMain/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStore.kt +++ /dev/null @@ -1,116 +0,0 @@ -@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.flow.drop -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import org.mobilenativefoundation.store.core5.ExperimentalStoreApi -import org.mobilenativefoundation.store.core5.StoreData -import org.mobilenativefoundation.store.core5.StoreKey -import org.mobilenativefoundation.store.store5.MutableStore -import org.mobilenativefoundation.store.store5.Store -import org.mobilenativefoundation.store.store5.StoreReadRequest -import org.mobilenativefoundation.store.store5.StoreReadResponse - -private class StopProcessingException : Exception() - -/** - * 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 stream A lambda that invokes [Store.stream]. - * @return A read-only [StateFlow] reflecting the state of the Store. - */ -@ExperimentalStoreApi -private fun , Output : StoreData> launchPagingStore( - scope: CoroutineScope, - keys: Flow, - stream: (key: Key) -> Flow>, -): StateFlow> { - val stateFlow = MutableStateFlow>(StoreReadResponse.Initial) - - scope.launch { - try { - val firstKey = keys.first() - if (firstKey !is StoreKey.Collection<*>) throw IllegalArgumentException("Invalid key type") - - stream(firstKey).collect { response -> - if (response is StoreReadResponse.Data) { - val joinedDataResponse = joinData(firstKey, stateFlow.value, response) - stateFlow.emit(joinedDataResponse) - } else { - stateFlow.emit(response) - } - - if (response is StoreReadResponse.Data || - response is StoreReadResponse.Error || - response is StoreReadResponse.NoNewData - ) { - throw StopProcessingException() - } - } - } catch (_: StopProcessingException) { - } - - keys.drop(1).collect { key -> - if (key !is StoreKey.Collection<*>) throw IllegalArgumentException("Invalid key type") - val firstDataResponse = stream(key).first { it.dataOrNull() != null } as StoreReadResponse.Data - val joinedDataResponse = joinData(key, stateFlow.value, firstDataResponse) - stateFlow.emit(joinedDataResponse) - } - } - - return stateFlow.asStateFlow() -} - -/** - * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. - * @see [launchPagingStore]. - */ -@ExperimentalStoreApi -fun , Output : StoreData> Store.launchPagingStore( - scope: CoroutineScope, - keys: Flow, -): StateFlow> { - return launchPagingStore(scope, keys) { key -> - this.stream(StoreReadRequest.fresh(key)) - } -} - -/** - * Initializes and returns a [StateFlow] that reflects the state of the [Store], updating by a flow of provided keys. - * @see [launchPagingStore]. - */ -@ExperimentalStoreApi -fun , Output : StoreData> MutableStore.launchPagingStore( - scope: CoroutineScope, - keys: Flow, -): StateFlow> { - return launchPagingStore(scope, keys) { key -> - this.stream(StoreReadRequest.fresh(key)) - } -} - -@ExperimentalStoreApi -private fun , Output : StoreData> joinData( - key: Key, - prevResponse: StoreReadResponse, - currentResponse: StoreReadResponse.Data, -): StoreReadResponse.Data { - val lastOutput = - when (prevResponse) { - is StoreReadResponse.Data -> prevResponse.value as? StoreData.Collection> - else -> null - } - - val currentData = currentResponse.value as StoreData.Collection> - - val joinedOutput = (lastOutput?.insertItems(key.insertionStrategy, currentData.items) ?: currentData) as Output - return StoreReadResponse.Data(joinedOutput, currentResponse.origin) -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStoreTests.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStoreTests.kt deleted file mode 100644 index 3ab4f97c7..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/LaunchPagingStoreTests.kt +++ /dev/null @@ -1,174 +0,0 @@ -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 -import kotlinx.coroutines.test.runTest -import org.mobilenativefoundation.store.core5.ExperimentalStoreApi -import org.mobilenativefoundation.store.paging5.util.FakePostApi -import org.mobilenativefoundation.store.paging5.util.FakePostDatabase -import org.mobilenativefoundation.store.paging5.util.PostApi -import org.mobilenativefoundation.store.paging5.util.PostData -import org.mobilenativefoundation.store.paging5.util.PostDatabase -import org.mobilenativefoundation.store.paging5.util.PostKey -import org.mobilenativefoundation.store.paging5.util.PostPutRequestResult -import org.mobilenativefoundation.store.paging5.util.PostStoreFactory -import org.mobilenativefoundation.store.store5.MutableStore -import org.mobilenativefoundation.store.store5.StoreReadRequest -import org.mobilenativefoundation.store.store5.StoreReadResponse -import org.mobilenativefoundation.store.store5.StoreReadResponseOrigin -import org.mobilenativefoundation.store.store5.StoreWriteRequest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs - -@OptIn(ExperimentalStoreApi::class) -class LaunchPagingStoreTests { - private val testScope = TestScope() - - private val userId = "123" - private lateinit var api: PostApi - private lateinit var db: PostDatabase - private lateinit var store: MutableStore - - @BeforeTest - fun setup() { - api = FakePostApi() - db = FakePostDatabase(userId) - val factory = PostStoreFactory(api, db) - store = factory.create() - } - - @Test - fun transitionFromInitialToData() = - testScope.runTest { - val key = PostKey.Cursor("1", 10) - val keys = flowOf(key) - val stateFlow = store.launchPagingStore(this, keys) - - stateFlow.test { - val state1 = awaitItem() - assertIs(state1) - val state2 = awaitItem() - assertIs(state2) - val state3 = awaitItem() - assertIs>(state3) - expectNoEvents() - } - } - - @Test - fun multipleValidKeysEmittedInSuccession() = - testScope.runTest { - val key1 = PostKey.Cursor("1", 10) - val key2 = PostKey.Cursor("11", 10) - val keys = flowOf(key1, key2) - val stateFlow = store.launchPagingStore(this, keys) - - stateFlow.test { - val state1 = awaitItem() - assertIs(state1) - val state2 = awaitItem() - assertIs(state2) - val state3 = awaitItem() - assertIs>(state3) - assertEquals("1", state3.value.posts[0].postId) - - val state4 = awaitItem() - assertIs>(state4) - assertEquals("11", state4.value.posts[0].postId) - assertEquals("1", state4.value.posts[10].postId) - val data4 = state4.value - assertIs(data4) - assertEquals(20, data4.items.size) - expectNoEvents() - } - } - - @Test - fun sameKeyEmittedMultipleTimes() = - testScope.runTest { - val key = PostKey.Cursor("1", 10) - val keys = flowOf(key, key) - val stateFlow = store.launchPagingStore(this, keys) - - stateFlow.test { - val state1 = awaitItem() - assertIs(state1) - val state2 = awaitItem() - assertIs(state2) - val state3 = awaitItem() - assertIs>(state3) - expectNoEvents() - } - } - - @Test - fun multipleKeysWithReadsAndWrites() = - testScope.runTest { - val api = FakePostApi() - val db = FakePostDatabase(userId) - val factory = PostStoreFactory(api = api, db = db) - val store = factory.create() - - val key1 = PostKey.Cursor("1", 10) - val key2 = PostKey.Cursor("11", 10) - val keys = flowOf(key1, key2) - - val stateFlow = store.launchPagingStore(this, keys) - stateFlow.test { - val initialState = awaitItem() - assertIs(initialState) - val loadingState = awaitItem() - assertIs(loadingState) - val loadedState1 = awaitItem() - assertIs>(loadedState1) - val data1 = loadedState1.value - assertEquals(10, data1.posts.size) - val loadedState2 = awaitItem() - assertIs>(loadedState2) - val data2 = loadedState2.value - assertEquals(20, data2.posts.size) - } - - val cached = - store.stream(StoreReadRequest.cached(key1, refresh = false)) - .first { it.dataOrNull() != null } - assertIs>(cached) - assertEquals(StoreReadResponseOrigin.Cache, cached.origin) - val data = cached.requireData() - assertIs(data) - assertEquals(10, data.posts.size) - - val cached2 = - store.stream(StoreReadRequest.cached(PostKey.Single("2"), refresh = false)) - .first { it.dataOrNull() != null } - assertIs>(cached2) - assertEquals(StoreReadResponseOrigin.Cache, cached2.origin) - val data2 = cached2.requireData() - assertIs(data2) - assertEquals("2", data2.title) - - store.write(StoreWriteRequest.of(PostKey.Single("2"), PostData.Post("2", "2-modified"))) - - val cached3 = - store.stream(StoreReadRequest.cached(PostKey.Single("2"), refresh = false)) - .first { it.dataOrNull() != null } - assertIs>(cached3) - assertEquals(StoreReadResponseOrigin.Cache, cached3.origin) - val data3 = cached3.requireData() - assertIs(data3) - assertEquals("2-modified", data3.title) - - val cached4 = - store.stream(StoreReadRequest.cached(PostKey.Cursor("1", 10), refresh = false)) - .first { it.dataOrNull() != null } - assertIs>(cached4) - assertEquals(StoreReadResponseOrigin.Cache, cached4.origin) - val data4 = cached4.requireData() - assertIs(data4) - assertEquals("2-modified", data4.posts[1].title) - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostApi.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostApi.kt deleted file mode 100644 index 9e4343951..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostApi.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -class FakePostApi : PostApi { - private val posts = mutableMapOf() - private val postsList = mutableListOf() - - init { - (1..100).forEach { - val id = it.toString() - posts[id] = PostData.Post(id, id) - postsList.add(PostData.Post(id, id)) - } - } - - override suspend fun get(postId: String): PostGetRequestResult { - val post = posts[postId] - return if (post != null) { - PostGetRequestResult.Data(post) - } else { - PostGetRequestResult.Error.Message("Post $postId was not found") - } - } - - override suspend fun get( - cursor: String?, - size: Int, - ): FeedGetRequestResult { - val firstIndexInclusive = postsList.indexOfFirst { it.postId == cursor } - val lastIndexExclusive = firstIndexInclusive + size - val posts = postsList.subList(firstIndexInclusive, lastIndexExclusive) - return FeedGetRequestResult.Data(PostData.Feed(posts = posts)) - } - - override suspend fun put(post: PostData.Post): PostPutRequestResult { - posts.put(post.id, post) - return PostPutRequestResult.Data(post) - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostDatabase.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostDatabase.kt deleted file mode 100644 index ac8fa76a8..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FakePostDatabase.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -class FakePostDatabase(private val userId: String) : PostDatabase { - private val posts = mutableMapOf() - private val feeds = mutableMapOf() - - override fun add(post: PostData.Post) { - posts[post.id] = post - - val nextFeed = - feeds[userId]?.posts?.map { - if (it.postId == post.postId) { - post - } else { - it - } - } - - nextFeed?.let { - feeds[userId] = PostData.Feed(nextFeed) - } - } - - override fun add(feed: PostData.Feed) { - feeds[userId] = feed - } - - override fun findPostByPostId(postId: String): PostData.Post? { - return posts[postId] - } - - override fun findFeedByUserId( - cursor: String?, - size: Int, - ): PostData.Feed? { - val feed = feeds[userId] - return feed - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FeedGetRequestResult.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FeedGetRequestResult.kt deleted file mode 100644 index 073efc92b..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/FeedGetRequestResult.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -sealed class FeedGetRequestResult { - data class Data(val data: PostData.Feed) : FeedGetRequestResult() - - sealed class Error : FeedGetRequestResult() { - data class Message(val error: String) : Error() - - data class Exception(val error: kotlin.Exception) : Error() - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostApi.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostApi.kt deleted file mode 100644 index 182bbe047..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostApi.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -interface PostApi { - suspend fun get(postId: String): PostGetRequestResult - - suspend fun get( - cursor: String?, - size: Int, - ): FeedGetRequestResult - - suspend fun put(post: PostData.Post): PostPutRequestResult -} 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 deleted file mode 100644 index 686e12270..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostData.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -import org.mobilenativefoundation.store.core5.InsertionStrategy -import org.mobilenativefoundation.store.core5.StoreData - -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) : StoreData.Collection, PostData() { - override val items: List get() = posts - - override fun copyWith(items: List): StoreData.Collection = copy(posts = items) - - override fun insertItems( - strategy: InsertionStrategy, - items: List, - ): StoreData.Collection { - return when (strategy) { - InsertionStrategy.APPEND -> { - val updatedItems = items.toMutableList() - updatedItems.addAll(posts) - copyWith(items = updatedItems) - } - - InsertionStrategy.PREPEND -> { - val updatedItems = posts.toMutableList() - updatedItems.addAll(items) - copyWith(items = updatedItems) - } - - InsertionStrategy.REPLACE -> { - copyWith(items = posts) - } - } - } - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostDatabase.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostDatabase.kt deleted file mode 100644 index a9f229c97..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostDatabase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -interface PostDatabase { - fun add(post: PostData.Post) - - fun add(feed: PostData.Feed) - - fun findPostByPostId(postId: String): PostData.Post? - - fun findFeedByUserId( - cursor: String?, - size: Int, - ): PostData.Feed? -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostGetRequestResult.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostGetRequestResult.kt deleted file mode 100644 index a5564afc0..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostGetRequestResult.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -sealed class PostGetRequestResult { - data class Data(val data: PostData.Post) : PostGetRequestResult() - - sealed class Error : PostGetRequestResult() { - data class Message(val error: String) : Error() - - data class Exception(val error: kotlin.Exception) : Error() - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostKey.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostKey.kt deleted file mode 100644 index 7d7a54f08..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostKey.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -import org.mobilenativefoundation.store.core5.InsertionStrategy -import org.mobilenativefoundation.store.core5.StoreKey - -sealed class PostKey : StoreKey { - data class Cursor( - override val cursor: String?, - override val size: Int, - override val sort: StoreKey.Sort? = null, - override val filters: List>? = null, - override val insertionStrategy: InsertionStrategy = InsertionStrategy.APPEND, - ) : StoreKey.Collection.Cursor, PostKey() - - data class Single( - override val id: String, - ) : StoreKey.Single, PostKey() -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostPutRequestResult.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostPutRequestResult.kt deleted file mode 100644 index 36cea329a..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostPutRequestResult.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.mobilenativefoundation.store.paging5.util - -sealed class PostPutRequestResult { - data class Data(val data: PostData.Post) : PostPutRequestResult() - - sealed class Error : PostPutRequestResult() { - data class Message(val error: String) : Error() - - data class Exception(val error: kotlin.Exception) : Error() - } -} diff --git a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostStoreFactory.kt b/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostStoreFactory.kt deleted file mode 100644 index b3e014a83..000000000 --- a/paging/src/commonTest/kotlin/org/mobilenativefoundation/store/paging5/util/PostStoreFactory.kt +++ /dev/null @@ -1,143 +0,0 @@ -@file:OptIn(ExperimentalStoreApi::class) - -package org.mobilenativefoundation.store.paging5.util - -import kotlinx.coroutines.flow.flow -import org.mobilenativefoundation.store.cache5.Cache -import org.mobilenativefoundation.store.cache5.StoreMultiCache -import org.mobilenativefoundation.store.core5.ExperimentalStoreApi -import org.mobilenativefoundation.store.core5.KeyProvider -import org.mobilenativefoundation.store.core5.StoreKey -import org.mobilenativefoundation.store.store5.Converter -import org.mobilenativefoundation.store.store5.Fetcher -import org.mobilenativefoundation.store.store5.MutableStore -import org.mobilenativefoundation.store.store5.SourceOfTruth -import org.mobilenativefoundation.store.store5.StoreBuilder -import org.mobilenativefoundation.store.store5.Updater -import org.mobilenativefoundation.store.store5.UpdaterResult -import kotlin.math.floor - -class PostStoreFactory(private val api: PostApi, private val db: PostDatabase) { - private fun createFetcher(): Fetcher = - Fetcher.of { key -> - when (key) { - is PostKey.Single -> { - when (val result = api.get(key.id)) { - is PostGetRequestResult.Data -> { - result.data - } - - is PostGetRequestResult.Error.Exception -> { - throw Throwable(result.error) - } - - is PostGetRequestResult.Error.Message -> { - throw Throwable(result.error) - } - } - } - - is PostKey.Cursor -> { - when (val result = api.get(key.cursor, key.size)) { - is FeedGetRequestResult.Data -> { - result.data - } - - is FeedGetRequestResult.Error.Exception -> { - throw Throwable(result.error) - } - - is FeedGetRequestResult.Error.Message -> { - throw Throwable(result.error) - } - } - } - } - } - - private fun createSourceOfTruth(): SourceOfTruth = - SourceOfTruth.of( - reader = { key -> - flow { - when (key) { - is PostKey.Single -> { - val post = db.findPostByPostId(key.id) - emit(post) - } - - is PostKey.Cursor -> { - val feed = db.findFeedByUserId(key.cursor, key.size) - emit(feed) - } - } - } - }, - writer = { key, data -> - when { - key is PostKey.Single && data is PostData.Post -> { - db.add(data) - } - - key is PostKey.Cursor && data is PostData.Feed -> { - db.add(data) - } - } - }, - ) - - private fun createConverter(): Converter = - Converter.Builder() - .fromNetworkToLocal { it } - .fromOutputToLocal { it } - .build() - - private fun createUpdater(): Updater = - Updater.by( - post = { key, data -> - when { - key is PostKey.Single && data is PostData.Post -> { - when (val result = api.put(data)) { - is PostPutRequestResult.Data -> UpdaterResult.Success.Typed(result) - is PostPutRequestResult.Error.Exception -> UpdaterResult.Error.Exception(result.error) - is PostPutRequestResult.Error.Message -> UpdaterResult.Error.Message(result.error) - } - } - - else -> UpdaterResult.Error.Message("Unsupported: key: ${key::class}, data: ${data::class}") - } - }, - ) - - private fun createPagingCacheKeyProvider(): KeyProvider = - object : KeyProvider { - override fun fromCollection( - key: StoreKey.Collection, - value: PostData.Post, - ): StoreKey.Single { - return PostKey.Single(value.postId) - } - - override fun fromSingle( - key: StoreKey.Single, - value: PostData.Post, - ): StoreKey.Collection { - val id = value.postId.toInt() - val cursor = (floor(id.toDouble() / 10) * 10) + 1 - return PostKey.Cursor(cursor.toInt().toString(), 10) - } - } - - private fun createMemoryCache(): Cache = StoreMultiCache(createPagingCacheKeyProvider()) - - fun create(): MutableStore = - StoreBuilder.from( - fetcher = createFetcher(), - sourceOfTruth = createSourceOfTruth(), - memoryCache = createMemoryCache(), - ).toMutableStoreBuilder( - converter = createConverter(), - ).build( - updater = createUpdater(), - bookkeeper = null, - ) -} diff --git a/settings.gradle b/settings.gradle index ded337faa..24415b964 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,5 +14,4 @@ include ':store' include ':cache' include ':multicast' include ':rx2' -include ':paging' include ':core'