Create builder with pre-built subtree in Flutter - flutter

In Flutter doc https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html, under Performance Optimization it states:
If your builder function contains a subtree that does not depend on the value of the ValueListenable, it's more efficient to build that subtree once instead of rebuilding it on every animation tick.
If you pass the pre-built subtree as the child parameter, the ValueListenableBuilder will pass it back to your builder function so that you can incorporate it into your build.
Using this pre-built child is entirely optional, but can improve performance significantly in some cases and is therefore a good practice.
Is there a more "general" builder widget that accepts pre-built subtree (similar to the mentioned ValueListenableBuilder) that is available in Flutter Widgets Catalog? If not, how does something like this work so I can create my own?
I looked at the source code but I don't understand.

I ended up creating my own.
class SubtreeBuilder extends StatelessWidget {
const SubtreeBuilder({
Key key,
#required this.builder,
this.child,
}) : super(key: key);
final Widget Function(BuildContext context, Widget child) builder;
final Widget child;
#override
Widget build(BuildContext context) {
return builder(context, child);
}
}

Related

StatelessWidget gets rebuilt even though parameters stay the same; notifyListeners() with ChangeNotifier

I've got two StatelessWidgets, one is a child of another. There is also a progress update function which updates the state in the external TransferState.
Once updateProgress function is being called the TransferIndicator widget gets rebuilt immediately. On the other hand, its parent (TransfersListTile) build method isn't called.
It works as expected, however I can't really work out what's the mechanism that's being used here. How Flutter decides to rebuild the: _TransferIndicator given that the parameter is a string hash that's not being changed, but only used as a lookup ID to reach the map in TransferState and load the status and progress.
Documentation: https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html says:
"The build method of a stateless widget is typically only called in
three situations: the first time the widget is inserted in the tree,
when the widget's parent changes its configuration, and when an
InheritedWidget it depends on changes."
If: notifyListeners(); function is removed, the widget doesn't get rebuilt.
It seem to be closely related to: ChangeNotifier, but couldn't find the exact info how it works.
In doc here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifier there is an example involving ChangeNotifier, however doesn't the receiving widget need to be wrapped around: Consumer (https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer)?
In my case there is no Consumer wrapping.
class TransfersListTile extends StatelessWidget {
TransfersListTile(this.transfer, {Key? key}) : super(key: key);
final Transfer transfer;
#override
Widget build(BuildContext context) {
return ListTile(
leading: _TransferIndicator(transfer.hash),
title: Text(transfer.name!),
);
}
}
class _TransferIndicator extends StatelessWidget {
const _TransferIndicator(this.hash, {Key? key}) : super(key: key);
final String? hash;
#override
Widget build(BuildContext context) {
final status = context.select((TransferState s) => s.map[hash]?.status) ?? TransferStatus.pending;
final progress = context.select((TransferState s) => s.map[hash].progress.percentage);
return CircularProgressIndicator(
value: status == TransferStatus.completed ? 100 : (progress / 100),
);
}
}
function:
class TransferState with ChangeNotifier {
updateProgress(String hash, TransferProgress progress) {
map[hash]?.progress = progress;
notifyListeners();
}
}
and provider part:
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TransferState(),
],
child: MyApp(),
)
);
More info about the select method and other convenience methods can be found on the provider (https://pub.dev/packages/provider) package site.
Excerpt:
The easiest way to read a value is by using the extension methods on
[BuildContext]:
context.watch(), which makes the widget listen to changes on T
context.read(), which returns T without listening to it
context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.

Flutter Riverpod - using read() inside build method

Suppose I want to initialize a text field by using the initialValue: property on a TextFormField, and I need my initial value to come from a provider. I read on the docs that calling read() from inside the build method is considered bad practice, but calling from handlers is fine (like onPressed). So I'm wondering if its fine to call read from the initialValue property like shown below?
No, you should use useProvider if you are using hooks, or a ConsumerWidget / Consumer if you are not.
The difference being, the initialValue field is a part of the build method, and like you said, onPressed is a handler, outside of the build method.
A core aspect of providers is optimizing rebuilds as provided values change. Using context.read in the build method is nullifying this benefit as you aren't listening to the provided value.
Using context.read is highly recommended in anonymous functions (onChanged, onPressed, onTap, etc.) because those functions are retrieving the provided value at the time the function is executed. This means the function will always execute with the current value of that provider, without having to listen to the provider. The other methods for reading providers use a listener which is more expensive and unnecessary in the case of anonymous functions.
In your example, you wanted to set initialValue of a TextFormField. The following is how you could use hooks_riverpod and flutter_hooks to accomplish that.
class HooksExample extends HookWidget {
const HooksExample({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
initialValue: useProvider(loginStateProv).email,
);
}
}
And for readers who prefer to not use hooks:
class ConsumerWidgetExample extends ConsumerWidget {
const ConsumerWidgetExample({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return TextFormField(
initialValue: watch(loginStateProv).email,
);
}
}
Or:
class ConsumerExample extends StatelessWidget {
const ConsumerExample({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, watch, child) {
return TextFormField(
initialValue: watch(loginStateProv).email,
);
},
);
}
}
The primary difference being that Consumer will only rebuild its children because only they are relying on provided data.

Does whole subtree rebuilds on setState in flutter

I am new to flutter and really wondering if all the subtree of widgets gets rebuild when we call setState.
Subtree here means all the widget tree below that widget (including that widget as root node).
When we call setState function, the build method is called on the root node of the subtree, which triggers the build methods on its child. Say a branch (here MyWidget1) of a subtree (a child of that widget) is independent of the state variables. I noticed that even independent branches are rebuilt on setState called in the parent node.
class _MyAppState extends State<MyApp> {
int count=0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[ MyWidget1(),MyWidget2(count),],),
floatingActionButton: FloatingActionButton(onPressed: ()=>setState((){count++;}),),
);
}
}
class MyWidget1 extends StatelessWidget {
#override
Widget build(BuildContext context) { print("widget builds 1");
return Container(height: 100, color: Colors.orange,);
}
}
class MyWidget2 extends StatelessWidget {
final int count;
MyWidget2(this.count);
#override
Widget build(BuildContext context) { print("widget builds 2");
return Text(count.toString());
}
}
Here we can see that MyWidget1 is independent of the state variable (here count), so generally, setState should have no impact on it.
I was wondering if there should be any optimization to avoid that useless build of MyWidget1 on the call of setState function. As the tree below MyWidget1 can be too big, that too will be rebuild again.
My Questions:
Is it Ok for this Independent Widget (here MyWidget1) to build again on setState?
Is there a better way to deal with this situation to avoid its rebuild.
Note: I have read this question
In this question, there is a way to avoid useless build by creating an instance of the independent branch outside the build method,
My doubt is :
Is this the WAY to deal with this situation or some other better way or this situation isn't that big at all as tree builds in O(n) time (which I think shouldn't be the answer because building tree might be O(n) operation but it may include many time-consuming operations which may not be optimization friendly to call again and again uselessly).
Yes, MyWidget1 is rebuilt upon that setState. Just trust the code. After you call setState, build is called, which calls the constructor of MyWidget1. After each setState, the entire subtree is rebuilt. Old widgets are thrown away. States are not thrown away, though. State instances live on, they are not recreated (see didUpdateWidget).
So, yes. After each setState, the entire subtree is rebuilt.
This is OK, don't worry.
The widget classes here are very lightweight classes. Dart's garbage collector is optimized to instantiate many such objects and throw them away together.
This tree that you get to recreate again and again is just a facade. There are two more parallel trees that are not lightweight and are not recreated. Your widget trees are diff'ed together to find how the actual ui elements should be modified by the system.
Why all this trouble, you may ask. Because creating trees is easy and maintaining them is difficult. This reactive declarative framework lets us get away with only creating the tree and not maintaining it.
There are some resources about Flutter internals that you can read more about this. One such resource is this video: https://www.youtube.com/watch?v=996ZgFRENMs
class _MyAppState extends State<MyApp> {
int count=0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[ const MyWidget1(),MyWidget2(count),],),
floatingActionButton: FloatingActionButton(onPressed: ()=>setState((){count++;}),),
);
}
}
class MyWidget1 extends StatelessWidget {
const MyWidget1();
#override
Widget build(BuildContext context) { print("widget builds 1");
return Container(height: 100, color: Colors.orange,);
}
}
class MyWidget2 extends StatelessWidget {
final int count;
MyWidget2(this.count);
#override
Widget build(BuildContext context) { print("widget builds 2");
return Text(count.toString());
}
}
when the constructor starts with a "const" keyword, which allows
you to cache and reuse the widget.
When calling the constructor to initiate the widget, use the "const" keyword. By calling with the "const" keyword, the widget does not rebuild when any parent widgets change
their state in the tree. If you omit the "const" keyword, the widget will be build every time the parent
widget redraws.

