Skip to content

programadorthi/kotlin-state-manager

Repository files navigation

kotlin-state-manager

A multiplatform and extensible state manager. Its wrapper the managed value to deliver a better, easy and extensible way. It's like a Value class with powerS.

The project is not a replacement for Coroutines Flow or Compose State. Your origin is from 2022 when Compose wasn't multiplatform.

How it works

There are a lot of ways to use a Value Manager

As a basic variable

class CounterViewModel {
    val counter = basicValueManager(initialValue = 0)
    val counterFlow = counter.asMutableStateFlow() // StateFlow version
    
    var value by basicValueManager(initialValue = 0) // Delegate property version available
}

Updating it value

class CounterViewModel {
    fun increment() {
        anyValueManagerType.update { current -> current + 1 }
        anyValueManager = anyValueManager + 1 // update method as a Delegate property
        anyValueManager++ // same as previous
    }
}

Collecting value changes

class CounterViewModel {
    fun listen() {
        anyValueManagerType.collect {
            // collect without suspend is available
        }
        
        coroutinesScope.launch {
            flowValueManagerType.collect {
                // suspend collect available in Flow
            }
        }
    }
}

Inside Jetpack Compose

@Composable
fun HomeScreen() {
    val counter = remember { basicValueManager(initialValue = 0) }
    var counterState by remember { counter.asState() } // remember or rememberSaveable are available
    
    // Update and listen operations are the same
}

Listening for errors

class CounterViewModel {
    val counter = basicValueManager(initialValue = 0)
    
    init {
        counter.onError {
            
        }
    }
}

Listening for changes

class CounterViewModel {
    val counter = basicValueManager(initialValue = 0)
    
    init {
        counter.onChanged {
            
        }
    }
}

Validations are supported

class PositiveValidator(
    override val message: (Int) -> String = { "Value $it should be positive" }
) : Validator<Int> {
    override fun isValid(value: Int): Boolean = value > 0
}

val counter = basicValueManager(initialValue = 0)

counter.addValidator(PositiveValidator())
// or
counter += PositiveValidator()

counter.onValidated {
    // Listen on each validation operation
}

// Put a value don't trigger validations
counter.value = -1
// Call validate() to trigger validations
counter.validate()

// Calling update always trigger validations and don't need call validate()
counter.update { -1 }

// Checking is valid
counter.isValid()

// Getting validators messages
counter.messages()

Serialization

A value manager can be encoded or decoded using Kotlin Serialization and serialization module. It is a good use case whether you have model shared with serialization infrastructure as network requests

import dev.programadorthi.state.serialization.ValueManager // Not from core package

@Serializable
data class MyClass(
    val count: ValueManager<Int>,
)

val data = MyClass(count = basicValueManager(1))
val json = Json.encodeToString(data)
println(json) // {"count": 1}

val decoded = Json.decodeFromString<MyClass>(json)
println(data == decoded) // true

State Restoration

Compose

val counter by rememberSaveableValueManager { ... }

Without remember function or using a class to manager, you need to pass a SaveableStateRegistry

class MyComposeViewModel(stateRegistry: SaveableStateRegistry) {
    private var counter by composeValueManager(0, stateRegistry = stateRegistry)
}

Checkout MVIViewModel sample for more details

Android

class MyActivity : ComponentActivity {
    private var counter by androidValueManager(0)
}
class MyFragment : AndroidXFragment {
    private var counter by androidValueManager(0)
}
class MyViewModel(savedStateHandle: SavedStateHandle) : AndroidXViewModel {
    private var counter by androidValueManager(0, savedStateHandle = savedStateHandle)
}

Checkout MainActivity sample for more details

Do you prefer inheritance over composition?

class CounterValueManager : BaseValueManager<Int>(initialValue = 0) {
    // Now all operations is available here
}

Samples

Samples folder have a mix of usage.

Close usage to real project here