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

(Keep Alive / Autodispose) Provider logic #3129

Closed
busslina opened this issue Nov 17, 2023 · 8 comments
Closed

(Keep Alive / Autodispose) Provider logic #3129

busslina opened this issue Nov 17, 2023 · 8 comments
Assignees
Labels
enhancement New feature or request needs triage

Comments

@busslina
Copy link

busslina commented Nov 17, 2023

I created a (Keep Alive / Auto Dispose) Provider logic.

Semantics:

KA provider: Provides KA status to multiple KA consumers. (It is a non-disposable provider)
KA consumer: Consumes KA status from one KA provider. (it is a disposable consumer)

Why:

It is a repeated scenario to have providers which have to change its Keep Alive/Auto Dispose status.
Example: My App manages multiple servers. I want some providers to be alive while its server connection is alive.
I can have multiple server connections alive.

Code:

. KeepAliveProviderMixin
. KeepAliveConsumerMixin

How it works?

A KA provider must call init() on the build() method.
A KA consumer must call init() on the build() method.
When a server get dis/connected its KA provider status is updated (via Side Effect).
When a KA consumer knows its KA provider, it must supply it (ProviderListenable<bool>) in init().
When a KA consumer doesn't know its KA provider, it must call updateProvider() on any widget update.

Complexity:

The easy scenario is when the KA provider is known by the KA consumers (the last are in the same or lower level in the project library chain).

A little bit more complex scenario is when the KA provider isn't known by KA consumers (E.G.. KA consumer is on a package library and KA provider is at project level).
This is resolved by passing the KA provider via Widget property. (**)

Questions:

(**) It works, but it is acceptable by the Riverpod "guidelines"?

Any help/comment/suggestion is appreciated.

keep_alive_provider.mixin.dart:

import 'package:busslina_dart_lightweight_lib/busslina_dart_lightweight_lib.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';

mixin KeepAliveProviderMixin<T> {
  // Data

  //#region
  final bool _debugging = true;
  //#endregion

  // Abstract setter functions
  //
  // - 01 - State

  //#region
  /// 01 - State.
  set state(bool value);
  //#endregion

  // Functions
  //
  // - 01 - Init
  // - 02 - Set

  //#region
  /// 01 - Init.
  void init() {
    _debug('init()');

    // [01]: Starting debug
    _startDebug();
  }

  /// 02 - Set.
  void set(bool value) {
    _debug('set -- $value()');

    state = value;
  }
  //#endregion

  // Private functions
  //
  // - 01 - Start debug
  // - 02 - Debug

  //#region
  /// 01 - Start debug.
  void _startDebug() {
    // [01]: Not active
    if (!_debugging) {
      return;
    }

    _debug('_startDebug()');

    // [02]: On resume
    getRef().onResume(() {
      _debug('onResume');
    });

    // [03]: On cancel
    getRef().onCancel(() {
      _debug('onCancel');
    });

    // [04]: On dispose
    getRef().onDispose(() {
      _debug('onDispose');
    });
  }

  /// 02 - Debug.
  void _debug(String msg) =>
      debug('KeepAliveProviderMixin -- ${getDebugName()} -- $msg',
          active: _debugging);
  //#endregion

  // Abstract functions
  //
  // - 01 - Get ref
  // - 02 - Get debug name

  //#region
  /// 01 - Get ref.
  NotifierProviderRef<bool> getRef();

  /// 02 - Get debug name.
  String getDebugName();
  //#endregion
}

keep_alive_consumer.mixin.dart:

import 'package:busslina_dart_lightweight_lib/busslina_dart_lightweight_lib.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';

