So I am making the jump from dev11 to dev16 and ta...
# compose
g
So I am making the jump from dev11 to dev16 and taking on the model change. Previously I had a reusable view class that took in state (@Model) at construction. The the composable functions all used that state. In this way I can inject the bit of app state that a view needs to know about into each view.
Copy code
class LoginView(
    private var dataModel : LoginDataModel
)  : Component () 

...

    @Composable
    override fun view () {
        BottomDrawerLayout(
            drawerState = dataModel.drawerState,

            gesturesEnabled = true,
            drawerContent = {
                drawerView()
            },
            bodyContent = {
                baseView()
            }
        )
    }
My application model was all defined in one library with @Model. Now I am trying to move to the recommended porting described in dev12:
Copy code
class Position(x: Int, y: Int) {
 var x by mutableStateOf(x)
 var y by mutableStateOf(y)
}

// source of Example is identical to original
@Composable fun Example() {
 var p = remember { Position(0, 0) }
 PositionChanger(
   position=p,
   onXChange={ p.x = it }
   onYChange={ p.y = it }
 )
}
However, if you look at mutableStateOf it says you should use state and stateOf instead. Then there is a bunch of talk of snapshots. This all leaves me scratching my head wondering how I create a reusable view that can be injected with state. Any good examples, explanations, or other help out there?
My model used to be defined as data classes. Now do they all need to be changed to classes with mutableStateOf? If I have a top level class that contains all of my application state do I declare that as mutable and it roles down to all the subclasses? How are primitives and collections handled?
Ultimately, I want my model to drive my views. What is the correct way to hook up data to a view with the latest compose.
And how does all this then relate to stateflow
Model was much more straightforward.
v
See the migration notes in this link - https://developer.android.com/jetpack/androidx/releases/compose#0.1.0-dev14 You basically have two options.
g
Dev14 says recomposition is driven by mutablestate assignments.
v
If all your state is in the top level, you will have to pass the MutableState instances to the children composables. Having said that, there are always going to be cases where you don’t really need that state at the top level and it could’ve just been declared in the child composable.
g
We keep the whole app model in one place for easy debugging, persistence, etc.
it acts as the decoupling point between the view and view-model
Dev14 notes don’t provide a clear example of how to manage state for compose.
I really need to move this forward. Any help would be greatly appreciated.
So it looks like the jetpack compose playground has this example:
Copy code
@Composable
@OptIn(
    ExperimentalFocus::class,
    ExperimentalKeyInput::class
)
private fun FocusableText(text: MutableState<String>) {
    var color by state { Color.Black }
    val focusRequester = FocusRequester()
    Text(
        modifier = Modifier
            .focusRequester(focusRequester)
            .focusObserver { color = if (it.isFocused) Color.Green else Color.Black }
            .focus()
            .tapGestureFilter { focusRequester.requestFocus() }
            .keyInputFilter { it.value?.let { text.value += it; true } ?: false },
        text = text.value,
        color = color
    )
}
It looks like you have to declare your whole model as mutable state and the primitive level.
It also looks like ModelMap is replaced with mutableStateMapOf and ModelList is now mutableStateListOf?
v
For state management, you have a few options available - 1. Pass callbacks that are interacting with the viewModel functions to trigger events. This way, your composables remain generic and they don’t know anything about the view model itself. Only pass viewmodels to composables where absolutely necessary. Here is how JetNews does it - https://github.com/android/compose-samples/blob/master/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt#L79. Look at how
AppContent
accepts the
navigationViewModel
but all the child composables have no information about the view model. They just receive callbacks. This way, you can continue making state changes using the view model, without your composable knowing anything about the view model itself. 2. Your second option is similar to the first one with a slight difference. Assuming you are storing your view state in LiveData/Flow/Rx objects, you can convert them to compose primitives using the
observeAsState
extension function. That way, you can pass around state objects in compose land while continuing to use your existing setup.
g
my view model has all the actions. It manipulates the model. The model drives the view.
I have not seen anything about livedata, flow , rx objects with compose.
oh your view model has actions which are updating a
@Model
class? Can you post some code of what that “action” looks like.
g
For example here is the ViewModel action for a button push:
Copy code
fun flashAction() {
    // if the control is not disabled make the request
    if (!appModel.homeView.flashControl.state.disabled && !appModel.homeView.flashControl.state.transition ) {
        appModel.homeView.flashControl.startTransition()
        var msg = UnitType.Flash()
        msg.value = !appModel.session.unitType.flash.value
        communication.tx(msg)
    }
}
the view model also receives all external communication from the cloud and bluetooth for the app.
Here is an example of an action for a external flash your lights message:
Copy code
fun flashRxAction (msg : UnitType.Flash, connection : Connections) {
    // update the application state
    appModel.session.unitType.flash = msg
    // update home view
    appModel.homeView.flashControl.active(appModel.session.unitType.flash.value)
    appModel.homeView.flashControl.stopTransition()
}
I just need a simple way to inject state into a view.
For instance this is the simple model behind a generic control:
Copy code
//@Model
data class ControlState (
    var active : MutableState<Boolean> = false ,
    var disabled : Boolean = true,
    var transition : Boolean = false
)
The control changes its color based on active, disabled, and transition state.
note I had started adding mutablestate
active was just like the other declarations.
v
I just need a simple way to inject state into a view.
Ambients are one way to do this although my personal preference is to pass the state objects all the way through to the composables instead of injecting. I expect this area to evolve as we get closer to the 1.0 release. Still early days until we figure out what feels great.
g
Basically every view inherits from a component class that has a view() function (the top level composable for that view). The top level view class constructor is passed in the part of the model that drives it (it generally is unaware of the big picture purposely to make it more reusable).
So I am not using anything crazy like Dagger, just passing in the model at view construction time.
I actually think we have a very clean setup.
For mutablestate can you declare that on a top level class or does every field also have to be designated?
So here is the before and after of a model class:
Copy code
Before:
@Model
data class ControlState (
    var active : Boolean = false ,
    var disabled : Boolean = true,
    var transition : Boolean = false
)

After:
class ControlState (
    active : Boolean = false,
    disabled : Boolean = true,
    transition : Boolean = false
) {
    var active by mutableStateOf(active)
    var disabled by mutableStateOf(disabled)
    var transition by mutableStateOf(transition)
}
Hopefully this doesn’t muck up serialization.