What is the difference between StatefullWidget and StatelessWidget regards to Performance?

Is there a Performance impact if we just use StatefullWidget over the StatelessWidget it vice-versa?
In my point of view, we just use StatefullWidget for things like update part of the UI using setState(), have a way to setup some code in the initState() and dispose things in the dispose() function. So when I don't need those things I go ahead and use StatelessWidget.
But what is the real performance impact between these two most used widgets?
Performance-wise, a StatelessWidget is almost identical to a StatefulWidget with an empty State.
Writing:
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
or:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
#override
Widget build(BuildContext context) {
return Container();
}
}
won't have any visible impact on the performance of your app.
There indeed is a small gain when using StatelessWidget here. But it's ridiculously small:
The difference between them can be summarized as calling an empty function vs not calling it. It's something, but absolutely doesn't matter.
The reason being, internally the implementation of StatefulWidget and StatelessWidget is almost the same.
StatelessWidget does have all the extra life-cycles that StatefulWidget possess.
It has an "initState"/"dispose". Even a setState!
They are just not part of the public API.
What is a StatelessWidget?
A widget that does not require mutable state.
Performance considerations
The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget's parent changes its configuration, and when an InheritedWidget it depends on changes.
Sample Code
class MyWidget extends StatelessWidget {
const MyWidget ({ Key key }) : super(key: key);
#override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFF00BD3A));
}
}
What is a StatefulWidget?
A widget that has mutable state.
Performance considerations
here are two primary categories of StatefulWidgets.
The first is one which allocates resources in State.initState and disposes of them in State.dispose, but which does not depend on InheritedWidgets or call State.setState. Such widgets are commonly used at the root of an application or page, and communicate with subwidgets via ChangeNotifiers, Streams, or other such objects. Stateful widgets following such a pattern are relatively cheap (in terms of CPU and GPU cycles), because they are built once then never update. They can, therefore, have somewhat complicated and deep build methods.
The second category is widgets that use State.setState or depend on InheritedWidgets. These will typically rebuild many times during the application's lifetime, and it is therefore important to minimize the impact of rebuilding such a widget. (They may also use State.initState or State.didChangeDependencies and allocate resources, but the important part is that they rebuild.)
Sample code
class YellowBird extends StatefulWidget {
const YellowBird({ Key key }) : super(key: key);
#override
_YellowBirdState createState() => new _YellowBirdState();
}
class _YellowBirdState extends State<YellowBird> {
#override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFFFFE306));
}
}
Full Explanaton is given here

