The name Loom draws inspiration from the traditional tool used for weaving fabric, symbolizing the process of integrating and managing data streams seamlessly in modern applications. This analogy is especially fitting for a system responsible for handling dynamic state updates, like refreshing UI states or fetching new data.
LoomIn offers a clean, easy-to-use solution for developers handling complex asynchronous workflows. It simplifies managing state-driven operations and ensures your app performs efficiently by avoiding redundant calls and state updates.
-
Integration: A loom weaves threads into a cohesive fabric, much like how the Loom system integrates multiple data streams into a single, unified state.
-
Precision and Control: Just as a loom controls the pattern and flow of threads, the Loom manages the timing and structure of state updates, ensuring consistency and avoiding unnecessary re-fetches.
-
Scalability: Looms can handle complex patterns with many threads, mirroring how the Loom system supports diverse and complex state flows with ease.
The Loom acts as an orchestrator for state updates, focusing on two key aspects:
-
Initial Data Fetch On the first subscription, Loom triggers the fetch operation to populate the state with initial data. Example: Automatically loading the home screen data on app launch.
-
Controlled Refreshing Subsequent refresh actions are explicitly triggered by user interactions or external signals, ensuring efficient and intentional updates without redundant API calls.
-
Debounce and Throttle: Control the frequency of state updates with customizable debounce and throttle intervals.
-
StateFlow Management: Efficiently manage state with
StateFlow
, offering a seamless reactive flow system. -
Safe Concurrency: Synchronize tasks using a robust locking mechanism to avoid race conditions and ensure thread safety.
-
Kotlin-centric: Built with Kotlin and CoroutineScope, leveraging Kotlin's best features for asynchronous operations.
-
Replay-Capable Flow The Loom uses a replayable StateFlow to retain the last emitted value, ensuring late subscribers can still access the most recent state without re-fetching data.
-
Explicit Refresh Triggers Allows the UI or external components to trigger updates through a refresh() function, maintaining clear separation of responsibilities.
-
Seamless Integration Works with Kotlin's StateFlow or SharedFlow to provide reactive and declarative state management.
-
Efficiency
- Avoids unnecessary recomposition or redundant API calls by reusing existing data when possible.
- Only the first subscriber triggers the initial data fetch, optimizing network usage.
-
Reusability
- Can be used across multiple ViewModels or components for consistent state handling.
-
Extensibility
- Supports adding additional layers (e.g., caching, error handling) without changing its core design.
internal class LoomImpl<T>(
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
private val debounceTimeMillis: Long = DEBOUNCE,
private val throttleTimeMillis: Long = THROTTLE,
private val execute: suspend (Boolean) -> T
) : Loom<T> {
private val mutex = Mutex();
private val _modifier = MutableSharedFlow<(T) -> T>(extraBufferCapacity = 1)
private val _state = MutableSharedFlow<LoomSignal>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override val state: StateFlow<LoomState<T>> = _state
.streamLine(debounceTimeMillis, throttleTimeMillis)
.onStart { emit(LoomSignal.Automatic) } // Emit the first refresh automatically
.flatMapLatest { event ->
flow {
emit(LoomState.Loading)
try {
val data = execute(event is LoomSignal.FromUser)
emit(LoomState.Loaded(data))
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
emit(LoomState.Error(e))
}
}
}
.combine(_modifier.onStart { emit { data: T -> data } }) { oldState, modifier ->
mutex.withLock {
if (oldState is LoomState.Loaded) {
val expectedData = modifier(oldState.data)
if (expectedData != oldState.data) {
LoomState.Loaded(expectedData)
} else {
oldState // Don't do anything if value is not changing
}
} else {
oldState // Don't do anything if value is not changing
}
}
}
.catch { emit(LoomState.Error(it)) }
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(
stopTimeoutMillis = 5_000,
replayExpirationMillis = 0
),
initialValue = LoomState.Init
)
override fun refresh(fromUser: Boolean) {
_state.tryEmit(if (fromUser) LoomSignal.FromUser else LoomSignal.Automatic)
}
override fun update(modifier: (T) -> T) {
_modifier.tryEmit(modifier)
}
}
The Loom metaphor emphasizes the importance of structured, efficient, and scalable state management. By weaving together streams of data and providing explicit controls for refreshing, Loom ensures that the UI remains responsive, consistent, and performant. This approach not only aligns with modern reactive programming paradigms but also provides a clear and elegant mechanism for managing dynamic states.
- Step 1: Grab from Maven central at the coordinates:
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/nphausg/loomIn")
}
}
- Step 2: Implementation from your module
$latestVersion = "0.0.1-alpha"
implementation("com.nphausg:loom:$latestVersion")
Contributing We welcome contributions! If you're interested in contributing to LoomIn, here are some ways you can help:
- Fix bugs π
- Improve documentation π
- Enhance features π‘
- Add examples π₯οΈ
- Fork the repo.
- Clone your forked repo locally.
- Create a new branch for your fix or feature.
- Write tests for your changes (if applicable).
- Submit a pull request with a detailed explanation of your changes.
- Please feel free to contact me or make a pull request.
Follow the repository for updates and improvements:
Thanks to the Kotlin and Coroutine community for their support and contributions. We also want to thank all of our contributors who helped improve LoomIn! π
If you find LoomIn helpful, please βοΈ star the repo and π fork it to use in your own projects. Your support helps others discover the project! Letβs make asynchronous programming easier for everyone! ππ