Skip to content

Commit 3c72e4c

Browse files
committed
Add Paging
Signed-off-by: mramotar <[email protected]>
1 parent b7aa329 commit 3c72e4c

File tree

70 files changed

+2612
-634
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2612
-634
lines changed

cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCache.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package org.mobilenativefoundation.store.cache5
44

5+
import org.mobilenativefoundation.store.core5.ExperimentalStoreApi
56
import org.mobilenativefoundation.store.core5.KeyProvider
67
import org.mobilenativefoundation.store.core5.StoreData
78
import org.mobilenativefoundation.store.core5.StoreKey
@@ -12,7 +13,8 @@ import org.mobilenativefoundation.store.core5.StoreKey
1213
* Depends on [StoreMultiCacheAccessor] for internal data management.
1314
* @see [Cache].
1415
*/
15-
class StoreMultiCache<Id : Any, Key : StoreKey<Id>, Single : StoreData.Single<Id>, Collection : StoreData.Collection<Id, Single>, Output : StoreData<Id>>(
16+
@OptIn(ExperimentalStoreApi::class)
17+
class StoreMultiCache<Id : Any, Key : StoreKey<Id>, Single : StoreData.Single<Id>, Collection : StoreData.Collection<Id, *, Single>, Output : StoreData<Id>>(
1618
private val keyProvider: KeyProvider<Id, Single>,
1719
singlesCache: Cache<StoreKey.Single<Id>, Single> = CacheBuilder<StoreKey.Single<Id>, Single>().build(),
1820
collectionsCache: Cache<StoreKey.Collection<Id>, Collection> = CacheBuilder<StoreKey.Collection<Id>, Collection>().build(),

cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.mobilenativefoundation.store.cache5
22

33
import kotlinx.atomicfu.locks.SynchronizedObject
44
import kotlinx.atomicfu.locks.synchronized
5+
import org.mobilenativefoundation.store.core5.ExperimentalStoreApi
56
import org.mobilenativefoundation.store.core5.StoreData
67
import org.mobilenativefoundation.store.core5.StoreKey
78

@@ -20,7 +21,8 @@ import org.mobilenativefoundation.store.core5.StoreKey
2021
* @property singlesCache The cache used to store single data items.
2122
* @property collectionsCache The cache used to store collections of data items.
2223
*/
23-
class StoreMultiCacheAccessor<Id : Any, Collection : StoreData.Collection<Id, Single>, Single : StoreData.Single<Id>>(
24+
@ExperimentalStoreApi
25+
class StoreMultiCacheAccessor<Id : Any, Collection : StoreData.Collection<Id, *, Single>, Single : StoreData.Single<Id>>(
2426
private val singlesCache: Cache<StoreKey.Single<Id>, Single>,
2527
private val collectionsCache: Cache<StoreKey.Collection<Id>, Collection>,
2628
) : SynchronizedObject() {

core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/StoreData.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ interface StoreData<out Id : Any> {
1818
/**
1919
* Represents a collection of identifiable items.
2020
*/
21-
interface Collection<Id : Any, S : Single<Id>> : StoreData<Id> {
22-
val items: List<S>
21+
interface Collection<Id : Any, CK : StoreKey.Collection<Id>, SO : Single<Id>> : StoreData<Id> {
22+
val items: List<SO>
23+
val itemsBefore: Int?
24+
val itemsAfter: Int?
25+
val prevKey: CK
26+
val nextKey: CK?
2327

2428
/**
2529
* Returns a new collection with the updated items.
2630
*/
27-
fun copyWith(items: List<S>): Collection<Id, S>
31+
fun copyWith(items: List<SO>): Collection<Id, *, SO>
2832

2933
/**
3034
* Inserts items to the existing collection and returns the updated collection.
3135
*/
32-
fun insertItems(strategy: InsertionStrategy, items: List<S>): Collection<Id, S>
36+
fun insertItems(strategy: InsertionStrategy, items: List<SO>): Collection<Id, *, SO>
3337
}
3438
}

core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/StoreKey.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface StoreKey<out Id : Any> {
1212
/**
1313
* Represents a key for fetching an individual item.
1414
*/
15-
interface Single<Id : Any> : StoreKey<Id> {
15+
interface Single<out Id : Any> : StoreKey<Id> {
1616
val id: Id
1717
}
1818

gradle/libs.versions.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ktlintGradle = "10.2.1"
1010
jacocoGradlePlugin = "0.8.7"
1111
mavenPublishPlugin = "0.22.0"
1212
moleculeGradlePlugin = "1.2.1"
13-
pagingCompose = "3.3.0-alpha02"
13+
pagingCompose = "3.3.0-alpha03"
1414
pagingRuntime = "3.2.1"
1515
spotlessPluginGradle = "6.4.1"
1616
junit = "4.13.2"
@@ -23,6 +23,8 @@ ktlint = "0.39.0"
2323
kover = "0.6.0"
2424
store = "5.1.0-alpha02"
2525
truth = "1.1.3"
26+
jetbrains-compose = "1.5.12"
27+
2628

2729
[libraries]
2830
android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
@@ -55,3 +57,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
5557
google-truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }
5658
touchlab-kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" }
5759
turbine = "app.cash.turbine:turbine:1.0.0"
60+
61+
62+
[plugins]
63+
compose = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }

paging/README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Paging
2+
3+
This is a Kotlin Multiplatform paging library developed by the Mobile Native Foundation. It provides a flexible and
4+
customizable way to implement pagination in Kotlin Multiplatform projects.
5+
6+
## Features
7+
8+
- Flexible and customizable KMP pagination with builder pattern
9+
- Supports custom actions, reducers, middleware, and post reducer effects
10+
- Supports customization of strategies for error handling, page aggregating, and data fetching
11+
- Supports retrying failed requests
12+
- Provides default implementations for common scenarios
13+
14+
## Getting Started
15+
16+
To use Paging in your project, follow these steps:
17+
18+
1. Add the library dependency to your project:
19+
20+
```kotlin
21+
dependencies {
22+
implementation("org.mobilenativefoundation.store:paging:5.1.0")
23+
}
24+
```
25+
26+
2. Creating a `PagingConfig` object to configure the pagination behavior
27+
28+
```kotlin
29+
val pagingConfig = PagingConfig(
30+
pageSize = 20,
31+
prefetchDistance = 10,
32+
)
33+
```
34+
35+
3. Creating a `PagingSource` with the default paging stream provider:
36+
37+
If you use the default post reducer effects, to provide data for pagination, you need to implement a `PagingSource`. We
38+
provide a `DefaultPagingSource`that you can use as a starting point. You need to provide a `PagingStreamProvider` that
39+
defines how to fetch data for each page. We also provide `MutableStore.defaultPagingStreamProvider()`
40+
and `Store.defaultPagingStreamProvider()` extensions. The default `PagingStreamProvider` delegates to Store and supports
41+
continuous streaming of children items.
42+
43+
```kotlin
44+
val streamProvider = store.defaultPagingStreamProvider(
45+
keyFactory = object : PagingKeyFactory<Int, SingleKey, SingleData> {
46+
override fun createKeyFor(data: SingleData): SingleKey {
47+
// Implement custom logic to create a single key from single data
48+
}
49+
}
50+
)
51+
```
52+
53+
```kotlin
54+
val pagingSource = DefaultPagingSource(
55+
streamProvider = streamProvider
56+
)
57+
```
58+
59+
3. Configuring the Pager using `PagerBuilder`:
60+
61+
```kotlin
62+
val pager =
63+
PagerBuilder<Int, CollectionKey, SingleData, CustomAction, CustomError>(
64+
initialKey = CollectionKey(0),
65+
anchorPosition = anchorPositionFlow,
66+
pagingConfig = pagingConfig
67+
)
68+
.dispatcher(
69+
logger = DefaultLogger(),
70+
) {
71+
72+
// Use the default reducer
73+
defaultReducer {
74+
errorHandlingStrategy(ErrorHandlingStrategy.PassThrough)
75+
pagingBufferMaxSize(100)
76+
}
77+
78+
middlewares(
79+
listOf(
80+
// Add custom middleware
81+
)
82+
)
83+
84+
// Add custom post reducer effects
85+
postReducerEffect<MyPagingState, MyPagingAction>(
86+
state = MyPagingState::class,
87+
action = MyPagingAction::class,
88+
effect = MyPostReducerEffect
89+
)
90+
91+
// Use the default post reducer effects
92+
defaultPostReducerEffects(pagingSource = pagingSource)
93+
}
94+
95+
.build()
96+
```
97+
98+
4. Observing the paging state and dispatching actions:
99+
100+
```kotlin
101+
pager.state.collect { state ->
102+
when (state) {
103+
is PagingState.Data.Idle -> {
104+
// Update UI with loaded data and provide dispatch callback
105+
DataView(pagingItems = state.data) { action: PagingAction.User ->
106+
pager.dispatch(action)
107+
}
108+
}
109+
is PagingState.LoadingInitial -> {
110+
// Show loading indicator
111+
InitialLoadingView()
112+
}
113+
is PagingState.Error -> {
114+
// Handle error state and provide dispatch callback
115+
InitialErrorViewCoordinator(errorState = state) { action: PagingAction.User ->
116+
pager.dispatch(action)
117+
}
118+
}
119+
}
120+
}
121+
```
122+
123+
## Customization
124+
125+
This library provides various customization options to tailor the pagination behavior to your needs:
126+
127+
1. `ErrorHandlingStrategy`: Defines how errors should be handled.
128+
2. `PageAggregatingStrategy`: Specifies how pages should be aggregated.
129+
3. `PageFetchingStrategy`: Determines when and how pages should be fetched.
130+
4. `CustomActionReducer`: Allows handling custom actions in the default reducer.
131+
5. `PagingBufferMaxSize`: Sets the maximum size of the paging buffer.
132+
133+
Apply the custom components when building the `Pager`:
134+
135+
```kotlin
136+
val pager =
137+
PagerBuilder<Int, CollectionKey, SingleData, CustomAction, CustomError>(
138+
// ...
139+
).dispatcher {
140+
defaultReducer {
141+
errorHandlingStrategy(ErrorHandlingStrategy.Ignore)
142+
aggregatingStrategy(MyPageAggregatingStrategy())
143+
pageFetchingStrategy(MyPageFetchingStrategy())
144+
customActionReducer(MyCustomActionReducer())
145+
pagingBufferMaxSize(500)
146+
}
147+
}.build()
148+
```
149+
150+
## Paging Source
151+
152+
If you do not use the default post reducer effects, you will need to implement and provide post reducer effects for
153+
providing data for pagination.
154+
155+
```kotlin
156+
class MyPagingSource : PagingSource<Int, CollectionKey, SingleData> {
157+
override fun stream(params: LoadParams<Int, CollectionKey>): Flow<LoadResult> {
158+
return flow {
159+
// Implement custom loading of paged data based on the provided parameters
160+
// Emit the paged data
161+
}
162+
}
163+
}
164+
```
165+
166+
## Error Handling
167+
168+
This library supports different error handling strategies:
169+
170+
1. Custom reducer: You can add your own reducer and have total control.
171+
2. Custom middleware: You can add custom middleware for handling errors.
172+
3. Custom post reducer effects: You can add custom post reducer effects for handling errors.
173+
4. `CustomActionReducer`: If using our default reducer, you can provide a custom action reducer
174+
in `DefaultReducerBuilder`.
175+
5. `ErrorHandlingStrategy.Ignore`: Ignores errors and continues with the previous state. If using our default reducer,
176+
you can specify this in `DefaultReducerBuilder`.
177+
6. `ErrorHandlingStrategy.PassThrough`: Passes the error to the UI layer for handling. If using our default reducer, you
178+
can specify this in `DefaultReducerBuilder`.
179+
180+
## Retrying Failed Requests
181+
182+
This library also supports different mechanisms for retrying failed requests:
183+
184+
1. Custom middleware: You can add custom middleware for retries.
185+
2. `DefaultRetryLast`: Default retry middleware. You can specify the maximum number of retries when configuring the
186+
pager using the `defaultMiddleware` function in `DispatcherBuilder`.

paging/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ kotlin {
3636
val commonMain by getting {
3737
dependencies {
3838
implementation(libs.kotlin.stdlib)
39-
implementation(project(":store"))
39+
api(project(":store"))
40+
implementation(libs.touchlab.kermit)
4041
implementation(project(":cache"))
4142
api(project(":core"))
4243
implementation(libs.kotlinx.coroutines.core)

0 commit comments

Comments
 (0)