Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
Link extension
- beginners
InheritedWidget
You can read this article firstFlutter using InheritedWidget- Advanced,
InheritedWidget
+Notifier
Implement state management modeCustom InheritedProvider- Advanced, state management refresh ideas for RxBinder
Based on thesetState
Refresh 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 use
StatelessWidget
Is not usedStatefulWidget
flutter
The 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
_InheritedNotifierElement
What did you do?
_InheritedNotifierElement
The data sourceNotifier
A listener is called when a change is triggeredmarkNeedsBuild
retracebuild
methods- We know that
InheritedElement
thebuild
The method does not refresh itself or its children._InheritedNotifierElement
Rewrite thebuild
Method, which makes a conditional judgment in a method, is callednotifyClients
Refresh 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 withprovider
It 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.