thlorenz / rid

Rust integrated Dart framework providing an easy way to build Flutter apps with Rust.
64 stars 4 forks source link

Best practice for updating other widgets on state change #41

Closed SecondFlight closed 2 years ago

SecondFlight commented 2 years ago

Let's say I have two widgets: MyStateEditor and MyStateReadOnlyView. Both are independent from each other and may be instanced anywhere in the widget tree, and both deal with a singleton state object called MyState.

Now let's say that MyStateEditor makes a change:

widget._store.msgUpdateState().then(() {
  setState(() {});
});

MyStateEditor will now re-render in response to the state change, but MyStateReadOnlyView will be stale.

What is the best way to handle this?

--

My thought after scanning through your multithreaded TODO video is that BLoC might be the missing piece. It's not a pattern I'm familiar with but it looks like an offshoot of the observable pattern. For BLoC to be a viable solution, I would expect to be able to:

  1. Have one bloc (?) per piece of state
  2. Be able submit state changes from anywhere in the UI via a bloc provider
  3. Be able to listen to state changes on any bloc from anywhere in the UI

From what I've read it looks like BLoC provides all these. Does that sound correct?

thlorenz commented 2 years ago

This is why the replyChannel has a stream which you can subscribe to in order to listen to any replies for a specific topic even from places where you didn't send the message causing that reply.

See an example here and here.

This can be done similarly without bloc/cubit as demonstrated here.

So to your last question rid provides it but I find the pattern to orchestrate communication to/from Rust via cubits a great pattern and highly recommend it. But it is not needed, all you need is the replyChannel.stream.

thlorenz commented 2 years ago

Responding in detail to 1 - 3:

1.

I'd recommend reading up on bloc and cubit (the latter is just a less boilerplatey version of the former) and/or watch some of the tutorials out there. Bloc/cubit is not like redux at all instead it just allows you to abstract logic (in our case communication to rid) from views without a global dispatcher/reducer. I'd say you'd have one cubit per view, i.e. one for a list and one instance for each item in that list if it has functionality.

There are also cubits that are used from various points in your app, i.e. a config that is read from anywhere but set in the menu view only.

2., 3.

Yes, views will context.read<T> the cubit for their particular scope and call method on it which causes it to delegate to Rust and as a result refresh the view state which the view also listens to via a StreamSubscription.

The todo_cubit example as well as the reddit ticker demonstrate how I have implemented this approach so far.

Hope this helps, but feel free to ask more questions so I can help more.

SecondFlight commented 2 years ago

This is incredibly helpful. replyChannel.stream is the big piece of architecture that I was missing in my mental model, and there's a lot that makes sense now. I also didn't realize rid-examples has things that rid/examples doesn't, so I will definitely be reading up on that as well (along with more on bloc/cubit + etc).

Thanks for your detailed response as always!