Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to remove a callback using Watcher.attach + Cubit #10

Closed
PackRuble opened this issue Dec 18, 2023 · 2 comments
Closed

Way to remove a callback using Watcher.attach + Cubit #10

PackRuble opened this issue Dec 18, 2023 · 2 comments
Assignees
Labels
discussion There's a lot to discuss enhancement New feature or request
Milestone

Comments

@PackRuble
Copy link
Owner

PackRuble commented Dec 18, 2023

This is generally the same story as in issue #9 with ChangeNotifier. However, a slightly more general syntax covering both cases at once appeared in my head:

mixin Detachability {
  List<VoidCallback>? _onDisposeCallbacks;

  void onDetach(void Function() cb) {
    _onDisposeCallbacks ??= [];
    _onDisposeCallbacks!.add(cb);
  }

  @protected
  void detach() {
    _onDisposeCallbacks?.forEach((cb) => cb.call());
    _onDisposeCallbacks = null;
  }
}

Which will make it possible to do things like this:

import 'package:bloc/bloc.dart';

class CubitImpl extends Cubit<int> with Detachability {
  CubitImpl() : super(0);
  
  void setValue(int value) => emit(value);

  @override
  Future<void> close() async {
    super.detach();
    return super.close();
  }
}

Future<void> main() async {
  // initialize...
  late CardotekaImpl cardoteka;
  late Card<int> counterCard;

  final cubit = CubitImpl();
  cardoteka.attach(
    counterCard,
    cubit.setValue,
    detacher: cubit.onDetach,
  );
}

We can't seem to do anything technically with those "parent" classes that we need to extends from. This also applies to Cubit and ValueNotifier and the like. And I don't like the fact that it's very easy to forget to override the close method when adding the “Detachability” mixin.

@PackRuble
Copy link
Owner Author

Yes, the important thing here is that I wouldn't want to force users to use a conditional CubitDetachability that is a standalone implementation of abstract MyCubit extends Cubit with Detachability because that's too tight an integration with other libraries, similar to how riverpod forces the use of ConsumerWidget instead of StatelessWidget (even though there is a Consumer in the package).

@PackRuble PackRuble added enhancement New feature or request discussion There's a lot to discuss labels Dec 18, 2023
@PackRuble PackRuble added this to the v2.0.0 milestone Dec 21, 2024
@PackRuble
Copy link
Owner Author

PackRuble commented Jan 17, 2025

There are a couple more options that I came up with. The most important thing in them is to simplify the constant use of Detachability for Cubit.

Input data:

final class AppCardoteka = Cardoteka with WatcherImpl;
final cardoteka = AppCardoteka(
  config: const CardotekaConfig(
    name: 'app_settings',
    cards: AppSettings.values,
  ),
);

enum AppSettings<T> implements Card<T> {
  themeMode(DataType.string, ThemeMode.system),
  ;

  const AppSettings(this.type, this.defaultValue);

  @override
  final DataType type;

  @override
  final T defaultValue;

  @override
  String get key => name;
}

1. mixin DetacherCubitV1<T> on Cubit<T> implements Detachability and ... Cubit<T> with DetacherCubitV1, Detachability

mixin DetacherCubitV1<T> on Cubit<T> implements Detachability {
  @override
  @mustCallSuper
  Future<void> close() async {
    detach();

    return super.close();
  }
}

class CubitThemeModeV1 extends Cubit<ThemeMode>
    with DetacherCubitV1, Detachability {
  CubitThemeModeV1() : super(AppSettings.themeMode.defaultValue) {
    cardoteka.attach(
      AppSettings.themeMode,
      (ThemeMode value) => emit(value),
      fireImmediately: true,
      detacher: onDetach,
    );
  }

  void setThemeMode(ThemeMode value) =>
      cardoteka.set(AppSettings.themeMode, value);
}

The user writes the CubitDetacher definition once in his code in a shared folder, and then uses it for any cubits with with CubitDetacher, Detachability. Detachability is a general class of cardoteka package.

I like this method because:

  • clean syntax
  • minimum amount of code to be implemented by the user
  • minimal code duplication on both sides
  • The cardoteka package has no third-party dependencies on bloc.
  • in the long run, it's easier to migrate somewhere else with this method.

There is also a short version for this method, which unfortunately is not available yet:

mixin DetacherCubit<T> on Cubit<T> extends Detachability {}
// and then...
class MyCubit extends Cubit<ThemeMode> with DetacherCubit {}

Related to:

2. mixin DetacherCubitV2<T> on Cubit<T> implements Detachability and ... Cubit<ThemeMode> with DetacherCubitV2<ThemeMode>

mixin DetacherCubitV2<T> on Cubit<T> implements Detachability {
  final _detachability = Detachability();

  @override
  void onDetach(void Function() cb) => _detachability.onDetach(cb);

  @override
  void detach() => _detachability.detach();

  @override
  @mustCallSuper
  Future<void> close() async {
    detach();
    return super.close();
  }
}

