https://github.com/pelagornis/kotlin-rex
Rex is a modular, state management architecture
https://github.com/pelagornis/kotlin-rex
kotlin pelagornis rex
Last synced: 6 months ago
JSON representation
Rex is a modular, state management architecture
- Host: GitHub
- URL: https://github.com/pelagornis/kotlin-rex
- Owner: pelagornis
- License: mit
- Created: 2025-09-08T12:35:54.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-10-12T06:22:52.000Z (9 months ago)
- Last Synced: 2025-10-13T16:41:29.579Z (9 months ago)
- Topics: kotlin, pelagornis, rex
- Language: Kotlin
- Homepage:
- Size: 175 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Kotlin Rex π¦
**Kotlin Rex** is a type-safe and predictable state management library for Android. Based on Redux/Flux architecture patterns, it elegantly handles asynchronous operations using Kotlin Coroutines.
## Features β¨
- π― **Type Safety**: Complete type safety leveraging Kotlin's powerful type system
- π **Predictable State Management**: Manage state changes predictably with unidirectional data flow
- β‘ **Coroutines-based**: Efficient asynchronous processing using Kotlin Coroutines
- π **Middleware Support**: Various middlewares including logging and time-travel debugging
- π¨ **Effect System**: Powerful and flexible side effect handling
- π **EventBus**: Built-in EventBus for inter-component event communication
- π§© **Modular**: Each component can be used independently
## Requirements π
- Android API 24+ (Android 7.0+)
- Kotlin 2.0+
- Android Gradle Plugin 8.0+
## Installation π§
### Maven Central (Recommended)
Add the dependency to your `build.gradle.kts`:
```kotlin
dependencies {
implementation("com.pelagornis:rex:1.0.0")
}
```
### GitHub Packages
Add the GitHub Packages repository:
```kotlin
repositories {
maven {
url = uri("https://maven.pkg.github.com/pelagornis/kotlin-rex")
credentials {
username = findProperty("gpr.user") as String? ?: System.getenv("GITHUB_USERNAME")
password = findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}
dependencies {
implementation("com.pelagornis:rex:1.0.0")
}
```
### Local Maven (For Development)
```bash
# Clone and build locally
git clone https://github.com/pelagornis/kotlin-rex.git
cd kotlin-rex
./gradlew :library:publishToMavenLocal
```
Then in your project:
```kotlin
repositories {
mavenLocal()
}
dependencies {
implementation("com.pelagornis:rex:1.0.0")
}
```
For detailed publishing instructions, see:
- **[USER_TOKEN_SETUP.md](USER_TOKEN_SETUP.md)** - Maven Central User Token setup (2024 updated) β
- [PUBLISHING.md](PUBLISHING.md) - Complete publishing guide
- [GPG_SETUP.md](GPG_SETUP.md) - GPG key setup
- [SONATYPE_SETUP.md](SONATYPE_SETUP.md) - Sonatype account setup
## Core Concepts π
### State
An immutable data structure representing the application state.
```kotlin
data class AppState(
val count: Int = 0,
val isLoading: Boolean = false,
val errorMessage: String? = null,
val lastUpdated: Long = System.currentTimeMillis()
) : StateType
```
### Action
Events that describe state changes.
```kotlin
sealed class AppAction : ActionType {
object Increment : AppAction()
object Decrement : AppAction()
data class SetCount(val count: Int) : AppAction()
object LoadData : AppAction()
data class DataLoaded(val data: String) : AppAction()
data class ErrorOccurred(val message: String) : AppAction()
}
```
### Reducer
A pure function that takes the current state and an action, then returns a new state and effects to execute.
```kotlin
class AppReducer : Reducer {
override fun reduce(state: AppState, action: AppAction): Pair>> {
return when (action) {
is AppAction.Increment -> {
state.copy(count = state.count + 1) to emptyList()
}
is AppAction.Decrement -> {
state.copy(count = state.count - 1) to emptyList()
}
is AppAction.SetCount -> {
state.copy(count = action.count) to emptyList()
}
is AppAction.LoadData -> {
val effect = Effect { emitter ->
try {
val data = fetchDataFromApi()
emitter.send(AppAction.DataLoaded(data))
} catch (e: Exception) {
emitter.send(AppAction.ErrorOccurred(e.message ?: "Unknown error"))
}
}
state.copy(isLoading = true) to listOf(effect)
}
is AppAction.DataLoaded -> {
state.copy(isLoading = false) to emptyList()
}
is AppAction.ErrorOccurred -> {
state.copy(isLoading = false, errorMessage = action.message) to emptyList()
}
}
}
private suspend fun fetchDataFromApi(): String {
// API call logic
return "Data from API"
}
}
```
### Store
The central repository that holds the state, processes actions, and notifies subscribers of state changes.
```kotlin
class MyViewModel : ViewModel() {
private val store = Store(
initialState = AppState(),
reducer = AppReducer(),
middlewares = listOf(LoggingMiddleware())
)
val state: StateFlow = store.state
fun dispatch(action: AppAction) {
store.dispatch(action)
}
override fun onCleared() {
super.onCleared()
store.clear()
}
}
```
## Usage Examples π
### 1. Basic Usage
```kotlin
// 1. Define State
data class CounterState(
val count: Int = 0
) : StateType
// 2. Define Actions
sealed class CounterAction : ActionType {
object Increment : CounterAction()
object Decrement : CounterAction()
}
// 3. Implement Reducer
class CounterReducer : Reducer {
override fun reduce(
state: CounterState,
action: CounterAction
): Pair>> {
return when (action) {
is CounterAction.Increment ->
state.copy(count = state.count + 1) to emptyList()
is CounterAction.Decrement ->
state.copy(count = state.count - 1) to emptyList()
}
}
}
// 4. Create and Use Store
val store = Store(
initialState = CounterState(),
reducer = CounterReducer()
)
// Subscribe to state changes
store.subscribe { state ->
println("Current count: ${state.count}")
}
// Dispatch actions
store.dispatch(CounterAction.Increment)
store.dispatch(CounterAction.Increment)
store.dispatch(CounterAction.Decrement)
```
### 2. Using Effects
Effects handle side effects like asynchronous operations, network requests, and timers.
```kotlin
// Basic Effect
val effect = Effect { emitter ->
val result = performNetworkRequest()
emitter.send(AppAction.RequestSuccess(result))
}
// Delayed Effect
val delayedEffect = Effect.delayed(
action = AppAction.ShowMessage("Hello!"),
delayMillis = 1000
)
// Retryable Effect
val retryEffect = Effect.retry(
effect = networkEffect,
maxAttempts = 3,
delayMillis = 1000,
shouldRetry = { error -> error is NetworkException },
onError = { error ->
Log.e("Effect", "Failed after retries: $error")
}
)
// Combining Multiple Effects
val combinedEffect = Effect.combine(effect1, effect2, effect3)
```
### 3. Using Middleware
Middleware can intercept and process actions before they reach the reducer.
```kotlin
// Custom Middleware
class AnalyticsMiddleware : Middleware {
override suspend fun process(
state: AppState,
action: AppAction,
emit: (AppAction) -> Unit
): List> {
// Send action to analytics tool
Analytics.logEvent(action.javaClass.simpleName)
return emptyList()
}
}
// Apply Middleware to Store
val store = Store(
initialState = AppState(),
reducer = AppReducer(),
middlewares = listOf(
LoggingMiddleware(),
AnalyticsMiddleware(),
TimeTravelMiddleware()
)
)
```
### 4. Using EventBus
Use EventBus to send and receive events between components.
```kotlin
// Define Events
sealed class AppEvent : EventType {
data class ShowToast(val message: String) : AppEvent()
object NavigateToHome : AppEvent()
}
// Use EventBus
val eventBus = store.getEventBus()
// Subscribe to specific event type
eventBus.subscribe { event ->
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
// Subscribe to all events
eventBus.subscribe { event ->
when (event) {
is AppEvent.ShowToast -> showToast(event.message)
is AppEvent.NavigateToHome -> navigateToHome()
}
}
// Publish event
eventBus.publish(AppEvent.ShowToast("Hello, World!"))
```
### 5. Integration with Jetpack Compose
```kotlin
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val state by viewModel.state.collectAsState()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Count: ${state.count}",
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(16.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(onClick = { viewModel.dispatch(CounterAction.Decrement) }) {
Text("-")
}
Button(onClick = { viewModel.dispatch(CounterAction.Increment) }) {
Text("+")
}
}
}
}
```
## Built-in Middleware π¦
### LoggingMiddleware
Logs all actions and state changes.
```kotlin
val store = Store(
initialState = AppState(),
reducer = AppReducer(),
middlewares = listOf(LoggingMiddleware())
)
```
### TimeTravelMiddleware
Tracks state history to enable time-travel debugging.
```kotlin
val timeTravelMiddleware = TimeTravelMiddleware()
val store = Store(
initialState = AppState(),
reducer = AppReducer(),
middlewares = listOf(timeTravelMiddleware)
)
// Undo to previous state
timeTravelMiddleware.undo()
// Redo to next state
timeTravelMiddleware.redo()
// View history
val history = timeTravelMiddleware.getHistory()
```
## Architecture ποΈ
```
βββββββββββββββββββββββββββββββββββββββββββββββ
β View β
β (Activity, Fragment, Composable) β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β dispatch(action)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Store β
β βββββββββββββββββββββββββββββββββββββββ β
β β Middleware Chain β β
β β ββββββββββ ββββββββββ ββββββββββ β β
β β β Logger βββAnalyticsβββTimeTravelβ β β
β β ββββββββββ ββββββββββ ββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β Reducer β β
β β (state, action) β (state, effects) β β
β βββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β New State β β
β βββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β Execute Effects β β
β β (async operations, side effects) β β
β βββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β state updates
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Subscribers β
β (UI updates via StateFlow) β
βββββββββββββββββββββββββββββββββββββββββββββββ
```
## Best Practices π‘
1. **Keep State Immutable**: Always use the `copy()` method of `data class` to create a new state.
2. **Keep Reducers Pure**: Don't cause side effects inside reducers; separate them into Effects.
3. **Make Actions Clear**: Action names should clearly express "what happened".
4. **Make Effects Reusable**: Create reusable Effects for common asynchronous operations.
5. **Single Responsibility for Middleware**: Each Middleware should perform one clear role.
6. **Manage Store in ViewModel**: In Android, create and manage Store in ViewModel.
## Testing π§ͺ
```kotlin
class CounterReducerTest {
private lateinit var reducer: CounterReducer
@Before
fun setup() {
reducer = CounterReducer()
}
@Test
fun `increment action increases count by one`() {
val initialState = CounterState(count = 0)
val action = CounterAction.Increment
val (newState, effects) = reducer.reduce(initialState, action)
assertEquals(1, newState.count)
assertTrue(effects.isEmpty())
}
@Test
fun `decrement action decreases count by one`() {
val initialState = CounterState(count = 5)
val action = CounterAction.Decrement
val (newState, effects) = reducer.reduce(initialState, action)
assertEquals(4, newState.count)
}
}
```
## Publishing π¦
### Quick Publish to All Repositories
```bash
# Publish to GitHub Packages + Maven Central
./gradlew :library:publish
```
### GitHub Actions (Recommended)
1. Go to **Actions** tab
2. Select **Publish Library** workflow
3. Choose release type:
- `all` - Publish to all repositories (GitHub Packages + Maven Central) β
- `github` - GitHub Packages only
- `sonatype` - Maven Central only
- `local` - Local testing only
### Auto-Deploy on Release
Create a release on GitHub to automatically publish to all repositories:
```bash
git tag v0.1.2
git push origin v0.1.2
```
For detailed instructions, see [PUBLISHING.md](PUBLISHING.md) and [GPG_SETUP.md](GPG_SETUP.md).
## Example Project π±
Check out the complete working example in the `example` module of this repository.
```bash
./gradlew :example:assembleDebug
```
## Contributing π€
Contributions are always welcome! Please refer to [CONTRIBUTING.md](CONTRIBUTING.md).
## License π
**kotlin-rex** is distributed under the MIT License. See the [LICENSE](LICENSE) file for more details.
## Credits π
Kotlin Rex is developed and maintained by [Pelagornis](https://github.com/pelagornis).
---
Made with β€οΈ by Pelagornis