{"id":37016971,"url":"https://github.com/motorro/rxlcemodel","last_synced_at":"2026-04-12T08:03:13.870Z","repository":{"id":41677107,"uuid":"172682449","full_name":"motorro/RxLceModel","owner":"motorro","description":"An Android library for data load with cache and loading state","archived":false,"fork":false,"pushed_at":"2026-04-11T19:50:58.000Z","size":2275,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-04-11T21:23:39.858Z","etag":null,"topics":["android","data-cache","data-loading","disklrucache","invalidation","lru-cache","lrucache","rxjava2","ui-status"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/motorro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-02-26T09:43:11.000Z","updated_at":"2026-04-11T19:51:02.000Z","dependencies_parsed_at":"2023-02-17T05:45:57.077Z","dependency_job_id":"bc279cb1-de43-45be-8f98-f8cf85d98172","html_url":"https://github.com/motorro/RxLceModel","commit_stats":null,"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"purl":"pkg:github/motorro/RxLceModel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motorro%2FRxLceModel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motorro%2FRxLceModel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motorro%2FRxLceModel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motorro%2FRxLceModel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/motorro","download_url":"https://codeload.github.com/motorro/RxLceModel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motorro%2FRxLceModel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31707954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T06:22:27.080Z","status":"ssl_error","status_checked_at":"2026-04-12T06:21:52.710Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","data-cache","data-loading","disklrucache","invalidation","lru-cache","lrucache","rxjava2","ui-status"],"created_at":"2026-01-14T01:56:29.451Z","updated_at":"2026-04-12T08:03:13.864Z","avatar_url":"https://github.com/motorro.png","language":"Kotlin","readme":"# RxLceModel\n[![Check](https://github.com/motorro/RxLceModel/actions/workflows/check.yml/badge.svg?branch=master)](https://github.com/motorro/RxLceModel/actions/workflows/check.yml) [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/rx/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/rx/)\n\nA reactive data loading for Android based on \n[RxJava](https://github.com/ReactiveX/RxJava). The library follows the guidelines recommended in official\n[Android guide to app architecture](https://developer.android.com/jetpack/docs/guide) to load data and report an \noperation state (`Loading`/`Content`/`Error`). \n\nA compiled javadoc is available at [Github Pages](https://motorro.github.io/RxLceModel/)\n\nDart/Flutter port of the library is available: [DartLceModel](https://github.com/motorro/DartLceModel)\n\n## Features\n- Widely used design with `Loading`/`Content`/`Error` states\n- Uses cache as a 'source of truth' with [CacheThenNetLceModel](#cachethennetlcemodel).\n- Checks data is valid (up-to-date or whatever).\n- Falls back to invalid cache data if failed to refresh which allows offline application use. \n- Supports data _refresh_ or _update_ to implement reload or server data update operations.\n- Cache may be _invalidated_ separately from loading to allow lazy data updates and complex data linking.\n- Extendable architecture on every level.\n- Thoroughly tested.\n\n## Sample application\nIncluded as a `sample` module - a classic notes application that illustrates `RxLceModel` use along with:\n* [Dagger2](https://github.com/google/dagger)\n* [Android architecture components](https://developer.android.com/topic/libraries/architecture):\n    * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata)\n    * [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel)\n    * [Navigation components](https://developer.android.com/guide/navigation/navigation-getting-started)\n    * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)\n\nIf you have questions on a general use of these tools in Android projects please refer to the following excellent \narticles by [James Shvarts](https://github.com/jshvarts):\n* [Implementing MVVM using LiveData, RxJava, Dagger Android](https://proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger-android-injection-639837b1eb6c)\n* [Navigation Architecture Component for the Rest of Us](https://proandroiddev.com/navigation-architecture-component-for-the-rest-of-us-faafa890e5)\n\n## Table of Contents\n* [v5.x](#v5x)\n* [Setting up the dependency](#setting-up-the-dependency)\n* [Enabling desugaring](#enabling-java8-desugaring-for-android)\n* [LceState](#lcestate)\n* [LceModel](#lcemodel)\n* [CacheThenNetLceModel](#cachethennetlcemodel)\n* [Getting and caching data](#getting-and-caching-data)\n* [Choosing EntityValidator](#choosing-entityvalidator)\n* [Displaying 'invalid' data and cache fall-back](#displaying-invalid-data-and-cache-fall-back)\n* [Cache invalidation and data updates](#cache-invalidation-and-data-updates)\n* [On-demand cache refetch](#on-demand-cache-refetch)\n* [Cache service implementation and DiskLruCache](#cache-service-implementation-and-disklrucache)\n* [A complete example of model setup](#a-complete-example-of-model-setup)\n* [Kotlin serialization](#kotlin-serialization)\n* [Cache key normalization](#cache-key-normalization)\n* [ProGuard configuration](#proguard-configuration)\n* [Updating data on server](#updating-data-on-server)\n* [Getting data-only stream](#getting-data-only-stream)\n* [LCE ViewModel](#lce-viewmodel)\n\n## v5.x\nOriginally written for RxJava, the version `5.x` introduces the experimental coroutines port.\nTo upgrade the version you need to change the dependency as described in the [migration guide](MIGRATION_5.md).\nDifferent artifacts for the main use-cases are to be required depending on which library you choose.\n\n## Setting up the dependency\n`Rx` Usecase module \n[![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/rx/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/rx/):\n```groovy\ndependencies {\n    // Base model components\n    implementation \"com.motorro.rxlcemodel:rx:x.x.x\"\n}\n```\n`Coroutines` Usecase module [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/coroutines/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/coroutines/):\n```groovy\ndependencies {\n    // Base model components\n    implementation \"com.motorro.rxlcemodel:coroutines:x.x.x\"\n}\n```\nOptional: [Jake Wharton's DiskLruCache](https://github.com/JakeWharton/DiskLruCache) cache delegate for RxLceModel [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/disklrucache/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/disklrucache/):\n```groovy\ndependencies {\n    // Jake Wharton's DiskLruCache delegate for cache implementation\n    implementation \"com.motorro.rxlcemodel:disklrucache:x.x.x\"\n}\n```\nOptional: [Kotlin serialization](https://github.com/Kotlin/kotlinx.serialization/) serializer for DiskLruCache delegate [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/kserializer/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/kserializer/):\n```groovy\ndependencies {\n    // Data serializer for DiskLruCache using Kotlin serialization\n    implementation \"com.motorro.rxlcemodel:kserializer:x.x.x\"\n}\n```\nOptional: LCE ViewModel and base view ready to accept LCE use-case for your view [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/viewmodel/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/viewmodel/):\n```groovy\ndependencies {\n    // Data serializer for DiskLruCache using Kotlin serialization\n    implementation \"com.motorro.rxlcemodel:viewmodel:x.x.x\"\n}\n```\nOptional: Compose LCE view [![Maven Central](https://maven-badges.sml.io/maven-central/com.motorro.rxlcemodel/composeview/badge.png)](https://repo1.maven.org/maven2/com/motorro/rxlcemodel/composeview/):\n```groovy\ndependencies {\n    // Data serializer for DiskLruCache using Kotlin serialization\n    implementation \"com.motorro.rxlcemodel:composeview:x.x.x\"\n}\n```\n\n### Enabling Java8 desugaring for Android\n**IMPORTANT** Version 3.x takes advantage of AGP 4.x Java8 desugaring. Follow [instructions](https://developer.android.com/studio/write/java8-support)\nto enable desugaring.\n\n## LceState\nA modern approach to architecting the reactive application suggests packing the combined state of the application into a \nflow of immutable state-objects. Each of them should contain the whole set of data required to process, transform, \nand display according to the business requirement. The most commonly used information besides the data itself is a state \nof data-loading pipeline.\n\n![LceState class diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/lce_state.puml)\n\nEach `LceState\u003cDATA, PARAMS\u003e` subclass represents a data-loading phase and contains the following data:\n*   `data: DATA?` - Loaded data\n*   `dataIsValid: Boolean` - The validity of data at the time of emission. May be used by caching services to indicate \n    the need of data refresh. More about it in [CacheThenNetLceModel](#cachethennetlcemodel) section.\n\nStates being emitted are:\n*   `Loading` - data is being loaded or updated. May contain some data. The exact state is defined by `type` property:\n```kotlin\n/**\n * Loading type\n */\nenum class Type {\n    /**\n     * Just loads. May be initial load operation\n     */\n    LOADING,\n    /**\n     * Loading more items for paginated view\n     */\n    LOADING_MORE,\n    /**\n     * Refreshing content\n     */\n    REFRESHING,\n    /**\n     * Updating data on server\n     */\n    UPDATING,\n}        \n```\n*   `Content` - data is loaded.\n*   `Error` - some error  while loading or updating data. May also contain some data.\n*   `Terminated` - a special state to indicate that resource identified by `params` is not available anymore. The sample\n    application demonstrates the use of `Terminated` to indicate that the note is deleted and view should be destroyed:\n```kotlin\noverride fun processTermination() {\n    findNavController().popBackStack()\n}\n``` \nThe sample fragment that demonstrates the use of LCE state to update it's view may be found [here](sample/src/main/kotlin/com/motorro/rxlcemodel/sample/view/note/NoteFragment.kt).\n\n## LceModel\n`LceState\u003cDATA, PARAMS\u003e` in this library is being produced by a simple model [interface](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/LceModel.kt): \n```kotlin\ninterface LceModel\u003cDATA: Any, PARAMS: Any\u003e {\n    val state: Observable\u003cLceState\u003cDATA, PARAMS\u003e\u003e\n    val refresh: Completable\n    val params: PARAMS\n}\n```\nThe model contains the following properties:\n*   `state` - the `Observable` that emits `LceState`\n*   `refresh` - the `Completable` to perform data refresh\n*   `params` - arbitrary parameters that identify the data being loaded\n\nAs you may see, parameters for model is a property - thus making the model immutable itself. This approach makes \nthings a bit easier in many cases like:\n*   When you share your model and it's emission\n*   You don't need to supply an Observable for `PARAMS` which complicates design a lot\nIf you need dynamic params - just flat-map your params with creating a new model for each parameter value like this:\n```kotlin\nval params = Observable.just(\"peach\", \"banana\", \"apple\")\nval state = params.switchMap { createNewModelWith(it).state }\n```\n\n## CacheThenNetLceModel   \nAs you may guess from it's name this kind of model tries to get cached data first and than loads data from network if \nnothing found. It goes along with current [Android guide to app architecture](https://developer.android.com/jetpack/docs/guide). This type of model is called\n`CacheThenNetLceModel`. Here is the sequence diagram of data loading using this type of model:\n\n![CacheThenNet loading sequence](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/loading.puml)\n\nThe model creates a data observable for given `PARAMS` in a [cache-service](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheService.kt) \nand transmits it to subscriber. If cache does not contain any data or data is not valid (more on validation later) the \nmodel subscribes a [net-service](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/NetService.kt) to download \ndata from network and saves it to cache for a later use.\n\nIt is worth nothing that `cache` and `net` here is just a common use-case of data-sources: locally stored  data (`cache`) \nand some data that is maybe not that easy to get (`net`). You may easily adopt data sources of your choice to that \napproach. Say you have a resource-consuming computation result which may be cached and consumed later. The computation \nitself than becomes a `net-service` while the result is being stashed to a `cache-service` of \nyour choice for later reuse. \n\nTo create new `CacheThenNet` model call a factory function:\n```kotlin\nprotected open fun createLceModel() = LceModel.cacheThenNet(\n    params = \"user_123\",\n    net = netService, // Gets original data\n    cache = cacheService, // Caches it to local storage\n    ioScheduler = Schedulers.io(), // Optional scheduler to use for internal IO operations\n    logger = Logger { level, message -\u003e }, // Optional adapter to your debug-logger\n)\n``` \n\n## Getting and caching data\nAs already mentioned above caching model uses two services to get data from network and to store it locally.\n\n*   [NetService](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/NetService.kt) - loads data from network.\n*   [CacheService](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheService.kt) - saves data locally.\n\nCaching data always brings up a problem of cache updates and invalidation. Be it a caching policy of your backend team\nor some internal logic of your application the data validity evaluation may be easily implemented: \n\n![Entity and validation](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/entity.puml)\n\nThe `NetService` retrieved data and packages it to [Entity](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/entity/Entity.kt) \nwrapper - effectively the data itself and some `EntityValidator` to provide information when data expires.\nValidator is a simple interface with only three essential methods:\n*   `isValid(): Boolean` - being used by loading pipeline to determine if data is still valid\n*   `serialize(): String` - being called by `CacheService` to save data validation parameters along with data\n*   `createSnapshot(): EntityValidator` - creates a 'snapshot' of `isVAlid()` value at the moment of creation (more \n    about it later). \n    \nThe resulting `Entity` is then saved using `CacheService` preserving the data itself and the way to tell when data \nexpires.\n\nTo convert `Any` data to `Entity` within your services use the following function:\n```kotlin\nfun createValidator() {\n    // Create a validator\n}\n\nval data = \"Some data\"\nval entity = data.toEntity(createValidator())\n``` \n\n## Choosing EntityValidator\nThere are some validators available already:\n*   `EntityValidator.Simple` - just a data-class that is initialized with boolean validity status.\n*   `EntityValidator.Never` - never valid.\n*   `EntityValidator.Always` - always valid.\n*   `EntityValidator.Lifespan` - A validator that is initialized with Time-To-Live value and becomes invalid after it \n    expires.\n    \nWhile the first three of above-listed validators are easy to use and intuitive the last one needs a to be explained.\n`Lifespan` when created gets a reference to a system clock and evaluates it's validity against it every time it is being\nasked of it. Thus `Lifespan` is not an immutable and is an object with a self-changing internal state. A valid `Entity` \nwith `Lifespan` validator that just seats in memory will expire eventually and become non-valid. That may be a desired \nbehavior however in most cases the most useful way to deal with validity is to take a shapshot of data state at the time \ndata is being emitted from the `CacheService`. To be able to do this both `EntityValidator` and `Entity` wrappers both \nhave `createSnapshot()` methods that fix the validation status at the time of function is called.\n\nTo create a `LifeSpan` validator, there is a helper-factory that takes a single parameter of TTL in a constructor:\n```kotlin\n// Creates validators that are valid for 5 seconds\nval validatorFactory = LifespanValidatorFactory(5000L)\n``` \n\nThe `LifespanValidatorFactory` is an implementation of [EntityValidatorFactory](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/entity/EntityValidatorFactory.kt)\nthat you may implement in case you need your own custom validator.\n\n## Displaying 'invalid' data and cache fall-back\nHaving a cache of required data besides eliminating extra network calls gives us the ability to fall-back to cached data\nin case network is not available and to keep working. This is an easy way to create an offline-capable mobile app when\ncomplex state synchronization between the app and server is not required. With 'cache-then-net' model you get the cache \nfall-back already implemented. Here is what you get when network connection is not available:\n\n![Cache fallback](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/cache_fallback_on_error.puml)\n\nWhen there is no cached data available you just get `null` for `data` property in emitted `LceState.Error`. \n \n## Cache invalidation and data updates\nA common task in complex applications may be the need to refresh some data in a part of application whenever something\nhappens in another part. Reloading a list of messages in a chat application when push arrives may be a simple example.\nThere are different ways of doing this - event-buses, Rx-subjects, you name it. \nWith reactive cache-service the library provides such an invalidation is made in a simple and clean way:\n\n![Cache invalidation](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/cache_invalidation.puml)\n\nIf the push-message brings a pyload that is enough to display data change you could simply save the new data to cash \nwith `save` method or delete it with `delete` method of [CacheService](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheService.kt) interface:\n\nThe sample application demonstrates cache invalidation with notes CRUD. Here is how the delete use-case invalidates \nnotes list and removes a note from cache if successfully deleted on server:\n```kotlin\n/**\n * Deletes note\n */\nfun delete(noteId: Int): Completable = connectionChecker.connectionCheck\n    .andThen(netRepository.deleteNote(noteId))\n    .andThen(noteCache.delete(noteId))\n    .andThen(listCache.invalidateAll)\n``` \n## On-demand cache refetch\nConsider a cache service with complex internal structure that is updated by some internal logic.\nFor example a database that saves entities and something that updates records directly.\nIn case of Room you may observe a query and get updates if something changes underneath. But sometimes \nyou have a complex entity with relations that are not so easy to fetch as they need conditional processing \nin synchronous way.\nIn this case you may write an SQL delegate for sync-delegate service (see below) to implement reactive cache.\nWhen you get/put the whole entity the solution works. But as soon as you start to update entity parts \nyou need some way to notify subscribers of data change. \n\n![Cache refetch](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/cache_refetch.puml)\n\n[CacheService](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheService.kt)\nhas two methods that when called makes it to refetch data and update its active clients:\n- `refetch(params: P): Completable` - makes cache service to refetch data for `params` and update corresponding clients\n- `refetchAll: Completable` - makes cache service to refetch data for all subscribers\n\n## Cache service implementation and DiskLruCache\nWhile you can implement any cache-service you like the library comes with a simple [SyncDelegateCacheService](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/SyncDelegateCacheService.kt)\nwhich uses the following delegate for data IO:\n\n![Cache delegate](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/motorro/RxLceModel/master/readme_files/cache_delegate.puml)\n\nThe interface is self-explanatory and does all the IO for `CacheService` in a synchronous way.\nThe library comes with a delegate that uses famous [Jake Wharton's DiskLruCache](https://github.com/JakeWharton/DiskLruCache) \ncache delegate to store cache in files. The delegate is to be plugged-in as a separate dependency as described in \n[Setting up the dependency](#setting-up-the-dependency).\n\nThe delegate requires a [CacheDelegateSerializerDeserializer](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheSerializersDeserealizers.kt)\nto save/restore data from file streams. The implemented serializer that comes with the library uses `ObjectStream` and\nrequires cached data to be `Serializable`.\n\nThe delegate is designed to use the same directory for several delegates each maintaining the cache of specific type or\ndata kind. It might come in handy if you need to destroy all cached data at once - say when user logs out. Thus delegate \nconstructor has a `prefix` parameter that is used to distinguish it's cache files from others.\n\nThe [DiskLruCacheSyncDelegate](disklrucache/src/main/kotlin/com/motorro/rxlcemodel/disklrucache/DiskLruCacheSyncDelegate.kt)\nrequires `PARAMS` to be `String` as it becomes a part of a file name.\n\nThe effort to configure delegate may seem overwhelming but thanks to Kotlin reified generics there is a couple of \n[factory functions](disklrucache/src/main/kotlin/com/motorro/rxlcemodel/disklrucache/factory.kt) that make things easier \nin everyday life. Follow the example below and the [CacheModule](sample/src/main/kotlin/com/motorro/rxlcemodel/sample/di/CacheModule.kt) \nin sample application.\n\n## A complete example of model setup\nHere is a complete setup of LceModel with services using `DiskLruCacheDelegate`:\n```kotlin\n/**\n * Data.\n * If using common setup mind the Serializable\n */\ndata class Data(val a: Int, val b: String) : Serializable {\n    companion object {\n        private const val serialVersionUID: Long = 1\n    }\n}\n\nval data = mapOf( 1 to Data(1, \"One\"))\n\n/**\n * Cache is valid for 5 seconds\n */\nval validatorFactory = LifespanValidatorFactory(5000L)\n\n/**\n * Net service - loads data from network\n */\nval netService = object : NetService\u003cData, Int\u003e {\n    override fun get(params: Int): Single\u003cEntity\u003cData\u003e\u003e = Single.just(\n        data[params]?.toEntity(validatorFactory.create()) ?: throw IllegalArgumentException(\"Not found\")\n    )\n}\n\n/**\n * DiskLru cache setup\n */\nval diskCache = DiskLruCacheSyncDelegate.DiskLruCacheProvider(\n    directory = context.cacheDir,\n    appVersion = BuildConfig.VERSION_CODE,\n    maxSize = 20 * 1024 * 1024\n)\n\n/**\n * Cache service\n */\nval cacheService: CacheService\u003cData, Int\u003e = CacheService.withSyncDelegate(\n    diskCache.withObjectStream(validatorFactory) { toString() }\n)\n\n/**\n * A model to load [Data] identified by [1]\n */\nval model = LceModel.cacheThenNet(\n    params = 1,\n    net = netService,\n    cache = cacheService\n)\n```\n\n## Kotlin serialization\nInstead of using Java serialization in cache delegate you may add `kserializer` library and use \n`@Serializable` data classes. \nHere is an updated above configuration using kotlin serialization:\n```kotlin\n/**\n * Data.\n */\n@Serializable\ndata class Data(val a: Int, val b: String)\n\n/**\n * Cache service\n */\nval cacheService: CacheService\u003cData, Int\u003e = CacheService.withSyncDelegate(\n    diskCache.withKotlin(validatorFactory, Data.serializer()) { toString() }\n)\n``` \n\n## Cache key normalization\nSometimes the data that form your key is too long for your cache key. For example the latest \npublished version of [DiskLruCache](https://github.com/JakeWharton/DiskLruCache/issues/98)\nhas the following pattern to validate key: `[a-z0-9_-]{1,64}`.\nAlso the total length of cash key in [DiskLruCache delegate](#cache-service-implementation-and-disklrucache)\nincludes the length of `prefix` used to construct delegate so stringifying `params` should result \nin even shorter string to fit into 64 symbols.\nThus if your parameters exceed the maximum length or has unsupported symbols you'll want to make use \nof some hashing function to generate cache key which may (depending on the has algorithm used) lead \nto key collisions and wrong results from cache.\nTo make things easier there are several utility classes and functions you may use to automate hasing\nand validation:\n```kotlin\n/**\n * Cache service with Serializable\n */\nval cacheService: CacheService\u003cData, Int\u003e = CacheService.withObjectStreamNormalized(\n    diskCache.withObjectStream(validatorFactory) { toString() }\n)\n\n/**\n * Cache service with kotlin\n */\nval cacheService: CacheService\u003cData, Int\u003e = CacheService.withSyncDelegateNormalized (\n    diskCache.withKotlinNormalized(validatorFactory, Data.serializer()) { toString() }\n)\n```\nThese functions create a wrapping [CacheFriendDelegate](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/service/CacheFriendDelegate.kt)\nwhich stores data along with original key to be able to compare it to the one used when reading data.\nIf original keys do not match - `null` is returned. \nBesides that the key is normalized before going to `DiskLruCache` - if it is considered invalid then\nit is being hashed.\n\nTo make delegate do less transformations and to try to avoid hashing your `PARAMS` class may \nimplement the following interface:\n \n```kotlin\n/**\n * Generates a cache-friendly key value for parameters\n */\ninterface CacheFriend {\n    /**\n     * A cache key\n     */\n    val cacheKey: String get() = toString()\n}\n``` \nAll delegate factories have overloads to accept parameters of that kind. \n\n## ProGuard configuration\nNothing special is required for library itself. If you use `DiskLruCache` delegate than you may want to add general \nrules for `Serializable` classes as [described](https://www.guardsquare.com/en/products/proguard/manual/examples#serializable) \nin the official guide.\n\n## Updating data on server\nWith `LceModel` you can update data easily with update operation state being transmitted through a `state` property just\nas with data loading. The updates are made with descendants of [UpdateWrapper](rx/src/main/kotlin/com/motorro/rxlcemodel/rx/UpdateWrapper.kt).\nThe idea behind is to wrap an existing `LceModel` and to mix the update status to the existing state stream.\nThe selection of ready-to use wrappers is:\n*   `UpdatingLceModelWrapper\u003cDATA, UPDATE, PARAMS\u003e` - the wrapper that updates the whole `DATA` using the `UPDATE` class.\n    The existing model may be extended with the factory function:\n```kotlin\nval serviceSet = object: UpdatingServiceSet\u003cData, Data, Int\u003e {\n    override val net: UpdatingNetService\u003cData, Data, Int\u003e = net\n    override val cache: CacheService\u003cData, Int\u003e = cache\n}\n\nval read = LceModel.cacheThenNet(\n    params = 1,\n    serviceSet = serviceSet\n)\n\nval write = read.withUpdates(serviceSet)\n\n// Update data\nwrite.update(Data(10, \"Ten\")).subscribe()\n```  \n*   `StrategyUpdateWrapper\u003cDATA, PARAMS\u003e` - may perform any update (patch) using a strategy passed to class instance:\n```kotlin\n// A repository interface\ninterface DataRepository {\n    // Patching `a` property of `Data`\n    fun updateA(id: Int, value: Int): Single\u003cData\u003e\n}\n\nval read = LceModel.cacheThenNet(\n    params = 1,\n    net = netService, // Gets original data\n    cache = cacheService // Caches it to local storage\n)\n\nval write = StrategyUpdateWrapper(\n    upstream = read,\n    cache = cacheService // Caches new data to local storage\n)\n\n// Patch `Data`\nwrite.update { id -\u003e \n    dataRepository\n        .updateA(id, 10) // Patch data\n        .map { it.toEntity(validatorFactory.create()) } // Create new entity\n}.subscribe()\n```    \n*   A subclass of `UpdateWrapper` - implement any logic you like calling `doUpdate` to perform update. An [example](sample/src/main/kotlin/com/motorro/rxlcemodel/sample/view/note/NoteViewModel.kt) \n    may be found in sample application.\n    \n## Getting data-only stream\nYou may transform the `state` property `Observable` to strip state information and to get only the data. The library \nships with some of the functions already implemented like:\n*   `dataStopOnErrors` - emits data stopping on any error\n*   `dataStopOnEmptyErrors` - emits data and terminates on error only if there is no data in original emission \n    (`LceState.Error` with `null` for `data` property)\n*   `dataNoErrors` - emits data and ignores errors\n*   `validData` - emits data only if it is valid\nMore information about them may be found in [documentation](docs/com.motorro.rxlcemodel.rx/io.reactivex.rxjava3.core.-observable/index.md)\nor in a source code.\n\n## LCE ViewModel\nYou may also take a look at basic Android [ViewModels](https://developer.android.com/topic/libraries/architecture/viewmodel)\nprovided as a separate package. Use them as-is or as a delegate in your own `ViewModel` system. \nThere are three main classes:\n\n*   `BaseLceModel` - a common frame for LCE view-model having a signature for all common tasks to:\n    *   Load data\n    *   Dismiss error    \n    *   Refresh data\n*   `BaseLceModel.Impl` - a common implementation to subclass if you need advanced logic\n*   `BaseLceModel.WithUpdates` - for those models that run some update operations on loaded data.\n    This model will mix loading and error states from an operation to main data state.\n    See usage example in a [sample application](sample/src/main/kotlin/com/motorro/rxlcemodel/sample/view/addnote/AddNoteViewModel.kt). \n    \nTo create a model from an `LceUseCase` call `BaseLceModel.create` methods and pass your state \nuse-cases or `LCE` observables to `ViewModel`\n\nYou may also want to use basic views to switch your display according to LCE state:\n\n*   [LceStateView](viewmodel/src/main/kotlin/com/motorro/rxlcemodel/view/LceStateView.kt) - for Android \n    view system.\n*   [LceStateView](composeview/src/main/kotlin/com/motorro/rxlcemodel/composeview/LceStateView.kt) - for Android\n    Compose view system.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotorro%2Frxlcemodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmotorro%2Frxlcemodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotorro%2Frxlcemodel/lists"}