Flutter Riverpod - using read() inside build method - flutter

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.

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.

How to access a variable data from the state of a flutter stateful widget from another stateful widget class's state

Hi guys I am facing a problem which I have tried to solve through multiple approaches but failed. I have a stateful widget class which has a variable in its state I need to access the data in this variable from another stateful widget's class's state, I have tried accessing the variable by creating a GlobalKey for the class which contains the variable, but when I access the variable I get null. Am I accessing the variable the wrong, way or is there a more appropriate way of doing it?
what i usually do in cases like this is i will define varableX globally so that i can access it from anywhere in my app. To do this install flutter_riverpod and define variableX like this:
final variableXProvider = StateProvider<bool>((ref)=> false/true);
inside your build method, you can access it like this
class StatefulWidget1 extends ConsumerStatefulWidget {
const StatefulWidget1({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _StatefulWidget1State();
}
class _StatefulWidget1State extends ConsumerState<StatefulWidget1> {
#override
Widget build(BuildContext context) {
//* watch variableX like this
final varibleX = ref.watch(variableXProvider.state).state;
return Scaffold(body: Column(
children: [
Text(varibleX),
//* to change the value of variableX you can do this
ElevatedButton(onPressed: (){
ref.read(variableXProvider.notifier).state = true/false;
}, child: Text('Change value'))
],),);
}
}
once you hit the button, the value changes without needing to call setState(). the ref object will automatically rebuild itself once it detects any changes.

Create builder with pre-built subtree in 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);
}
}

FutureBuilder keeps executing when navigating between pages using Bottom Navigation Bar

I have a bottom navigation bar from my MaterialApp and one of the pages use FutureBuilder to retrieve data from my RESTful API. My bottom navigation bar needs to save the state of the pages, So I came across this guide on how to keep the state of my bottom navigation bar using PageStorage.
The issue I have encountered is that whenever I navigate out of the FutureBuilder page and back again, it rebuilds the entire page and re-executes my Future method.
I also read another guide on using AsyncMemoizer to run my Future method only once (It still rebuilds the page, but much faster). The snippet of code below is how I have implemented it.
//Unsure why AsyncMemoizer somehow only works if I use StatelessWidget, and not StatefulWidget
class FuturePage extends StatelessWidget {
/*I had to comment this constructor out because AsyncMemoizer must be
initialised with a constant value */
//const FuturePage({Key key}) : super(key: key);
//To store my PageStorageKey into bucket
FuturePage({Key key}) : super(key: key);
final _memoizer = new AsyncMemoizer();
_fetchData() => this._memoizer.runOnce(_myFutureMethod);
Future<MyType> _myFutureMethod() async => print("Executed"); //await post and return data
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: _fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//set up my widgets
}
return Center(child: CircularProgressIndicator());
}
);
}
}
On the output log, Executed is only displayed once. However, I need to use a StatefulWidget instead of StatelessWidget, and AsyncMemoizer wouldn't work in my case.
class FuturePage extends StatefulWidget {
FuturePageState createState() => FuturePageState();
const FuturePage({Key key}) : super(key: key);
}
How do I save the state of my FutureBuilder Page using StatefulWidget? I'm still relatively new to flutter and the concepts of reactive programming. Sincerest apologies if I happen to be doing something wrongly!
To achieve this behavior you can use the AutomaticKeepAliveClientMixin
class FuturePage extends StatefulWidget {
const FuturePage({Key key}) : super(key: key);
#override
FuturePageState createState() => FuturePageState();
}
You need to implement AutomaticKeepAliveClientMixin
class FuturePageState extends State<FuturePage> with AutomaticKeepAliveClientMixin<FuturePage>{
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context); // call super method
return // Your Widget
}
}

streambuilder is rebuilding again and again when keyboard popup or closes

Here I was stuck in a problem. I have a column of widgets with a stream builder and a text field. When i try to input some text, the keyboard pops up and then the stream builder rebuilds again or when the keyboard closes, the stream builder rebuilds again. As i am building a chat screen, I don't want to rebuild the stream builder again as it increases in number of reads.
Any sort of suggestions helpful.
Flutter calls the build() method every time it wants to change
anything in the view, and this happens surprisingly often.
You can pass the stream into the stateless widget
MyApp({Key key, this.stream}) : super(key: key);
Or build the stream in the initState method if the widget is statefull.
#override
void initState() {
super.initState();
post = buildStream();
}
What #TuanNguyen means by
build the stream in the initState method
is the following, if for you are using Firestore for exemple:
class MyStateFullWidget extends StatefulWidget {
const MyStateFullWidget({Key key}) : super(key: key);
#override
_MyStateFullWidgetState createState() => _MyStateFullWidgetState();
}
class _MyStateFullWidgetState extends State<MyStateFullWidget> {
Stream _myStream;
#override
void initState() {
super.initState();
_myStream = FirebaseFirestore.instance.collection(myCollection) ... .snapshots();
}
#override
Widget build(BuildContext context) {
return SomeUpperWidget(
child:
StreamBuilder(
stream: _myStream,
builder: (ctx, snap) => ... ,
)
);
}
}
I was facing the same issue. I could not find simple alternative to avoid re-rendering without changing much code. So I ended up like this:
So in the Bloc class, first initiated a variable say
streamStateIndex = 0;
And wherever I am using
sink.add(data)
, I started using
streamStateIndex++;
sink.add({"data": data, "streamStateIndex":streamStateIndex});
And initiated another variable lets say localStreamStateIndex = 0; inside Stateful flutter class to compare streamStateIndex from bloc
And used inside StreamBuilder like this:
if(snapshot.hasData){
if(localStreamStateIndex < snapshot.data['streamStateIndex']){
updateLocalState(snapshot.data['data']);
localStreamStateIndex = snapshot.data['streamStateIndex'];
}
}