GlobalKey in InheritedWidget causing re-parent

Is passing a GlobalKey down the tree using an InheritedWidget an antipattern? The stateful widget using that key is re-created (i.e. a new state this initState/disposed) every time its subtree is re-built.
My InheritedWidget looks like:
import 'package:flutter/material.dart';
import '../widgets/carousel.dart';
import '../widgets/panel/panel.dart';
class _CarouselKey extends GlobalObjectKey<CarouselState> {
const _CarouselKey(Object value) : super(value);
}
class _ProgressiveChatHeaderKey extends GlobalObjectKey<PanelScaffoldState> {
const _ProgressiveChatHeaderKey(Object value) : super(value);
}
class DimensionScopedKeyProvider extends InheritedWidget {
final _CarouselKey parallelBubbleCarouselKey;
final _ProgressiveChatHeaderKey progressiveChatHeaderKey;
final String keyString;
DimensionScopedKeyProvider({
Key key,
#required this.keyString,
#required Widget child,
}) : parallelBubbleCarouselKey = _CarouselKey(keyString),
progressiveChatHeaderKey = _ProgressiveChatHeaderKey(keyString),
super(key: key, child: child);
static DimensionScopedKeyProvider of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(DimensionScopedKeyProvider)
as DimensionScopedKeyProvider);
}
#override
bool updateShouldNotify(DimensionScopedKeyProvider oldWidget) => oldWidget.keyString != keyString;
}
And this InheritedWidget is rendered with a constant keyString, meaning that 1) updateShouldNotify always returns false and 2) the hashCode of the GlobalKeys passed to my build methods via DimensionScopedKeyProvider.of() are always identical.
The stateful widget builds something like
GlobalKey<PanelScaffoldState> get _headerKey => //
DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
// ...
PanelScaffold(
key: _headerKey,
// ...
)
When I change a property that affects the subtree that the PanelScaffold lives in, though, a new PanelScaffoldState is created and the old one is disposed, even though the widget tree hasn't changed structure and the _headerKey hasn't changed either.
I also able to solve this problem, but I have no idea why it works.
The solution is to cache the access to the GlobalKey in didChangeDependencies
#override
void didChangeDependencies() {
super.didChangeDependencies();
_headerKey ??= DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
}
.... and now everything is working as expected again—the rebuilds re-parent the existing state.
Does anyone know why caching the getter to the GlobalKey is the key here?