get_state.dart 8.09 KB
import 'dart:collection';

import 'package:flutter/widgets.dart';
import 'package:get/src/instance/get_instance.dart';
import 'package:get/src/navigation/root/smart_management.dart';
import 'package:get/src/state_manager/rx/rx_core/rx_interface.dart';
import 'package:get/state_manager.dart';

import 'simple_builder.dart';

// Changed to VoidCallback.
//typedef Disposer = void Function();

// replacing StateSetter, return if the Widget is mounted for extra validation.
typedef GetStateUpdate = bool Function();

/// Complies with [GetStateUpdater]
///
/// This mixin's function represents a [GetStateUpdater], and might be used
/// by [GetBuilder()], [SimpleBuilder()] (or similar) to comply
/// with [GetStateUpdate] signature. REPLACING the [StateSetter].
/// Avoids the potential (but extremely unlikely) issue of having
/// the Widget in a dispose() state, and abstracts the API from the ugly fn((){}).
/// TODO: check performance HIT for the extra method call.
///
mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
  bool getUpdate() {
    final _mounted = mounted;
    if (_mounted) setState(() {});
    return _mounted;
  }
}

class GetxController extends DisposableInterface {
  final _updaters = HashSet<GetStateUpdate>();

//  final _updatersIds = HashMap<String, StateSetter>(); //<old>
  final _updatersIds = HashMap<String, GetStateUpdate>();

//  final _updatersGroupIds = HashMap<String, List>(); //<old>
  final _updatersGroupIds = HashMap<String, HashMap<int, GetStateUpdate>>();

  static int _groupIdCount = 0;

  /// Rebuilds [GetBuilder] each time you call [update()];
  /// Can take a List of [ids], that will only update the matching
  /// `GetBuilder( id: )`,
  /// [ids] can be reused among `GetBuilders` like group tags.
  /// The update will only notify the Widgets, if [condition] is true.
  void update([List<String> ids, bool condition = true]) {
    if (!condition) return;
    if (ids == null) {
//      _updaters.forEach((rs) => rs(() {}));//<old>
      _updaters.forEach((rs) => rs());
    } else {
      ids.forEach((element) {
//        _updatersGroupIds[element]?.forEach((k, rs) => rs(() {}));//<old>
//        _updatersIds[element]?.call(() {});//<old>
        _updatersIds[element]?.call();
        _updatersGroupIds[element]?.forEach((k, rs) => rs());
      });
    }
  }

//  VoidCallback addListener(StateSetter listener) {//<old>
  VoidCallback addListener(GetStateUpdate listener) {
    _updaters.add(listener);
    return () => _updaters.remove(listener);
  }

//  VoidCallback addListenerId(String key, StateSetter listener) {//<old>
  VoidCallback addListenerId(String key, GetStateUpdate listener) {
//    _printCurrentIds();
    if (_updatersIds.containsKey(key)) {
      final _innerKey = _groupIdCount++;
//      final _ref = _updatersGroupIds[key] ??= HashMap<int, StateSetter>();//<old>
      final _ref = _updatersGroupIds[key] ??= HashMap<int, GetStateUpdate>();
      _ref[_innerKey] = listener;
      return () {
        _ref?.remove(_innerKey);
//        _printCurrentIds();
      };
    } else {
      _updatersIds[key] = listener;
      return () => _updatersIds.remove(key);
    }
  }

  /// To dispose an [id] from future updates(), this ids are registered
  /// by [GetBuilder()] or similar, so is a way to unlink the state change with
  /// the Widget from the Controller.
  void disposeId(String id) {
    _updatersIds.remove(id);
    _updatersGroupIds.remove(id);
  }

  /// Remove this after checking the new implementation makes sense.
  /// Uncomment this if you wanna control the removal of ids..
//  bool _debugging = false;
//  Future<void> _printCurrentIds() async {
//    if (_debugging) return;
//    _debugging = true;
//    print('about to debug...');
//    await Future.delayed(Duration(milliseconds: 10));
//    int totalGroups = 0;
//    _updatersGroupIds.forEach((key, value) {
//      totalGroups += value.length;
//    });
//    int totalIds = _updatersIds.length;
//    print(
//        'Total: ${totalIds + totalGroups}, in groups:$totalGroups, solo ids:$totalIds');
//    _debugging = false;
//  }
}

