I don't understand how the Compose system "knows" that it needs to recompose certain composables when MutableStates that they're observing change value. For example, AIUI, I could write something like:
class StateHolder {
val theState by mutableStateOf("The state")
}
...
#Composable
fun StateDisplay(stateHolder: StateHolder) {
Text(stateHolder.theState)
}
Then, elsewhere in my code I can assign a new value to theState, and that will trigger a recomposition and the display will be updated to show the new value. (At the moment I'm not sure whether that means StateDisplay() gets run again, or just its Text child).
My question is, how on earth does a MutableState know what's observing it, and what it needs to do to update the observer? From the above code it looks like the MutableState can only know that something called its getter, but not know what called it, and the composables can only know that they called a getter, but not know when they need to call it again.
Similarly, I wondered if it was possible to observe a MutableState from elsewhere. It's probably not a conventional pattern in Android/Compose because all I could find on the subject was this answer about snapshotFlow. But again, how does the flow know when a new value needs to be emitted when it looks like the only relationship that's been established between the flow and state is that one called the other's getter?
To answer your question, Compose only recompose components that are actually consuming this data, as an example imagine you have a Parent component like
class MyViewModel : ViewModel(){
private val _myObservableStringInViewModel: MutableStateFlow<String> = MutableStateFlow("Hello World")
val myObservableStringInViewModel: StateFlow = _myObservableStringInViewModel.asStateFlow()
}
#Composable
fun ParentComponent(viewModel: MyViewModel){
val myObservedString by viewModel.myObservableStringInViewModel.collectAsState()
Scaffold{
MyChildComponent(data = myObservedString)
}
}
#Composable
fun MyChildComponent(data: String){
Text(text = data)
}
In this case MyChildComponent it's the only one that is going to be affected by recomposition, because compose only recompose those components that are actually using the data, also works the same with Text() components, imagine you have 3 observables MutableStateFlow, and 3 Text() components being feed by those respective, if one of those change, only the Text() component that is being feeded by this particular MutableStateFlow is the only one that is going to be re-composed.
And according to your second question, this example shows the most used approach to implement StateFlow in Compose, also you must use an activity or fragment between your viewModel and the composable, but there are many approaches to do this, so I'll leave this to your particular research, also you can use Compose ViewModel direct injection but this is in beta for now.
Related
Disclaimer: I have seen many similar questions posted already. However in all those questions, they were not aware of the update parameter to AndroidView. I am aware of the update parameter.
The problem
I have a legacy CustomView that has two methods to set its data. Let's call them setDataSlow() and setDataFast().
The setDataSlow() method is expected to be called only once in the lifetime of the view, and it is relatively slow.
The setDataFast() method needs to be called frequently, and is very fast.
So, the natural code in Compose would be something like this:
var data1 by remember {mutableStateOf(0)}
var data2 by remember {mutableStateOf(0)}
AndroidView(factory = {
val v = CustomView(it)
v.setDataSlow(data1)
v
}, update = {
it.setDataFast(data2)
}
)
The problem is, the factory lambda gets only called once, even when data1 changes. I understand this is by design.
Question
How do I solve this? If I call setDataSlow() inside update, it will slow down the UI tremendously.
I need to somehow force the factory method to update, and the only way I can do that is to force recompose of AndroidView somehow.
Do you see any way to know when ANY model’s property has been modified through a binding?
I would need something generic because it would be applied to all the forms of the application. This means I cannot just have a 'property’Changed() observable callback for every properties of the models. I’m thinking along the ways of overriding the properties setters created by the binding engine so they can call a single defined callback but I feel like there could be a better way.
I created a aurelia-plugin for this kind of scenario (and more).
Its not exactly what your asking for, but can help you a lot.
because the plugin will create a single property called isDirty that you can observe and fire your code accordingly.
https://github.com/avrahamcool/aleph1-aurelia-utilities
look at the Dirty Tracking a model: section
your model class need to extends the baseClass provided by the plugin.
now you can decorate any properties of your model with the
#dirtyTrack() decorator.
for babel users: the assignment in the declaration will set the
default value for the property. for TS users: you should call the
decorator with a parameter #dirtyTrack(7) someInt: number;
this will set up a isDirty variable in your model. this property will
be automatically updated to with every change to your tracked
properties.
at any point, you can call saveChanges() on your model, to commit the
current changes. or discardChanges() to revert back to the last saved
point. you can call serialize() to get a pojo object from your model,
or deserialize(pojo) to populate your model from a pojo object.
Ok, I ended up just using the binding engine to watch all properties changes. This allowed me to implement my isDirty checks without modifying the existing models...
So the final code looks like this:
Object.getOwnPropertyNames(obj).forEach(p => {
this.subscriptions.push(this.binding.propertyObserver(obj, p)
.subscribe(() => this.updateDirty()));
});
my updateDirty() method is called after every property change and no change was necessary to the model.
If anyone can come up with a better solution, I'm still interested but this fits my needs for the time being.
I found JavaFX to be suprisingly expressive after getting over the fact that I had to redeclare every field as property so I am a little stumped and would like to know if there is a better, more idomatic way of binding a boolean to some more complex operation like adding and removing style classes.
In this example I have a project that maybe valid or not and I want the font color of the tab to indicate which it is. I am doing this by adding and removing the error style class:
val errorSwitch = { valid : Boolean ->
logger.debug {"errorSwitcher: $valid"}
if( valid) tab.styleClass.remove("error")
else tab.styleClass.add("error")
Unit
}
product.isValid.onChange (errorSwitch)
errorSwitch(product.isValid.value)
What I don't like here is that I have to call the function once myself to start with because "onChange" obviously does not trigger unless the isValid actually changes. It's a small thing but I am wondering if there isn't a better way with bindings because thats what I want: the presence of the error class should be bound to "isValid"
In TornadoFX the best way to achieve this is to use the toggleClass function, which will automatically add or remove a given class based on a boolean observable value. Therefore you can simply say:
tab.toggleClass(Styles.error, product.isValid)
This example requires you to use the Type Safe CSS feature of TornadoFX. Read more about Type Safe CSS in the guide :)
I have an Polymer.dart element with multiple attributes, e.g.
<code-mirror lines="{{lines}}" widgets="{{widgets}}">
</code-mirror>
on some occasions lines and widgets change simultaneously sometimes only widgets changes.
I would like to rerender component once independently on how many properties change in the same turn of event loop.
Is there a way a good built-in way to achieve that?
Additional trouble here is that interpretation of widgets depends on content of lines and ordering in which linesChanged and widgetsChanged callbacks arrive is browser dependent, e.g. on Firefox widgetsChanged arrives first before linesChanged and component enters inconsistent state if I do any state management in the linesChanged callback.
Right now I use an auxiliary class like this:
class Task {
final _callback;
var _task;
Task(this._callback);
schedule() {
if (_task == null) {
_task = new async.Timer(const Duration(milliseconds: 50), () {
_task = null;
_callback();
});
}
}
}
final renderTask = new Task(this._render);
linesChanged() => renderTask.schedule();
widgetsChanged() => renderTask.schedule();
but this looks pretty broken. Maybe my Polymer element is architectured incorrectly (i.e. I have two attributes with widgets depending on lines)?
*Changed methods are definitely the right way to approach the problem. However, you're trying to force synchronicity in an async delivery system. Generally we encourage folks to observe property changes and react to them and not rely on methods being called in a specific order.
One thing you could use is an observe block. In that way, you could define a single callback for the two properties and react accordingly:
http://www.polymer-project.org/docs/polymer/polymer.html#observeblock
Polymer's data binding system does the least amount of work possible to rerender DOM. With the addition of Object.observe(), it's even faster. I'd have to see more about your element to understand what needs rendering but you might be creating a premature optimization.
I think there are three possible solutions:
See this: http://jsbin.com/nilim/3/edit
Use an observe block with one callback for multiple attributes (the callback will only be called once)
Create an additional attribute (i.e. isRender) that is set by the other two attributes (lines and widgets). Add a ChangeWatcher (i.e. isRenderChanged() in which you call your expensive render method)
Specify a flag (i.e. autoUpdate) that can be set to true or false. When autoUpdate = false you have to call the render method manually. If it is set to true then render() will be called automatically.
The disadvantage of solution 1 is that you can only have one behavior for all observed attributes. Sometimes you want to do different things when you set a specific attribute (i.e. size) before you call render. That's not possible with solution 1.
I don't think there is a better way. You may omit the 50ms delay (just Timer.run(() {...});) as the job gets scheduled behind the ongoing property changes anyway (my experience, not 100% sure though)
I'm looking for a way to use a handler function to respond to changes to an observable collection in Dart. I want to pipe my change directly to a function.
List things = toObservable([]);
//...
things.onChange.listen((e) => onThingsChanged(e) ); //blows up
//...
function onThingsChanged(e){
//...
}
There obviously isn't such thing as onChange, so what am I looking for here? All the examples I find are just watching the changes with a <template> tag in the HTML.
There is a good (official) article about Observables and Data Binding with Web UI. I think it is still under construction and thus there are no links on the dartlang.org website yet.
The part that answers your question is: Expression Observers
You can do something like this:
List things = toObservable([]);
observe(() => things, onThingsChanged);
onThingsChanged(ChangeNotification e) {
// ...
}
Few additions to Marco's answer which might not be obvious.
Besides observe which takes an expression, you can also use observeChanges which takes an instance of Observable, so you can write observeChanges(things, (c) => ...).
More important is the fact that if you use ObservableList outside of Web UI context (e.g. in a standalone script), the changes will not be triggered immediately. Instead, changes are queued and you need to call deliverChangesSync to trigger the notifications. The listener will then get notified with list of changes.