class CubitThemeMode2 extends Cubit<ThemeMode> with DetacherCubitV2 {
  // ... exactly like in the previous example
}

Cons:

  • duplication and redirection of logic in DetacherCubitV2

Props:

  • as for the first case
  • just one with DetacherCubitV2 for each cubit

For now, I would prefer to implement the first option, as the most optimal. On the other hand, the user will have to copy the code anyway, so why not copy DetacherCubitV2 to write less in everyday code. Probably I choose option 2 as the primary option for documentation

PackRuble added a commit that referenced this issue Jan 17, 2025
…his solves problems:

- Way to remove a callback using `Watcher.attach` + `Cubit` #10
- Way to remove a callback using `Watcher.attach` + `ChangeNotifier`, close #9
@PackRuble PackRuble self-assigned this Jan 17, 2025
PackRuble added a commit that referenced this issue Feb 10, 2025
- upd: minimum supported SDK version to Flutter 3.24.0/Dart 3.5.0
- 🛡️fix: [Implement security advisories CWE-502](#29)
- new: now you can directly create an instance of the `Cardoteka` ([#15](#15))
- new: `CardotekaAsync` for asynchronous data retrieval (works without cache) ([#24](#24))
- 🧨upd: all declarations of own classes from `Cardoteka` and `CardotekaAsync` must now necessarily be declared as `final` or `base` or `sealed`
- 🧨upd: `AccessToSP` has been deleted. Use `import package:cardoteka/access_to_sp.dart`.
- 🧨upd: changes in `Watcher.attach`: `onRemove` parameter is now required and callback is now a named `onChange` parameter ([#14](#14) и [#37](#37))
- add: `Detachability` and `DetacherChangeNotifier`for easy dispose of linked resources in classes with business logic ([#10](#10) и [#9](#9))
- add: `CRUD.readAsync` method for use with `CardotekaAsync`
- add: `notifyAll` method for `Watcher` [17](#17)
- upd: `Converters.colorAsInt` is temporarily deprecated. See more details in [#31](#31)
- add: `CardotekaCore.migrate` method for data migration ([#33](#33))
- upd: all examples in `example` folder have been updated
- upd: some internal methods have been hidden from the IDE prompts to make package easier to use
- doc: "Notifier (riverpod)", "Analogy in `SharedPreferencesWithCache` and `SharedPreferencesAsync`", "Migration", "Sync or Async storage", "Detachability" sections were added to readme

You can see all closed issues in [Milestone v2.0.0](https://github.com/PackRuble/cardoteka/milestone/2?closed=1).

Also, read `readme.md` section on data migration [Cardoteka from v1 to v2](https://github.com/PackRuble/cardoteka?tab=readme-ov-file#cardoteka-from-v1-to-v2).
PackRuble added a commit that referenced this issue Feb 10, 2025
- upd: minimum supported SDK version to Flutter 3.24.0/Dart 3.5.0
- 🛡️fix: [Implement security advisories CWE-502](#29)
- new: now you can directly create an instance of the `Cardoteka` ([#15](#15))
- new: `CardotekaAsync` for asynchronous data retrieval (works without cache) ([#24](#24))
- 🧨upd: all declarations of own classes from `Cardoteka` and `CardotekaAsync` must now necessarily be declared as `final` or `base` or `sealed`
- 🧨upd: `AccessToSP` has been deleted. Use `import package:cardoteka/access_to_sp.dart`.
- 🧨upd: changes in `Watcher.attach`: `onRemove` parameter is now required and callback is now a named `onChange` parameter ([#14](#14) и [#37](#37))
- add: `Detachability` and `DetacherChangeNotifier`for easy dispose of linked resources in classes with business logic ([#10](#10) и [#9](#9))
- add: `CRUD.readAsync` method for use with `CardotekaAsync`
- add: `notifyAll` method for `Watcher` [#17](#17)
- upd: `Converters.colorAsInt` is temporarily deprecated. See more details in [#31](#31)
- add: `CardotekaCore.migrate` method for data migration ([#33](#33))
- upd: all examples in `example` folder have been updated
- upd: some internal methods have been hidden from the IDE prompts to make package easier to use
- doc: "Notifier (riverpod)", "Analogy in `SharedPreferencesWithCache` and `SharedPreferencesAsync`", "Migration", "Sync or Async storage", "Detachability" sections were added to readme

You can see all closed issues in [Milestone v2.0.0](https://github.com/PackRuble/cardoteka/milestone/2?closed=1).

Also, read `readme.md` section on data migration [Cardoteka from v1 to v2](https://github.com/PackRuble/cardoteka?tab=readme-ov-file#cardoteka-from-v1-to-v2).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion There's a lot to discuss enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant