How single event works in Jetpack Compose? - android-jetpack-compose

Without Jetpack Compose I am used to using LiveData then observe it, and when it changes, I do UI change (navigation, state, toast). But observe method in LiveData need owner.

If your LiveData is in a view model, you can observe it from any composable function like this, and navigate when the value of the LiveData changes:
val observedData: Boolean by viewModel.navigate.observeAsState(false)
if (observedData) {
navController.navigate("destination")
}
// reset the value of navigate in the view model

In your viewModel you can use an event channel like this
val events = Channel<Event>(Channel.BUFFERED)
And in your composable, you can consume it this way
val scope = rememberCoroutineScope()
scope.launch(Dispatchers.Main) {
viewModel.events.consumeEach { event -> handleEvents(event) }}

If you do not want to assign an owner to the observer, you can use the observeForever{...}call, or else, passing in the activity itself is always a viable option.
Good luck!

Related

How do Composables get notified to recompose when a MutableState changes?

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.

vue.js prop array/object vs literal

Props in vue.js are one way binding, by the way,in the documentation :
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
"Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child will affect parent state."
So i want to know, the prop.sync is it only for "litteral" (ie; string, number,date) or I must use it also with object/array ?
I already use object WITHOUT sync and all work very well, but i am fear it is not the good solution for do "vue.js" way ?
SO my question is : can i use object/array in prop,without sync, with no problem ?
In some cases, we may need “two-way binding” for a prop. Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child.
That’s why instead, we recommend emitting events in the pattern of update:myPropName. For example, in a hypothetical component with a title prop, we could communicate the intent of assigning a new value with:
this.$emit('update:title', newTitle)
You'll get errors if you mutate a prop directly in a child component. So in that sense, no it's not safe. The reason being is the parent will override anything the child sets.
Yes you can use objects and arrays as prop values. One difference, however, is when providing a default value for props of type object or array, you must define a factory function to return the default value.
The, main difference is the sync modifier is syntactic sugar that expands to a v-on listener. The child component emits events back the parent when a value changes, allowing the parent to update accordingly.
The child component must explicitly emit the event.
// example from docs
this.$emit('update:foo', newValue)

Publishing "existing event value" upon subscription

F# has a rather nice syntax for events, which can be subscribed to as observables without any custom code for that purpose. I am creating an event that publishes updates to a member variable. I intend to subscribe to this event as an observable, but I want the existing value (which I know exists) to be pushed on subscription. Is this possible and simple to do with the event syntax, or do I need to create a proper observable using e.g. BehaviorSubject?
This depends a lot on how you plan to use it.
When you convert from an event to an observable, the EventArgs are mapped through as the observable's type. With a "standard" event, this won't have a value (EventArgs doesn't carry any information).
However, you can easily use a custom event type, or event violate normal .NET guidelines for events and use the value itself:
let evt = Event<int>()
let obs = evt.Publish :> IObservable<_>
obs |> Observable.add (fun v -> printfn "New value: %d" v)
evt.Trigger 3
evt.Trigger 4
That being said, depending on your use case, you may want to look at Gjallarhorn. This library was specifically designed for tracking changes to mutable values and signaling nicely. It's built around the concept of a "signal", which is an observable that contains a current value. This makes the above concept first class - you can pass something (a signal) that can directly be used as an IObservable whenever needed, but also can always be used to get the underlying, current value. In practice, this dramatically simplifies many use cases.

Creating a listener on a list for changes

I have a dart object which contains a list. Simply put:
class Test extends PolymerElement{
List list = [];
addTask(item){
list.add(item);
}
}
and I wanted to implement a listener on that list in another object:
class listenerClass extends PolymerElement {
Test get _data => $['Object'];
}
So the object is retrieved from the dom withthe getter.
Is there a way to observe a change in the parent? I tried the following:
#Observe("_data.list")
dataListChanged(_,__){
var item = _data.list.last;
//do magic with item.
}
I have the underscore because it is private and isnt exposed to Dom or anything... but the list itself isnt a property so it doesnt notify or anything.
I was hoping there was a way to do some sort of listener for it though.
My desired endstate is that I want to fire a function in the parent whenever an item is added to the list, with only the reference to the child object as defined above. Even though this _data is populated by way of Polymer, Since this isnt touching properties at all, the answer may likely just be Pure dart.
This is not how polymer works. Polymer framework has no way to know you have defined a getter in your class and emit the right notification.
You should add notify to the list property of Test and bind that to a property in listenerClass. Then observe that variable.
But probably you will have better luck with ObservableList and using autonotify for a simpler way to use observability with polymer-dart projects.

Dart Observable Change Handler

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.

Resources