escamoteur / watch_it

MIT License
103 stars 8 forks source link

[Request] Add a watch method that select a values from a model but return the model instead of the selected value #7

Closed MohiuddinM closed 10 months ago

MohiuddinM commented 10 months ago

Similar to: bool loggedIn = watchValue((UserModel x) => x.isLoggedIn) but instead of returning the value it returns the notifier when the selected value (isLoggedIn) changes: UserModel userModel = watchNotifier((UserModel x) => x.isLoggedIn).

This function would be a shorthand for a watchValue followed by a get.

escamoteur commented 10 months ago

why not

final userModel = watchtIt<UserModel>()
MohiuddinM commented 10 months ago

why not

final userModel = watchtIt<UserModel>()

This would trigger every time the model changes, but I only want to trigger on selected property. It can also work if watchIt can take a selector as an optional arg.

escamoteur commented 10 months ago

hmm, that would make the Api more difficult to use, I would then use probably

final userModel = di<UserModel>()
watchPropertyValue((x) => x.isLoggedIn, target: userModel);

that communicate clearly what you want to do.

MohiuddinM commented 10 months ago

hmm, that would make the Api more difficult to use, I would then use probably

final userModel = di<UserModel>()
watchPropertyValue((x) => x.isLoggedIn, target: userModel);

that communicate clearly what you want to do.

Ok, I got it thanks.

On another note, I was going through your code I found out that it depends on a global variable: https://github.com/escamoteur/watch_it/blob/337cf041a0cda58bead8a1f98ee652f4fa4474c6/lib/src/elements.dart#L3

Don't you think that could create a problem when multiple widgets are built? And also don't you just communicate between the widget and the element by simply passing the BuildContext to watch functions?

escamoteur commented 10 months ago

this is actually the trick that flutter_hooks to uses, its never a problem because dart flutter is single threaded only one widget is built at the same time, see https://github.com/escamoteur/watch_it#lifting-the-magic-curtain

MohiuddinM commented 10 months ago

Good to know. I saw it yesterday and was curious so I made a little POC to also understand how the thing works:

mixin WatchElement on ComponentElement {
  final _listeners = <Listenable, ListenerEntry>{};

  void watch<R extends Listenable>(R model, {Selector<R>? selector}) {
    void callback() {
      if (mounted) {
        if (selector == null) {
          return markNeedsBuild();
        }

        final selection = selector(model);
        final oldSelection = _listeners[model]!.selection;

        assert(selection is! Map || selection is! Set || selection is! List);
        assert(
          oldSelection is! Map || oldSelection is! Set || oldSelection is! List,
        );

        if (selection != oldSelection) {
          markNeedsBuild();
        }

        _listeners[model] = ListenerEntry(callback, selection);
      }
    }

    final selection = selector?.call(model);
    final entry = ListenerEntry(callback, selection);
    _listeners[model] = entry;
    model.addListener(entry.callback);
  }

  void unwatch<R extends Listenable>(R model) {
    final entry = _listeners.remove(model);

    if (entry != null) {
      model.removeListener(entry.callback);
    }
  }

  @override
  void unmount() {
    _listeners.keys.forEach(unwatch);
    super.unmount();
  }
}
mixin Watchable {
  R watch<R extends Listenable>(
    BuildContext context,
    R model, {
    Selector<R>? selector,
  }) {
    if (context is! WatchElement) {
      throw ArgumentError('watch can only be called inside WatchingWidget');
    }

    context.watch(model, selector: selector);
    return model;
  }
}
escamoteur commented 10 months ago

the intersting point comes when you want to allow multiple watches inside the same build method

MohiuddinM commented 10 months ago

the intersting point comes when you want to allow multiple watches inside the same build method

Do you mean multiple watches for the same model or different models?

escamoteur commented 10 months ago

Can me multiple watches on different properties of the same object or on different objects but inside the same build method. That's why the order of the watches has to stay the same between build alls as the only way to do that is to store the in a list and increment a counter on each watch call that gets reset to zero at tge begin of each build. Am 25. Aug. 2023, 23:12 +0200 schrieb Muhammad Mohiuddin @.***>:

the intersting point comes when you want to allow multiple watches inside the same build method Do you mean multiple watches for the same model or different models? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

MohiuddinM commented 10 months ago

My POC will work for multiple watches on the same model, I guess even if you change the order or use watch functions conditionally. E.g. Subscribe to a property only if there is a certain change.

I have uploaded the full code: https://github.com/MohiuddinM/watch_it_poc/tree/master

escamoteur commented 10 months ago

Thanks, will have a closer look at it soon