liangxianzhe / creator

A state management library that enables concise, fluid, readable and testable business logic code.
MIT License
213 stars 19 forks source link

listen and listenSelf equivalents #27

Closed bcihanc closed 1 year ago

bcihanc commented 1 year ago

Hi, once again thanks for this great package 👍

Is there listen and listenSelf methods equivalents in Creator?

liangxianzhe commented 1 year ago

Hi!

creator.change can be used for listening. It is better in my mind because it is just a normal creator that returns a wrapper class with previous and current value. You can use the value however you want, for example, having a Creator that watch the change and do stuff. See https://github.com/liangxianzhe/creator#listen-to-change

I'm not too sure about listenSelf, why would someone listen to self? Creator has an API called readSelf though.

bcihanc commented 1 year ago

I don't want to create a second creator for just listen another creator. Maybe I can figure it out with change. Thanks mate 😊.

I close this.

bcihanc commented 1 year ago
final langLocalValueEmitter = Emitter<String>((ref, emit) async {
  String? lang = await _langLocalValue.read();
  if (lang == null) {
    await _langLocalValue.write('en');
    lang = 'en';
  }
  emit(lang);
}, keepAlive: true, name: 'languageLocalValue');
Watcher(listener: (ref) async {
      final langChange = await ref.watch(langLocalValueEmitter.change);
      if (langChange.before != null && langChange.after != langChange.before) {
        _langLocalValue.write(langChange.after);
      }
    }, (context, ref, child) {
      final lang = ref.watch(langLocalValueEmitter.asyncData).data;
      return MaterialApp.router( );
    })

I finally found a example for that. How can I listen emitter change without a Watcher?

liangxianzhe commented 1 year ago

How can I listen emitter change without a Watcher?

That depends on where you want to watch and what you want to do.

Option 1 - Using watcher like in your code. Is there a reason you don't like it? Adding a watcher is cheap.

Option 2 - Put the listen logic in a Creator inside a State, watch it in didChangeDependencies and dispose it dispose. If you really hate repeating these two steps, then maybe use a mixin like this:

/// Add a creator to stateful widget that got disposed automatically when the
/// state is disposed.
mixin CreatorMixin<T extends StatefulWidget> on State<T> {
  // A creator that is typically set in initState and updated in didChangeWidget
  // as needed.
  Creator? _creator;
  set creator(Creator value) {
    if (_creator == null) {
      _creator = value;
    } else {
      ref.dispose(_creator!);
      _creator = value;
      ref.watch(_creator!);
    }
  }

  late Ref ref;

  @override
  void didChangeDependencies() {
    ref = context.ref;
    if (_creator != null) {
      ref.watch(_creator!);
    }
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    if (_creator != null) {
      ref.dispose(_creator!);
    }
    super.dispose();
  }
}

Option 3 - If the logic is global, you can watch the creator in main(). Something like:

  final app = CreatorGraph(child: const MyApp());
  await app.ref.watch(someEmitter);
bcihanc commented 1 year ago

Thanks for options. How about this?

extension RefX on Ref {
  void listen<T>(Creator<T> creator, void Function(Change<T> change) change, {String? name, bool keepAlive = false, List<Object?>? args}) {
    watch(Creator(
      (ref) => change(ref.watch(creator.change)),
      name: name ?? '${creator.name}_listen',
      keepAlive: keepAlive,
      args: args,
    ));
  }
}

ref.listen(creator, (change) { });

or

extension CreatorExtension<T> on Creator<T> {
  void listener(Ref ref, void Function(Change<T> change) cb, {String? name, bool keepAlive = false, List<Object?>? args}) {
    ref.watch(Creator(
      (ref) => cb(ref.watch(change)),
      name: name ?? '${infoName}_listen',
      keepAlive: keepAlive,
      args: args,
    ));
  }
}

creator.listener(ref, (change) {});
liangxianzhe commented 1 year ago

Hi, your options are fine. Just remember to make the temporary creator stable (with args) if the ref in your last line could rebuild by others. For example:

final someCreator = Creator((ref) {
  ref.watch(fooCreator);
  ref.listen(barCreator, (change) { print(change); }, args: ["some args to keep it stable"]); 
  return "something";
});

Though personally I would prefer:

final barLogger = Creator((ref) => print(ref.watch(barCreator.change)));

final someCreator = Creator((ref) {
  ref.watch(fooCreator);
  ref.watch(barLogger);
  return "something";
});
bcihanc commented 1 year ago

Thanks mate, I'm gonna use listen out of context like this.

extension RefX on Ref {
  void listen<T>(CreatorBase<FutureOr<T>> creator, void Function(Change<T> change) onChange, {String? name, bool keepAlive = false, List<Object?>? args}) {
    if (creator is Creator<T>) {
      watch(Creator((ref) {
        onChange(ref.watch(creator.change));
      }, name: name, keepAlive: keepAlive, args: args));
    } else if (creator is Emitter<T>) {
      watch(Emitter((ref, emit) async {
        onChange(await ref.watch(creator.change));
      }, name: name, keepAlive: keepAlive, args: args));
    } else {
      throw Exception('creator must be Creator or Emitter');
    }
  }
}

main() {
  await globalRef.read(langLocalValueEmitter);
        globalRef.listen(langLocalValueEmitter, (change) {
          if (change.before != null && change.after != change.before) {
            _langLocalValue.write(change.after);
          }
        });
}

runApp(...)