Provides observable properties and one-time commands.
Note: you don't need this module in Jetpack Compose project. Use mutableStateOf
and derivedStateOf
instead.
Observable property - a property that notifies about its changes.
Computed property - a read-only observable property that is automatically kept in sync with other observable properties.
Command - one-time action such as showing a toast or an error dialog.
Property host - a class that contains observable properties and commands.
Property observer - a class that observes properties and handles commands.
- Implement
PropertyHost
interface inViewModel
:
class CounterViewModel : ViewModel(), PropertyHost {
override val propertyHostScope: CoroutineScope get() = viewModelScope
...
}
- Declare observable properties and commands:
var count by state(0)
private set
val plusButtonEnabled by computed(::count) { it < MAX_COUNT }
val showMessage = command<String>()
- Change properties and send commands when it is required:
fun onPlusButtonClicked() {
if (plusButtonEnabled) {
count++
}
if (count == MAX_COUNT) {
showMessage("It's enough!")
}
}
- Implement
PropertyObserver
inActivity
orFragment
:
class CounterFragment : Fragment(), PropertyObserver {
override val propertyObserverLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner
...
}
- Bind UI to View Model:
private val binding by viewBinding(FragmentCounterBinding::bind)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
plusButton.setOnClickListener { vm.onPlusButtonClicked() }
vm::count bind { count.text = it.toString() }
vm::plusButtonEnabled bind plusButton::setEnabled
vm.showMessage bind { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() }
}
}
Sometimes it is required to run some code in a PropertyHost
whenever certain properties are changed. You can do it with autorun
:
init {
autorun(::count) {
Log.d("Counter", "Count is changed: $it")
}
}
StateFlow is an observable data holder. It works fine, but the syntax is not ideal for View Model's properties. The goal of Sesame property component is to improve the syntax.
Compare:
val count: StateFlow<Int> get() = _count
private val _count = MutableStateFlow(0)
_count.value++
with:
var count by state(0)
private set
count++
By the way, Sesame properties use StateFlow
under the hood, so this is not competition but cooperation.
To convert a StateFlow
to an observable property use stateFromFlow
method.
To get a StateFlow
from an observable property use ::someProperty.flow
syntax.