I am going through the Compose pathway and I keep seeing all of this emphasis on state hoisting. Is this in some way more relevant in the new Compose framework as compared to the old paradigm? It seems like it is just a good programming technique in general. I am worried I am missing a particular advantage in the Composable system.
From the perspective of an Activity or a Fragment they are nearly identical. A stream of states come in and the Activity or Fragment needs to react to the changes by updating its content.
In Compose, however, the composables are themselves tiny fragments of UI all the way down, so to speak. In Compose you can arbitrarily break a UI into smaller and smaller pieces that each react to state changes. It is in this decomposition (so to speak) that state hoisting becomes important and allows these smaller UI pieces to be reusable. Even when you use a Text or a Button they are just composable functions that layout and draw text or map user input to click events, etc.
If state is already in the UI model then it is already hoisted. No additional hoisting is necessary. State hoisting is only important if the composable has its own model of information. The TextField, for example, takes its state as a parameter instead of creating and holding it internally. This allows the state to be held directly in the UI model instead of needing to be synchronized with it. The model can decide if an when the state changes, the TextField just requests the state to change, it doesn't control it. Traditionally, a TextField would have it own internal model of the text and just send out onChange notifications when the value changed. This means there are at least two models of the value of the field, one held by the control and one held by the application model. Synchronizing these can be tricky.
Allowing hoisting of composable state allows there to be a single source of truth, the application model, instead of several states all needing to be synchronized to a single value. Compose does not dictate what the model is or how it is stored, it just needs to know when the value changes so it knows when to update the UI.
Related
If I want to trigger a switch in the SettingsScreen and then the HomeScreen reacts to the change, what should be the best practice? Should I collect the same flow across two screens? Or should I update the UI state through the life cycle after returning to the HomeScreen?
It's better for storing that value inside a data store and observing updates in HomeScreen and just updating its value on SettingScreen.
of course if you provide more detail for example your codes, i can help better
I've seen two different suggested methods of hiding widgets in Compose.
Set alpha to zero, analogous to Android's View.INVISIBLE because the widget still takes up space.
Text("I'm visible", Modifier.alpha(if (shouldShowIt) 1f else 0f))
Completely omit the element if it shouldn't be shown, analogous to Android's View.GONE.
if (shouldShowIt) {
Text("I'm visible")
}
I would assume that in case 1, Compose will reuse the same widget element after a recomposition and merely draw it differently.
However, in case 2, does Compose have to allocate a new Text widget instance from scratch if the text was not shown during the previous recomposition but then becomes visible again in another recomposition? Or, does it use intelligent pooling of these widget classes?
From briefly browsing the source code, as far as I can tell, the nodes that we are building are automatically pooled. I'm specifically wondering about the UI elements that are generated from the nodes (the classes that are internal to the library but are responsible for the actual rendering). Or are these the same classes as the nodes themselves?
If pooling is not used, I would guess that the first method would be better for performance, even though the code is uglier.
My application is to support only landscape content. That content will be generated on-the-fly during runtime, i.e. there will be lots of addView calls to instantiate the UI hierarchy, with some ObjectAnimator calls to move widgets around.
At some point the user might rotate the device to reverseLandscape, insert it in a dock, etc. triggering an activity restart.
Since the appearance on screen should remain the same after the config change I am looking for a lightweight solution to retain the hierarchy.
Adding android:configChanges="orientation|screenSize|keyboardHidden" is discouraged by Google since it does not cover all configuration change cases.
I could create a fragment to contain the UI and call its setRetainState(true) - but again Google discourages that use.
Is there any kind of robust serialization approach to simply write out the current activity state and recreate it once the config change is complete ?
I'm referring to Why use Fragment#setRetainInstance(boolean)?
The reason I ask so is for Activity to handle rotation, Official Activity Documentation encourages us to let Activity shut-down and restart during rotation.
android:configChanges Lists configuration changes that the activity
will handle itself. When a configuration change occurs at runtime, the
activity is shut down and restarted by default, but declaring a
configuration with this attribute will prevent the activity from being
restarted. Instead, the activity remains running and its
onConfigurationChanged() method is called. Note: Using this attribute
should be avoided and used only as a last-resort. Please read Handling
Runtime Changes for more information about how to properly handle a
restart due to a configuration change.
Any attempt to change this Activity default behavior seems to be bad practice. To avoid Activity from reloading time consuming data structure during restarting, we make make use of onRetainNonConfigurationInstance and getLastNonConfigurationInstance. - Official Handling Runtime Changes
However, when comes to handling rotation in Fragment, does Google give us different recommendation? They do not want us to shut down and restart Fragment?
public Object onRetainNonConfigurationInstance ()
This method was deprecated in API level 13. Use the new Fragment API
setRetainInstance(boolean) instead; this is also available on older
platforms through the Android compatibility package.
Why does Google encourage us to shut down and restart Activity during rotation, but encourage us to retain Fragment during rotation?
If setRetainInstance(true) is good in handling rotation, why don't Google make it as Fragment's default behavior?
Configuration changes: when suddenly screen becomes much wider and much less in height (typical landscape), it is apt for a visual component to update its display and more intelligently use the screen available. Another examples of config change are user sliding the hardware keyboard, device language changing, and so on. why re-start :
Android components favor declarative layout, you load a bunch of XML layouts, and work from there. Finding every View and re-arranging/updating it in real time will be a mess, not to mention the re-wiring of all the event handlers and other custom View code. Its way easier to reload another bunch of layout files.
Also, In Android, Activities kind of live at the mercy of system, so naturally, Activity life cycle is so designed (and recommended) that it is capable of re-creating itself on demand , any time, just as it was before it was destroyed. This pattern accommodates all re-starts, those due to configuration changes as well. If you make your Activities and Fragments capable of maintaining an eternal state, configuration changes won't be that much of a problem.
Retain state data (Models), not the stuff displaying it (UI and Views).
setRetainInstance(true): It is recommended only to be used with fragments that do not hold any reference to anything, that will be recreated on rotation. This means you should not use it on any Fragment that holds Context, Views, etc. A typical Visual fragment does. But it is very useful with Fragments that hold objects like running Threads, AsyncTasks, Data Collections, loaded assets, fetched results etc. This method helps in using a non visual Fragment, as a detachable holder, for non Context-dependent objects of an Activity.
Because you are misunderstanding its use. setRetainInstance(true) should only be used in fragments that are like solo elements/modules. Fragment that handle sockets etc. an don't have a GUI really benefit from being retained. Fragments with a GUI should probably not use setRetainInstance(true). Also any fragments that goes to the backstack shouldn't use setRetainIstance(true).
You could generalize it to any fragment which handles only data/connection etc. should use setRetainInstance(true). But there is a multitude of different ways to use Fragments, which wouldn't benefit of setRetainInstance(true).
Does the Android platform lend itself well to a particular style of UI programming like MVC or MVP? Most of my UI experience is with spaghetti code on a very old embedded device or in GWT with MVP so I do not know where to start.
The MVC Pattern is more or less pre build into android.
You have three layers consisting of:
The Model Your data classes, Content providers etc. wrapping all your data.
The Controllers Treat all your activities as controller classes. Don't do anything in them that looks like business logic or data persitance. Just react to events from the model or the user and forward them to the correct layer.
The View Often the Activities are called the view because there it is the java code that is closest to the views. But in my opinion the view layer in Android is mostly defined in xml. You define your buttons, images, state changes etc in xml and then connect it with your application through your Activities.
There are some simple rules to follow to have a basic separation of this layers.
Define as much of your UI in xml only
instantiate Views yourself if there
is no other way to achieve something,
don't change the graphical state of
views from code, for example don't
change the background of a button if
the button is deactivated, or the
color of a font if a button was
clicked, do all this through stateful
drawables, and selectors in xml.
Don't do any data saving or logic in
your activity classes. Call to extra
model classes for this purpose. This
will make your activities clean and
short
If you want to change your data think
about going through a full
controller changes model -> model
informs controller about changes
-> controller changes UI cycle instead of having the controller
change the model and the UI
directly because other observers
of the modes may not be notified.
I do not know if the Android lends itself well to a specific design pattern when it comes to UI development per se, you can certainly use a particular pattern if it helps.
When in doubt you can check out the standard User Interface Guidelines and see what the guidelines are for particular interactions.