class GetBuilder<T extends GetxController> extends StatefulWidget {
  final Widget Function(T) builder;
  final bool global;
  final String id;
  final String tag;
  final bool autoRemove;
  final bool assignId;
  final void Function(State state) initState, dispose, didChangeDependencies;
  final void Function(GetBuilder oldWidget, State state) didUpdateWidget;
  final T init;

  const GetBuilder({
    Key key,
    this.init,
    this.global = true,
    @required this.builder,
    this.autoRemove = true,
    this.assignId = false,
    this.initState,
    this.tag,
    this.dispose,
    this.id,
    this.didChangeDependencies,
    this.didUpdateWidget,
  })  : assert(builder != null),
        super(key: key);

  @override
  _GetBuilderState<T> createState() => _GetBuilderState<T>();
}

class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  GetxController controller;
  bool isCreator = false;

  /// TODO: @jonny, you intend to use disposers?
//  final HashSet<Disposer> disposers = HashSet<Disposer>();

  VoidCallback remove;

  @override
  void initState() {
    super.initState();

    if (widget.initState != null) widget.initState(this);
    if (widget.global) {
      final isPrepared = GetInstance().isPrepared<T>(tag: widget.tag);
      final isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);

      if (isPrepared) {
        if (GetConfig.smartManagement != SmartManagement.keepFactory) {
          isCreator = true;
        }
        controller = GetInstance().find<T>(tag: widget.tag);
      } else if (isRegistered) {
        controller = GetInstance().find<T>(tag: widget.tag);
        isCreator = false;
      } else {
        controller = widget.init;
        isCreator = true;
        GetInstance().put<T>(controller, tag: widget.tag);
      }
    } else {
      controller = widget.init;
      isCreator = true;
      controller?.onStart();
    }

    if (widget.global &&
        GetConfig.smartManagement == SmartManagement.onlyBuilder) {
      controller?.onStart();
    }
    _subscribeToController();
  }

  /// Register to listen Controller's events.
  /// It gets a reference to the remove() callback, to delete the
  /// setState "link" from the Controller.
  void _subscribeToController() {
    remove?.call();
    remove = (widget.id == null)
//        ? controller?.addListener(setState)//<old>
//        : controller?.addListenerId(widget.id, setState);//<old>
        ? controller?.addListener(getUpdate)
        : controller?.addListenerId(widget.id, getUpdate);
  }

  /// Sample for [GetStateUpdate] when you don't wanna use [GetStateHelper mixin].
//  bool _getUpdater() {
//    final _mounted = mounted;
//    if (_mounted) setState(() {});
//    return _mounted;
//  }

  @override
  void dispose() {
    super.dispose();
    if (widget.dispose != null) widget.dispose(this);
    if (isCreator || widget.assignId) {
      if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
        GetInstance().delete<T>(tag: widget.tag);
      }
    }
    remove?.call();

//    disposers.forEach((element) {
//      element();
//    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (widget.didChangeDependencies != null) {
      widget.didChangeDependencies(this);
    }
  }

  @override
  void didUpdateWidget(GetBuilder oldWidget) {
    super.didUpdateWidget(oldWidget as GetBuilder<T>);
    // to avoid conflicts when modifying a "grouped" id list.
    if (oldWidget.id != widget.id) {
      _subscribeToController();
    }
    if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this);
  }

  @override
  Widget build(BuildContext context) => widget.builder(controller);
}

/// This is a experimental feature.
/// Meant to be used with SimpleBuilder, it auto-registers the variable
/// like Rx() does with Obx().
class Value<T> extends GetxController {
  Value([this._value]);
  T _value;

  T get value {
    TaskManager.instance.notify(_updaters);
    return _value;
  }

  set value(T newValue) {
    if (_value == newValue) return;
    _value = newValue;
    update();
  }
}