Skip to content

Commit a30255e

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents c13c012 + 50abca6 commit a30255e

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

README.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,172 @@
1+
[![Download](https://api.bintray.com/packages/popalay/maven/Hoard/images/download.svg) ](https://bintray.com/popalay/maven/Hoard/_latestVersion)
2+
[![API](https://img.shields.io/badge/API-14%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=14)
3+
[![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://github.com/Popalay/Hoard/blob/master/LICENSE)
4+
15
# Hoard
26
Kotlin library for reactive data loading
7+
8+
## Main advantages
9+
+ separated persister and fetcher
10+
+ contains fetch policy
11+
+ supports RxJava2
12+
+ persister returns Flowable
13+
+ simple to extend
14+
+ suitable for Unit Testing
15+
+ 100% Kotlin code
16+
17+
## How to add
18+
Add the dependency in your build.gradle:
19+
```groovy
20+
dependencies {
21+
implementation "com.popalay.hoard:hoard:$hoardVersion"
22+
}
23+
```
24+
## Usage
25+
26+
Create Hoard instance and observe changes
27+
```kotlin
28+
GithubUserHoard(githubUserService, githubUserDao)
29+
.get(GithubUserHoard.Key.All, dataIsEmpty = { it.isEmpty() })
30+
.subscribeOn(Schedulers.io())
31+
.observeOn(AndroidSchedulers.mainThread())
32+
.subscribe(adapter::submitList, ::handleError)
33+
```
34+
35+
## [Sample](app/src/main/java/com/github/popalay/hoard/)
36+
37+
Hoard implementation
38+
```kotlin
39+
class GithubUserHoard(
40+
service: GithubUserService,
41+
dao: GithubUserDao
42+
) : Hoard<List<GithubUser>, GithubUserHoard.Key>(
43+
fetcher = GithubUserFetcher(service),
44+
persister = GithubUserPersister(dao),
45+
fetchPolicy = FetchPolicyFactory.timeFetchPolicy(TimeFetchPolicy.MEDIUM_DELAY)
46+
) {
47+
48+
sealed class Key : com.github.popalay.hoard.Key {
49+
50+
object All : Key()
51+
}
52+
}
53+
```
54+
Persister implementation
55+
```kotlin
56+
private class GithubUserPersister(
57+
private val dao: GithubUserDao
58+
) : Persister<List<GithubUser>, GithubUserHoard.Key> {
59+
60+
override fun read(key: GithubUserHoard.Key): Flowable<List<GithubUser>> = with(key) {
61+
when (this) {
62+
GithubUserHoard.Key.All -> dao.findAll()
63+
}
64+
}
65+
66+
override fun write(data: List<GithubUser>, key: GithubUserHoard.Key): Completable = Completable.fromAction {
67+
dao.deleteAndInsert(data)
68+
}
69+
70+
override fun isNotEmpty(key: GithubUserHoard.Key): Single<Boolean> = with(key) {
71+
when (this) {
72+
GithubUserHoard.Key.All -> dao.isNotEmpty().toSingle(false)
73+
}
74+
}
75+
}
76+
```
77+
Fetcher implementation
78+
```kotlin
79+
private class GithubUserFetcher(
80+
private val service: GithubUserService
81+
) : Fetcher<List<GithubUser>, GithubUserHoard.Key> {
82+
83+
override fun fetch(key: GithubUserHoard.Key): Single<List<GithubUser>> = with(key) {
84+
when (this) {
85+
GithubUserHoard.Key.All -> service.fetchUsers()
86+
}
87+
}
88+
}
89+
```
90+
Custom mapper
91+
```kotlin
92+
sealed class Result<out T> {
93+
94+
data class Loading<out T>(val content: T) : Result<T>()
95+
data class Success<out T>(val content: T) : Result<T>()
96+
object Empty : Result<Nothing>()
97+
object Idle : Result<Nothing>()
98+
data class Outdated<out T>(val throwable: Throwable) : Result<T>()
99+
data class Error(val throwable: Throwable) : Result<Nothing>()
100+
101+
fun <R> map(transform: (T) -> R): Result<R> = when (this) {
102+
is Loading -> Loading(transform(content))
103+
is Success -> Success(transform(content))
104+
is Outdated -> Outdated(throwable)
105+
is Error -> this
106+
Empty -> Empty
107+
Idle -> Idle
108+
}
109+
}
110+
111+
internal class ResultMapper<in KEY, IN>(
112+
private val fetchPolicy: FetchPolicy<IN>,
113+
private val dataIsEmpty: (data: IN) -> Boolean
114+
) : Mapper<KEY, IN, Result<IN>> {
115+
116+
override fun mapNext(key: KEY, data: IN) = when {
117+
fetchPolicy.shouldFetch(data) -> Result.Loading(data)
118+
!fetchPolicy.shouldFetch(data) && !dataIsEmpty(key, data) -> Result.Success(data)
119+
else -> Result.Empty
120+
}
121+
122+
override fun mapError(throwable: Throwable): Flowable<Result<IN>> = when {
123+
throwable is EmptyResultSetException || throwable is NoSuchElementException -> Flowable.just(Result.Empty)
124+
throwable.isConnectivityExceptions() -> Flowable.just(Result.Error(throwable))
125+
else -> Flowable.error(throwable)
126+
}
127+
128+
override fun mapErrorWithData(key: KEY, data: IN, throwable: Throwable): Flowable<Result<IN>> =
129+
if (dataIsEmpty(key, data)) {
130+
Flowable.just(Result.Error(throwable))
131+
} else {
132+
Flowable.just(Result.Outdated(throwable))
133+
}
134+
135+
private fun dataIsEmpty(key: KEY, data: IN): Boolean {
136+
val dataIsEmpty = dataIsEmpty(data)
137+
return dataIsEmpty && (key as? PageKey)?.offset ?: 0 == 0
138+
}
139+
}
140+
141+
internal fun <KEY : Key, RAW> Hoard<RAW, KEY>.getWithResult(
142+
key: KEY,
143+
dataIsEmpty: (data: RAW) -> Boolean = { false }
144+
): Flowable<Result<RAW>> = flow(
145+
key,
146+
ignoreConnectivity = false,
147+
mapper = ResultMapper(fetchPolicy, dataIsEmpty)
148+
).startWith(Result.Idle)
149+
```
150+
151+
## Developed by
152+
153+
[Denys Nykyforov](https://github.com/Popalay)
154+
[Ruslan Sierov](https://github.com/Augusent)
155+
156+
## License
157+
158+
```
159+
Copyright (c) 2018 Denys Nykyforov (@popalay)
160+
161+
Licensed under the Apache License, Version 2.0 (the "License");
162+
you may not use this file except in compliance with the License.
163+
You may obtain a copy of the License at
164+
165+
http://www.apache.org/licenses/LICENSE-2.0
166+
167+
Unless required by applicable law or agreed to in writing, software
168+
distributed under the License is distributed on an "AS IS" BASIS,
169+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
170+
See the License for the specific language governing permissions and
171+
limitations under the License.
172+
```

0 commit comments

Comments
 (0)