jinyus / dart_beacon

A reactive primitive (signals) and state management library for dart and flutter
https://pub.dev/packages/state_beacon
28 stars 2 forks source link

New Core #63

Closed jinyus closed 6 months ago

jinyus commented 6 months ago

Description

cc: @btrautmann

This is the implementation of the new core of state_beacon that uses the algorithm from reactively and oby.

Pros:

BeaconScheduler.useFlutterScheduler();
BeaconScheduler.use60fpsScheduler();

For flutter apps, it's recommended to use the flutter scheduler. The method must be called in the main function of your app.

void main() {
 BeaconScheduler.useFlutterScheduler();

 runApp(const MyApp());
}

Cons:

[!NOTE]
This only applies to pure dart tests. In widgets tests, calling tester.pumpAndSettle() will flush the queue.

final a = Beacon.writable(10);
var called = 0;

// effect is queued for execution. The scheduler decides when to run the effect
Beacon.effect(() {
      print("current value: ${a.value}");
      called++;
});

// manually flush the queue to run the all effect immediately
BeaconScheduler.flush(); 

expect(called, 1);

a.value = 20; // effect will be queued again.

BeaconScheduler.flush();

expect(called, 2);

Type of Change

btrautmann commented 6 months ago

Haven't looked at the code, but wanted to just comment on the trade-off RE: needing to invoke flush to propagate Beacon changes. This seems OK to me. To be honest I'm already used to doing pumpEventQueue() for dealing with Streams in tests, this seems similar (I'm surprised that doing that wouldn't also automatically handle Beacons as well? Why is that?)

One question:

Asynchronous by default

Does this mean that we wouldn't have to be so careful about async gaps in things like derivedFutures anymore? I.e a beacon accessed after an async gap would still be picked up as a dependency of that beacon?

jinyus commented 6 months ago

I'm surprised that doing that wouldn't also automatically handle Beacons as well? Why is that?

Any sort of awaiting will flush the queue. even await Future.delayed(Duration.zero) unless you're using a custom scheduler.

Does this mean that we wouldn't have to be so careful about async gaps

Unfortunately, no... I tried this but it's impossible to do this correctly, futures can be paused/resumed anytime by the VM and I wouldn't know which future was running when the beacon is accessed. Still researching though. It would be possible with a ref-like argument to the future, not sure how ergonomic that'd be.

Beacon.future((use){
  final count = use(countBeacon);
  await someOp();
  final name = use(nameBeacon)
})

What do you think about that?

ps: the regular Beacon.future is now a derived beacon so I'll deprecate derivedFuture. Same for Beacon.stream.

btrautmann commented 6 months ago

Still researching though. It would be possible with a ref-like argument to the future, not sure how ergonomic that'd be.

I'm hesitant about a ref-like argument as that was one of my peeves with riverpod. It felt too magical to me, and the Ref was actually the thing that was becoming "invalid" across async gaps (I think the specific issue I had was that I'd access another provider that itself had an async gap and at that provider would recompute and then the ref became invalid so then on the next access it'd blow up. It didn't cause runtime issues/errors but my console was always blowing up during development which made things feel very brittle). The lack of any perceived brittleness with state_beacon has made me happy I don't have to deal with anything ref-like.

jinyus commented 6 months ago

Very valuable feedback, thanks. Still in the design phase so nothing is concrete yet. I will try my best to break it before it's shipped to users.

btrautmann commented 6 months ago

Very valuable feedback, thanks. Still in the design phase so nothing is concrete yet. I will try my best to break it before it's shipped to users.

Cool cool. Just a heads up that I shot you a few questions on Discord 👀