fix dokka, update comments, integrate persister (#56)
* fix dokka, update comments, integrate persister
This commit is contained in:
parent
0cd21be2ee
commit
267f803db0
11 changed files with 163 additions and 48 deletions
|
@ -9,7 +9,7 @@ buildscript {
|
|||
}
|
||||
|
||||
ext.versions = [
|
||||
androidGradlePlugin : '3.6.0-rc01',
|
||||
androidGradlePlugin : '4.0.0-alpha07',
|
||||
dexcountGradlePlugin: '1.0.2',
|
||||
kotlin : '1.3.61',
|
||||
dokkaGradlePlugin : '0.10.0',
|
||||
|
@ -89,3 +89,5 @@ subprojects {
|
|||
project.plugins.withType(com.android.build.gradle.AppPlugin).whenPluginAdded(preDexClosure)
|
||||
project.plugins.withType(com.android.build.gradle.LibraryPlugin).whenPluginAdded(preDexClosure)
|
||||
}
|
||||
|
||||
|
||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Sat Sep 14 18:42:21 PDT 2019
|
||||
#Tue Jan 07 09:58:49 EST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-milestone-2-all.zip
|
||||
|
|
|
@ -54,3 +54,9 @@ compileTestKotlin {
|
|||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dokka {
|
||||
outputFormat = 'javadoc'
|
||||
outputDirectory = "$buildDir/dokkaHtml"
|
||||
}
|
|
@ -1,5 +1,14 @@
|
|||
package com.dropbox.android.external.store4
|
||||
|
||||
/**
|
||||
* Interface for retrieving [Raw] data from disk/persistent sources based on a [key] identifier
|
||||
* @param Raw - the type of data returned from a persistent source
|
||||
* @param Key - a unique identifier for data
|
||||
*/
|
||||
interface DiskRead<Raw, Key> {
|
||||
/**
|
||||
* @param key identifier for data
|
||||
* @return data for a [key]
|
||||
*/
|
||||
suspend fun read(key: Key): Raw?
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package com.dropbox.android.external.store4
|
||||
|
||||
/**
|
||||
* Interface for saving [Raw] data from disk/persistent sources based on a [key] identifier
|
||||
* @param Raw - the type of data to be saved to a persistent source
|
||||
* @param Key - a unique identifier for data
|
||||
*/
|
||||
interface DiskWrite<Raw, Key> {
|
||||
/**
|
||||
* @param key to use to get data from persister
|
||||
* If data is not available implementer needs to
|
||||
* either return Observable.empty or throw an exception
|
||||
* @param key to use to get data from a persistent source.
|
||||
* If data is not available implementer needs to throw an exception
|
||||
* @return true if data was successfully written
|
||||
*/
|
||||
suspend fun write(key: Key, raw: Raw): Boolean
|
||||
}
|
||||
|
|
|
@ -3,18 +3,15 @@ package com.dropbox.android.external.store4
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* MemoryPolicy holds all required info to create MemoryCache and
|
||||
* [NoopPersister]
|
||||
* MemoryPolicy holds all required info to create MemoryCache
|
||||
*
|
||||
*
|
||||
* This class is used, in order to define the appropriate parameters for the MemoryCache
|
||||
* This class is used, in order to define the appropriate parameters for the Memory [com.dropbox.android.external.cache3.Cache]
|
||||
* to be built.
|
||||
*
|
||||
*
|
||||
* MemoryPolicy is used by a [Store]
|
||||
* and defines the in-memory cache behavior. It is also used by
|
||||
* [NoopPersister]
|
||||
* to define a basic caching mechanism.
|
||||
* and defines the in-memory cache behavior.
|
||||
*/
|
||||
class MemoryPolicy internal constructor(
|
||||
val expireAfterWrite: Long,
|
||||
|
|
|
@ -2,16 +2,17 @@ package com.dropbox.android.external.store4
|
|||
|
||||
/**
|
||||
* Interface for fetching data from persister
|
||||
* when implementing also think about implementing PathResolver to ease in creating primary keys
|
||||
*
|
||||
* @param <Raw> data type before parsing
|
||||
* @param <Raw> data type
|
||||
* @param Key unique identifier for data
|
||||
</Raw> */
|
||||
interface Persister<Raw, Key> : DiskRead<Raw, Key>, DiskWrite<Raw, Key> {
|
||||
|
||||
/**
|
||||
* @param key to use to get data from persister
|
||||
*
|
||||
* If data is not available implementer needs to
|
||||
* either return null or throw an exception
|
||||
* either return null or throw an exception
|
||||
*/
|
||||
override suspend fun read(key: Key): Raw?
|
||||
|
||||
|
|
|
@ -6,13 +6,40 @@ import kotlinx.coroutines.flow.filterNot
|
|||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.transform
|
||||
|
||||
// possible replacement for [Store] as an internal only representation
|
||||
// if this class becomes public, should probaly be named IntermediateStore to distingush from
|
||||
// Store and also clarify that it still needs to be built/open? (how do we ensure?)
|
||||
/**
|
||||
* A Store is responsible for managing a particular data request.
|
||||
*
|
||||
* When you create an implementation of a Store, you provide it with a Fetcher, a function that defines how data will be fetched over network.
|
||||
*
|
||||
* You can also define how your Store will cache data in-memory and on-disk. See [StoreBuilder] for full configuration
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* val store = StoreBuilder
|
||||
* .fromNonFlow<Pair<String, RedditConfig>, List<Post>> { (query, config) ->
|
||||
* provideRetrofit().fetchData(query, config.limit).data.children.map(::toPosts)
|
||||
* }
|
||||
* .persister(reader = { (query, _) -> db.postDao().loadData(query) },
|
||||
* writer = { (query, _), posts -> db.dataDAO().insertData(query, posts) },
|
||||
* delete = { (query, _) -> db.dataDAO().clearData(query) })
|
||||
* .build()
|
||||
* //single shot response
|
||||
* viewModelScope.launch {
|
||||
* val data = store.fresh(key)
|
||||
* }
|
||||
*
|
||||
* //get cached data and collect future emissions as well
|
||||
* viewModelScope.launch {
|
||||
* val data = store.cached(key, refresh=true)
|
||||
* .collect{data.value=it }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
interface Store<Key, Output> {
|
||||
|
||||
/**
|
||||
* Return a flow for the given key
|
||||
* @param request - see [StoreRequest] for configurations
|
||||
*/
|
||||
fun stream(request: StoreRequest<Key>): Flow<StoreResponse<Output>>
|
||||
|
||||
|
@ -38,12 +65,18 @@ fun <Key, Output> Store<Key, Output>.stream(key: Key) = stream(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper factory that will return data for [key] if it is cached otherwise will return fresh/network data (updating your caches)
|
||||
*/
|
||||
suspend fun <Key, Output> Store<Key, Output>.get(key: Key) = stream(
|
||||
StoreRequest.cached(key, refresh = false)
|
||||
).filterNot {
|
||||
it is StoreResponse.Loading
|
||||
}.first().requireData()
|
||||
|
||||
/**
|
||||
* Helper factory that will return fresh data for [key] while updating your caches
|
||||
*/
|
||||
suspend fun <Key, Output> Store<Key, Output>.fresh(key: Key) = stream(
|
||||
StoreRequest.fresh(key)
|
||||
).filterNot {
|
||||
|
|
|
@ -33,8 +33,25 @@ import kotlinx.coroutines.flow.flow
|
|||
@ExperimentalCoroutinesApi
|
||||
interface StoreBuilder<Key, Output> {
|
||||
fun build(): Store<Key, Output>
|
||||
/**
|
||||
* A store multicasts same [Output] value to many consumers (Similar to RxJava.share()), by default
|
||||
* [Store] will open a global scope for management of shared responses, if instead you'd like to control
|
||||
* the scope that sharing/multicasting happens in you can pass a @param [scope]
|
||||
*
|
||||
* @param scope - scope to use for sharing
|
||||
*/
|
||||
fun scope(scope: CoroutineScope): StoreBuilder<Key, Output>
|
||||
|
||||
/**
|
||||
* controls eviction policy for a store cache, use [MemoryPolicy.MemoryPolicyBuilder] to configure a TTL
|
||||
* or size based eviction
|
||||
* Example: MemoryPolicy.builder().setExpireAfterWrite(10).setExpireAfterTimeUnit(TimeUnit.SECONDS).build()
|
||||
*/
|
||||
fun cachePolicy(memoryPolicy: MemoryPolicy?): StoreBuilder<Key, Output>
|
||||
|
||||
/**
|
||||
* by default a Store caches in memory with a default policy of max items = 16
|
||||
*/
|
||||
fun disableCache(): StoreBuilder<Key, Output>
|
||||
|
||||
/**
|
||||
|
@ -50,7 +67,22 @@ interface StoreBuilder<Key, Output> {
|
|||
): StoreBuilder<Key, NewOutput>
|
||||
|
||||
/**
|
||||
* Connects a ([Flow]) source of truth that is accessed via [reader], [writer] and [delete].
|
||||
* Connects a ([kotlinx.coroutines.flow.Flow]) source of truth that is accessed via [reader], [writer] and [delete].
|
||||
*
|
||||
* A source of truth is usually backed by local storage. It's purpose is to eliminate the need
|
||||
* for waiting on network update before local modifications are available (via [Store.stream]).
|
||||
*
|
||||
* @param [com.dropbox.android.external.store4.Persister] reads records from the source of truth
|
||||
* WARNING: Delete operation is not supported when using a legacy [com.dropbox.android.external.store4.Persister],
|
||||
* please use another override
|
||||
*/
|
||||
|
||||
fun nonFlowingPersisterLegacy(
|
||||
persister: Persister<Output, Key>
|
||||
): StoreBuilder<Key, Output>
|
||||
|
||||
/**
|
||||
* Connects a ([kotlinx.coroutines.flow.Flow]) source of truth that is accessed via [reader], [writer] and [delete].
|
||||
*
|
||||
* For maximal flexibility, [writer]'s record type ([Output]] and [reader]'s record type
|
||||
* ([NewOutput]) are not identical. This allows us to read one type of objects from network and
|
||||
|
@ -124,6 +156,20 @@ private class BuilderImpl<Key, Output>(
|
|||
} ?: builder
|
||||
}
|
||||
|
||||
private fun withLegacySourceOfTruth(
|
||||
sourceOfTruth: PersistentNonFlowingSourceOfTruth<Key, Output, Output>
|
||||
) = BuilderWithSourceOfTruth(fetcher, sourceOfTruth).let { builder ->
|
||||
if (cachePolicy == null) {
|
||||
builder.disableCache()
|
||||
} else {
|
||||
builder.cachePolicy(cachePolicy)
|
||||
}
|
||||
}.let { builder ->
|
||||
scope?.let {
|
||||
builder.scope(it)
|
||||
} ?: builder
|
||||
}
|
||||
|
||||
override fun scope(scope: CoroutineScope): BuilderImpl<Key, Output> {
|
||||
this.scope = scope
|
||||
return this
|
||||
|
@ -153,6 +199,18 @@ private class BuilderImpl<Key, Output>(
|
|||
)
|
||||
}
|
||||
|
||||
override fun nonFlowingPersisterLegacy(
|
||||
persister: Persister<Output, Key>
|
||||
): BuilderWithSourceOfTruth<Key, Output, Output> {
|
||||
val sourceOfTruth: PersistentNonFlowingSourceOfTruth<Key, Output, Output> =
|
||||
PersistentNonFlowingSourceOfTruth(
|
||||
realReader = { key -> persister.read(key) },
|
||||
realWriter = { key, input -> persister.write(key, input) },
|
||||
realDelete = { key -> error("Delete is not implemented in legacy persisters") }
|
||||
)
|
||||
return withLegacySourceOfTruth(sourceOfTruth)
|
||||
}
|
||||
|
||||
override fun <NewOutput> persister(
|
||||
reader: (Key) -> Flow<NewOutput?>,
|
||||
writer: suspend (Key, Output) -> Unit,
|
||||
|
@ -217,4 +275,7 @@ private class BuilderWithSourceOfTruth<Key, Input, Output>(
|
|||
writer: suspend (Key, Output) -> Unit,
|
||||
delete: (suspend (Key) -> Unit)?
|
||||
): StoreBuilder<Key, NewOutput> = error("Multiple persisters are not supported")
|
||||
|
||||
override fun nonFlowingPersisterLegacy(persister: Persister<Output, Key>): StoreBuilder<Key, Output> =
|
||||
error("Multiple persisters are not supported")
|
||||
}
|
||||
|
|
|
@ -15,42 +15,54 @@
|
|||
*/
|
||||
package com.dropbox.android.external.store4
|
||||
|
||||
/**
|
||||
* data class to represent a single store request
|
||||
* @param key a unique identifier for your data
|
||||
* @param skippedCaches List of cache types that should be skipped when retuning the response see [CacheType]
|
||||
* @param refresh If set to true [Store] will always get fresh value from fetcher while also
|
||||
* starting the stream from the local [com.dropbox.android.external.store4.impl.SourceOfTruth] and memory cache
|
||||
*
|
||||
*/
|
||||
data class StoreRequest<Key> private constructor(
|
||||
/**
|
||||
* The key for the request
|
||||
*/
|
||||
|
||||
val key: Key,
|
||||
/**
|
||||
* List of cache types that should be skipped when retuning the response
|
||||
*/
|
||||
|
||||
private val skippedCaches: Int,
|
||||
/**
|
||||
* If set to with stream requests, Store will always get fresh value from fetcher while also
|
||||
* starting the stream from the local data (disk and/or memory cache)
|
||||
*/
|
||||
|
||||
val refresh: Boolean = false
|
||||
) {
|
||||
|
||||
internal fun shouldSkipCache(type: CacheType) = skippedCaches.and(type.flag) != 0
|
||||
|
||||
/**
|
||||
* Factories for common store requests
|
||||
*/
|
||||
companion object {
|
||||
private val allCaches = CacheType.values().fold(0) { prev, next ->
|
||||
prev.or(next.flag)
|
||||
}
|
||||
|
||||
// TODO figure out if any of these helper methods make sense
|
||||
/**
|
||||
* Create a Store Request which will skip all caches and hit your fetcher (filling your caches)
|
||||
*/
|
||||
fun <Key> fresh(key: Key) = StoreRequest(
|
||||
key = key,
|
||||
skippedCaches = allCaches,
|
||||
refresh = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a Store Request which will return data from memory/disk caches
|
||||
* @param refresh if true then return fetcher (new) data as well (updating your caches)
|
||||
*/
|
||||
fun <Key> cached(key: Key, refresh: Boolean) = StoreRequest(
|
||||
key = key,
|
||||
skippedCaches = 0,
|
||||
refresh = refresh
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a Store Request which will return data from disk cache
|
||||
* @param refresh if true then return fetcher (new) data as well (updating your caches)
|
||||
*/
|
||||
fun <Key> skipMemory(key: Key, refresh: Boolean) = StoreRequest(
|
||||
key = key,
|
||||
skippedCaches = CacheType.MEMORY.flag,
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
package com.dropbox.android.external.store4.legacy
|
||||
|
||||
import com.dropbox.android.external.store4.Persister
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* [Barcode][BarCode] is used as a unique
|
||||
* identifier for a particular [Store]
|
||||
* Barcode is used as an example unique
|
||||
* identifier for a particular [com.dropbox.android.external.store4.Store]
|
||||
*
|
||||
*
|
||||
* Barcode will be passed to Fetcher
|
||||
* and [Persister]
|
||||
* Barcode will be passed to Fetcher
|
||||
* and [com.dropbox.android.external.store4.impl.SourceOfTruth]
|
||||
*/
|
||||
@Deprecated("here for testing")
|
||||
data class BarCode(val type: String, val key: String) : Serializable {
|
||||
companion object {
|
||||
private val EMPTY_BARCODE = BarCode("", "")
|
||||
|
||||
fun empty(): BarCode {
|
||||
return EMPTY_BARCODE
|
||||
}
|
||||
}
|
||||
}
|
||||
data class BarCode(val type: String, val key: String)
|
||||
|
|
Loading…
Reference in a new issue