https://github.com/ch4rl3x/compose-cache
A lightweight Jetpack Compose utility that buffers user input locally until the backing state catches up β preventing cursor jumps and lost text when syncing with databases or flows.
https://github.com/ch4rl3x/compose-cache
cache compose compose-multiplatform kotlin-android kotlin-ios kotlin-multiplatform remember userinput
Last synced: 3 months ago
JSON representation
A lightweight Jetpack Compose utility that buffers user input locally until the backing state catches up β preventing cursor jumps and lost text when syncing with databases or flows.
- Host: GitHub
- URL: https://github.com/ch4rl3x/compose-cache
- Owner: ch4rl3x
- License: apache-2.0
- Created: 2022-02-28T11:29:54.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2025-10-30T09:04:03.000Z (5 months ago)
- Last Synced: 2025-10-31T02:17:12.227Z (5 months ago)
- Topics: cache, compose, compose-multiplatform, kotlin-android, kotlin-ios, kotlin-multiplatform, remember, userinput
- Language: Kotlin
- Homepage:
- Size: 1.69 MB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# compose-cache
`compose-cache` is a lightweight utility for Jetpack Compose that helps you handle two-way state synchronization between UI and external sources (e.g. databases, flows) β without suffering from cursor jumps or overwritten user input.
> [!NOTE]
> π RevealSwipe is now Compose Multiplatform
## π§ The Problem
In Compose, text fields are typically bound to a single source of truth β for example, a `StateFlow` or immutable UI state provided by a ViewModel.
When the user types, you usually:
1. Send the new input to the ViewModel (`onValueChange`)
2. The ViewModel emits a new UI state (often via `combine`, `copy`, or other transformations)
3. The UI collects this state and updates the `TextField` value accordingly
The issue arises because state emission and recomposition are asynchronous.
If the user types quickly, there can be a short delay before the ViewModel emits the updated state. During that window:
* The `TextField` still receives the old value from the last emission
* Compose updates the UI with that outdated value
* This causes the cursor to jump or recently typed characters to disappear
This can happen even without databases β simply using `collectAsState()` with a ViewModel is enough to reproduce it, especially in multi-field forms or with combined UI state.
## π The Solution
`rememberForUserInput` wraps your state with a temporary local cache.
When the user types, it stores the input locally and marks the state as dirty.
While dirty, external updates are ignored to prevent overwriting user input.
Once the external source emits the same value that the user typed, the dirty flag is cleared and control is handed back to the source.
π This means the UI stays responsive, and your database stays the single source of truth β without flicker or cursor issues.
## Dependency
Add actual compose-cache library:
```groovy
dependencies {
implementation 'de.charlex.compose:compose-cache:3.0.1'
}
```
## How does it work?
`rememberForUserInput` can be used for every value/onValueChange behaviors where race conditions occurs
```kotlin
val (text, onTextChange) = rememberForUserInput(
value = dbValue,
onValueChange = { newValue ->
viewModel.updateDatabase(newValue)
}
)
TextField(
value = text,
onValueChange = onTextChange
)
```
### Optional: Custom equality check
If your value isnβt a simple primitive, you can provide a custom equals function:
```kotlin
val (item, onItemChange) = rememberForUserInput(
value = selectedItem,
equals = { a, b -> a.id == b.id },
onValueChange = { newItem -> updateInDb(newItem) }
)
```
License
--------
Copyright 2022 Alexander Karkossa
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.