Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Link extension

  1. beginnersInheritedWidgetYou can read this article firstFlutter using InheritedWidget
  2. Advanced,InheritedWidget+NotifierImplement state management modeCustom InheritedProvider
  3. Advanced, state management refresh ideas for RxBinder

Based on thesetStateRefresh status management

I have written two previous articles on state management (link extension 2,3 above). The general idea: Use the StatefulWidget as the parent node, listen for Notifier data source changes, and use setState() to restart builds after triggering updates. The intermediate component uses Inherited to implement data sharing and partial refresh.

bypassStateful, refresh directly through Element

Before we get down to business, let’s first identify two optimization points for improving page performance:

  • Be able to useStatelessWidgetIs not usedStatefulWidget
  • flutterThe whole is a tree structure for drawing, where local refresh can be used, not full refresh

Without further ado, let’s get straight to the code:

///Custom status management tool
class RxInheritedProvider<T extends ChangeNotifier> extends StatelessWidget {
  final T create;
  final Widget Function(BuildContext context) builder;

  const RxInheritedProvider({
    Key? key,
    required this.create,
    required this.builder,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    returnRxInheritedWidget( child: Builder(builder: (context) => builder(context)), value: create, ); }}class RxInheritedWidget<T extends ChangeNotifier> extends InheritedNotifier<T> {
  RxInheritedWidget({
    required T value,
    required Widget child,
  }) : super(notifier: value, child: child);

  get value => this.notifier;
}
Copy the code

Core: Uses the encapsulated class InheritedNotifier

provided by the official SDK. The following uses some InheritedNotifier key code to discuss ideas

///The code comes from the SDK
abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget {
  
  const InheritedNotifier({
    Key key,
    this.notifier,
    @required Widget child,
  }) : assert(child ! =null),
       super(key: key, child: child);

 
  final T notifier;

  @override
  bool updateShouldNotify(InheritedNotifier<T> oldWidget) {
    returnoldWidget.notifier ! = notifier; }@override
  _InheritedNotifierElement<T> createElement() => _InheritedNotifierElement<T>(this);
}
Copy the code

The Widget layer is plain and simple, so we’re going to use _InheritedNotifierElement

// Code comes from the SDK
class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
  _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) { widget.notifier? .addListener(_handleUpdate); }@override
  InheritedNotifier<T> get widget => super.widget as InheritedNotifier<T>;

  bool _dirty = false;

  @override
  void update(InheritedNotifier<T> newWidget) {
    final T oldNotifier = widget.notifier;
    final T newNotifier = newWidget.notifier;
    if(oldNotifier ! = newNotifier) { oldNotifier? .removeListener(_handleUpdate); newNotifier? .addListener(_handleUpdate); }super.update(newWidget);
  }

  @override
  Widget build() {
    if (_dirty)
      notifyClients(widget);
    return super.build();
  }

  void _handleUpdate() {
    _dirty = true;
    markNeedsBuild();
  }

  @override
  void notifyClients(InheritedNotifier<T> oldWidget) {
    super.notifyClients(oldWidget);
    _dirty = false;
  }

  @override
  voidunmount() { widget.notifier? .removeListener(_handleUpdate);super.unmount(); }}Copy the code

_InheritedNotifierElementWhat did you do?

  • _InheritedNotifierElementThe data sourceNotifierA listener is called when a change is triggeredmarkNeedsBuildretracebuildmethods
  • We know thatInheritedElementthebuildThe method does not refresh itself or its children._InheritedNotifierElementRewrite thebuildMethod, which makes a conditional judgment in a method, is callednotifyClientsRefresh the dependency

Thus, the InheritedNotifier

provided by the white whoresystem is implemented, and the ability to listen to the data source for local refreshes is implemented. We also need a utility class to register the bindings.

///Provide registration dependency methods
abstract class RxTool {
  static T of<T extends ChangeNotifier>(BuildContext context) {
    return (_getInheritedElement<T>(context).widget as RxInheritedWidget<T>).value;
  }

  static void register<T extends ChangeNotifier>(BuildContext context) {
    var element = _getInheritedElement<T>(context);
    // context.dependOnInheritedElement(element);

    context.dependOnInheritedWidgetOfExactType<RxInheritedWidget<T>>(aspect: element.widget);

    // There is no correlation in this way
    // context.getElementForInheritedWidgetOfExactType<RxInheritedWidget<T>>();
  }

  static InheritedElement _getInheritedElement<T extends ChangeNotifier>(
      BuildContext context) {
    var element = context.getElementForInheritedWidgetOfExactType<RxInheritedWidget<T>>();
    if (element == null) {
      throw (Exception("RxInheritedWidget<${T.runtimeType}> is find null"));
    }
    returnelement; }}Copy the code

Provide a register method that binds the Context to an InheritedElement. A dependency flush occurs when the notifyClient() method is executed. There are two ways to bind dependencies, one of which is required:

  • context.dependOnInheritedElement(element)
  • context.dependOnInheritedWidgetOfExactType<RxInheritedWidget<T>>(aspect: element.widget), note that generics must be associated withproviderIt is a perfect match, otherwise the association cannot succeed

Create a new ConsumerBuilder to encapsulate registered dependencies and retrieve shared data

class ConsumerBuilder<T extends ChangeNotifier> extends StatelessWidget {
  final Widget Function(BuildContext context, T value) builder;

  const ConsumerBuilder({Key? key, required this.builder}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    RxTool.register<T>(context);
    returnbuilder( context, RxTool.of<T>(context), ); }}Copy the code

Run the Demo

/// The demo sample
class Counter extends ChangeNotifier {
  int count = 0;
  voidincrease() { ++count; notifyListeners(); }}class TestWidget extends StatelessWidget {
  TestWidget({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RxInheritedProvider(
          create: Counter(),
          builder: (context) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _child(),
                  Builder(builder: (context) {
                    return TextButton(
                        child: Text("On the"), onPressed: () { RxTool.of<Counter>(context).increase(); }); })],),); })); } Widget _child() {return ConsumerBuilder<Counter>(builder: (context, counter) {
      return Text(
        'click on the${counter.count}Time ',
        style: TextStyle(fontSize: 30.0)); }); }}Copy the code

Pro test effective, the running interface is not screenshots, welcome to the comment area exchange and discussion.