mixin KeepAliveConsumerMixin<T> {
  // Data

  //#region
  final bool _debugging = true;
  KeepAliveLink? _keepAliveLink;
  ProviderSubscription<bool>? _listenSubscription;
  ProviderListenable<bool>? _provider;
  //#endregion

  // Functions
  //
  // - 01 - Init
  // - 02 - Set keep alive
  // - 03 - Set auto dispose
  // - 04 - Update provider

  //#region
  /// 01 - Init.
  void init([ProviderListenable<bool>? provider]) {
    _debug('init()');

    // [01]: Starting debug
    _startDebug();

    // [02]: (On Dispose)
    getRef().onDispose(_clear);

    // [03]: Updating provider
    if (provider != null) {
      updateProvider(provider);
    }
  }

  /// 02 - Set keep alive.
  void setKeepAlive({bool active = true}) {
    // [01]: Not active
    if (!active) {
      return;
    }

    _debug('setKeepAlive()');

    // [02]: Closing previous keep alive link
    _keepAliveLink?.close();

    // [03]: Keep alive
    _keepAliveLink = getRef().keepAlive();
  }

  /// 03 - Set auto dispose.
  void setAutoDispose({bool active = true}) {
    // [01]: Not active
    if (!active) {
      return;
    }

    _debug('setAutoDispose()');

    // [02]: Closing keep alive link
    _keepAliveLink?.close();
  }

  /// 04 - Update provider.
  void updateProvider(ProviderListenable<bool> provider) {
    // [01]: No change
    if (_provider == provider) {
      return;
    }

    // [02]: Updating
    _provider = provider;

    // [03]: Closing previous subscription
    _listenSubscription?.close();

    // [04]: [Initial Keep Alive]
    setKeepAlive(active: getRef().read(provider));

    // [05]: [Listen Keep Alive provider]
    _listenSubscription =
        getRef().listen(provider, _keepAliveProviderStateListener);
  }
  //#endregion

  // Private functions
  //
  // - 01 - Start debug
  // - 02 - Keep alive provider state listener
  // - 03 - Debug
  // - 04 - Clear

  //#region
  /// 01 - Start debug.
  void _startDebug() {
    // [01]: Not active
    if (!_debugging) {
      return;
    }

    _debug('_startDebug()');

    // [02]: On resume
    getRef().onResume(() {
      _debug('onResume');
    });

    // [03]: On cancel
    getRef().onCancel(() {
      _debug('onCancel');
    });

    // [04]: On dispose
    getRef().onDispose(() {
      _debug('onDispose');
    });
  }

  /// 02 - Keep alive provider state listener.
  void _keepAliveProviderStateListener(bool? previous, bool next) {
    _debug('_keepAliveProviderStateListener() -- $next');

    // [A]: Keep alive
    if (next) {
      setKeepAlive();
    }

    // [B]: Auto dispose
    else {
      setAutoDispose();
    }
  }

  /// 03 - Debug.
  void _debug(String msg) =>
      debug('KeepAliveConsumerMixin<$T> -- ${getDebugName()} -- $msg',
          active: _debugging);

  /// 04 - Clear.
  void _clear() {
    _debug('_clear()');

    _listenSubscription?.close();
    _keepAliveLink?.close();
    _provider = null;
  }
  //#endregion

  // Abstract functions
  //
  // - 01 - Get ref
  // - 02 - Get debug name

  //#region
  /// 01 - Get ref.
  AutoDisposeRef<T> getRef();

  /// 02 - Get debug name.
  String getDebugName();
  //#endregion
}
@TekExplorer
Copy link

TekExplorer commented Nov 17, 2023

Consider instead creating an extension method on AutoDisposeRef

extension AliveWhenServer<>T on AutoDisposeRef<T> {

  void keepAliveForServer() {
    KeepAliveLink? link;

    listen(serverProvider, (_, next) {
       if (next.isConnected) { // check that link isn't active?
          link = keepAlive();
       }

       if (!next.isConnected) link?.close();
    });
  }

}

@busslina
Copy link
Author

@TekExplorer but that doesn't work for the case that KA consumer doesn't know his KA provider !? !?

@TekExplorer
Copy link

@TekExplorer but that doesn't work for the case that KA consumer doesn't know his KA provider !? !?

What does that even mean? If you need to pass family parameters (for example) pass them in as parameters.

@busslina
Copy link
Author

busslina commented Nov 17, 2023

@TekExplorer but that doesn't work for the case that KA consumer doesn't know his KA provider !? !?

What does that even mean? If you need to pass family parameters (for example) pass them in as parameters.

It is the case when the Consumer is in a package and the Provider is in the project.
The only way seems to pass the Provider via Widget property

imagen

@TekExplorer
Copy link

@TekExplorer but that doesn't work for the case that KA consumer doesn't know his KA provider !? !?

What does that even mean? If you need to pass family parameters (for example) pass them in as parameters.

It is the case when the Consumer is in a package and the Provider is in the project.
The only way seems to pass the Provider via Widget property

imagen

That is bad. Don't do that. Packages should not ever ask for a raw provider for any reason. That's what callbacks and normal values are for.

@TekExplorer
Copy link

Also, you're passing a ref into something. Don't do that.

@busslina
Copy link
Author

Also, you're passing a ref into something. Don't do that.

I'm not passing a ref, I use it inside a mixin (via an abstract method)

@busslina
Copy link
Author

Closing due @TekExplorer teach me about a solution with https://api.flutter.dev/flutter/widgets/IndexedStack-class.html.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request needs triage
Projects
None yet
Development

No branches or pull requests

3 participants