So I have this ChangeNotifierProvider high in my widget tree as I am seeing many children widgets to listen to its value.
What I am currently doing is that I pass down the Provider.of(context) object from the parent widget into it's children via constructors whenever I am to reuse some values/functions on my children widgets. For example, everytime I create a Provider.of(context) object for my children widgets, it seems that it does not carry over the updated values I have on the Parent Provider but rather this one has my default null/0/'empty' ones like it has only been created. This lead me to pass down the initial Provider.of(context) object to each children that will use the updated values and functions of the ChangeNotifier.
This setup is working for me, however, when my Widget Tree has started being complex, I am constantly passing down values through each widget and to some that do not even use it at all just for its children to listen to the main provider.
I think what I may be doing now is anti-pattern of the Provider Architecture, I am hoping you guys can help me on a more optimized and efficient way of doing this.
Thank you very much!
P.S. There are some things in the documentation that I am not yet quite grasping properly.
Edits Below to include sample code and visualization:
provider_type.dart
class ProviderType extends ChangeNotifier{
String valueA = '';
String valueB = '';
}
home.dart
import ..provider_type.dart
...
Widget build(BuildContext context){
return ChangeNotifierProvider<ProviderType>(
create: (context) => ProviderType(),
child: ScreenColumn();
);
}
...
screen_column.dart
import ..screen_a.dart
import ..screen_b.dart
class ScreenColumn extends StatelessWidget{
Widget build(BuildContext context){
var providerType = Provider.of<ProviderType>(context);
return Column(
children: <Widget>[
ScreenA(providerType: providerType),
ScreenB(providerType: providerType),
],
);
}
}
screen_a.dart
class ScreenA extends StatelessWidget{
final ProviderType providerType;
ScreenA({this.providerType});
Widget build(BuildContext context){
return Text(
'${providerType.valueA}'
);
}
}
screen_b.dart
import ..screen_c.dart
class ScreenB extends StatelessWidget{
final ProviderType providerType;
ScreenB({this.providerType});
Widget build(BuildContext context){
return ScreenC(providerType: providerType);
}
}
screen_c.dart
class ScreenC extends StatelessWidget{
final ProviderType providerType;
ScreenB({this.providerType});
Widget build(BuildContext context){
return Column(
children: <Widget>[
Text(
'${providerType.valueA}'
)
Text(
'${providerType.valueB}'
)
Text(
'${providerType.valueC}'
)
]
);
}
}
Visualization
So what I am currently doing is to pass down the object providerType from ScreenColumn to Screens A, B, and C just so each of them have the same "Source of Values". Cause when I try to make different Provider.of objects and use them, they do not share the same updated values when I do some computation.
Is there something I can do to make this more efficient or is there a better way that I need to do?
To those who may be wondering or are searching for answers to the same question, look at my sample code below that shows how you can reuse/share your Provider Values and Functions at any point in your widget tree as long as they are under your Parent Provider.
And yes, you can actually just create Provider.of Objects anywhere in
your tree without passing down the initial Provider.of object that you
have created.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderType extends ChangeNotifier {
String value = DateTime.now().toString();
changeValue() {
value = DateTime.now().toString();
notifyListeners();
}
}
void main() => runApp(AppIndex());
class AppIndex extends StatelessWidget {
const AppIndex({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ProviderType>(
create: (context) => ProviderType(),
child: MaterialApp(
home: Home(),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Scaffold(
appBar: AppBar(
title: Text('Sample App'),
),
body: ScreenColumn(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => providerType.changeValue(),
label: Text('ChangeValue'),
),
);
}
}
class ScreenColumn extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ScreenA(),
ScreenB(),
ScreenC(),
ScreenC(),
],
));
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.red,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(providerType.value),
),
);
}
}
class ScreenB extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.blue,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(providerType.value),
ScreenC(),
ScreenC(),
],
),
),
);
}
}
class ScreenC extends StatelessWidget {
#override
Widget build(BuildContext context) {
// var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.green,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('This is Screen B with no Provider.of Object'),
ScreenD(),
ScreenD(),
ScreenD(),
],
),
),
);
}
}
class ScreenD extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.yellow,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'This is Screen D. A Provider.of object was created here without inheriting the Parent\'s Provider.of object.'),
Text(providerType.value),
],
),
),
);
}
}
Related
How to change a variable of a Widget from another widget?
This is the main stateful widget called HomePage:
class _HomePageState extends State<HomePage> {
#override
num counter = 0;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Title")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [Text(counter.toString()), CardWidget()],
),
));
}
}
This is CardWidget which is added to HomePage:
class CardWidget extends StatefulWidget {
const CardWidget({Key? key}) : super(key: key);
#override
_CardWidgetState createState() => _CardWidgetState();
}
class _CardWidgetState extends State<CardWidget> {
#override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Text("Press the button to increment the counter"),
ElevatedButton(
onPressed: () {
//Something here to increment the counter in HomePage
},
child: const Text('Increment'),
),
],
));
}
}
This is what is shown on the screen:
Is it possible to create a connection between the two widgets: if I tap the button something happens in the HomePage Widget? (similar to delegate in UIKit)
You can pass Function parameter.
In your CardWidget add Function parameter.
class CardWidget extends StatefulWidget {
//Add clicked function
final Function onClicked;
const CardWidget({Key? key, required this.onClicked}) : super(key: key);
#override
_CardWidgetState createState() => _CardWidgetState();
}
class _CardWidgetState extends State<CardWidget> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Text("Press the button to increment the counter"),
ElevatedButton(
onPressed: () {
//Something here to increment the counter in HomePage
//Execute `onClicked` and pass parameter you want
_count++;
widget.onClicked(_count);
},
child: const Text('Increment'),
),
],
));
}
}
then on HomePage add onClicked parameter
class _HomePageState extends State<HomePage> {
#override
num counter = 0;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Fontanelle")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(counter.toString()),
CardWidget(
//Add onClicked
onClicked:(count){
print("Clicked "+count.toString());
}
)
],
),
));
}
}
You have some solution for this case:
1, Create GlobalKey for StatefullWidget, and you can access to State from HomePage
2, Create a Stream from Homepage and pass to StatefullWidget
3, Pass param to StatefullWidget and use didUpdateWidget on state to listen.
I remember that was my first question when did my first step in flutter, how say #dangngocduc is true but my advice is that read about BLOC
https://pub.dev/packages/flutter_bloc
This is very helpful to do this.
I am trying to have a global integer that is displayed in a widget and then is updated by something (a button click or something) from another widget. All of the other ways i have tried don't work. What is the best way to do this?
Stack overflow says i have too much code so more text more text more text
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ScoreDisplay(),
PointButton(),
],
),
),
),
),
);
}
int score = 0;
class ScoreDisplay extends StatefulWidget {
#override
_ScoreDisplayState createState() => _ScoreDisplayState();
}
class _ScoreDisplayState extends State<ScoreDisplay> {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Text(
'Score: $score',
),
),
);
}
}
class PointButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: RaisedButton(
//onPressed: //increment score,
),
),
);
}
}
You need to implement some kind of State Management for that.
Here are two basic ways to implement such a feature: with a StatefulWidget and with Riverpod.
1. With a StatefulWidget
I extracted your Scaffold as a StatefulWidget maintaining the score of your application.
I then use ScoreDisplay as a pure StatelessWidget receiving the score as a parameter. And your PointButton is also Stateless and call the ScorePage thanks to a simple VoidCallback function.
Full source code:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: ScorePage(),
),
);
}
class ScorePage extends StatefulWidget {
const ScorePage({
Key key,
}) : super(key: key);
#override
_ScorePageState createState() => _ScorePageState();
}
class _ScorePageState extends State<ScorePage> {
int score = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ScoreDisplay(score: score),
PointButton(onIncrement: () => setState(() => score++)),
],
),
),
);
}
}
class ScoreDisplay extends StatelessWidget {
final int score;
const ScoreDisplay({Key key, this.score}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Text(
'Score: $score',
),
),
);
}
}
class PointButton extends StatelessWidget {
final VoidCallback onIncrement;
const PointButton({Key key, this.onIncrement}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: ElevatedButton(
onPressed: () => onIncrement?.call(),
child: Text('CLICK ME'),
),
),
);
}
}
2. With Riverpod
Create a StateProvider:
final scoreProvider = StateProvider<int>((ref) => 0);
Watch the StateProvider:
final score = useProvider(scoreProvider).state;
Update the StateProvider
context.read(scoreProvider).state++
Full Source Code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
home: Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ScoreDisplay(),
PointButton(),
],
),
),
),
),
),
);
}
int score = 0;
class ScoreDisplay extends HookWidget {
#override
Widget build(BuildContext context) {
final score = useProvider(scoreProvider).state;
return Center(
child: Container(
child: Text(
'Score: $score',
),
),
);
}
}
class PointButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: ElevatedButton(
onPressed: () => context.read(scoreProvider).state++,
child: Text('CLICK ME'),
),
),
);
}
}
final scoreProvider = StateProvider<int>((ref) => 0);
Check Riverpod Website for more info and more advanced use cases.
But you have many more flavors of State Management available.
The best example is to use "provider" package which can be found on www.pub.dev
It is very easy state management package that can help You solve this problem. Keep in my that provider instead of setState() uses notifyListener()
I'm learning Flutter and I'm stuck on state management. I took a look at Riverpod and it looks promising, but I have a hard time to go beyond the counter app to something more complicated.
For example, I want to have two TextFields that collect numbers, and another Text widget to display the sum of the two TextField values. Here's what I have.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(ProviderScope(
child: MyApp(),
));
}
class MyApp extends HookWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Adding two cells'),
);
}
}
final cellProvider = StateProvider((_) => <int>[0, 0]);
class MyHomePage extends HookWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Consumer(builder: (context, watch, _) {
print(watch(cellProvider).state);
num _sum = watch(cellProvider).state[0] + watch(cellProvider).state[1];
return Center(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Cell(0),
Cell(1),
],
),
SizedBox(
height: 100,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Sum: ${_sum.toString()}'),
],
),
],
),
);
}),
);
}
}
class Cell extends HookWidget {
Cell(this.index);
final int index;
#override
Widget build(BuildContext context) {
return Container(
width: 150,
padding: EdgeInsets.all(30),
child: TextField(
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (value) {
context.read(cellProvider).state[index] = num.tryParse(value);
},
),
);
}
}
The Text widget does not update. Any suggestion?
Thank you very much,
Tony
The provider only updates when the object it provides changes, just as a Stream returns a final value you need to update the whole object (List<int>) so the consumer updates properly, changing inner values of an iterable won't trigger an update
onChanged: (value) {
final List<int> myList = context.read(cellProvider).state;
myList[index] = num.tryParse(value);
context.read(cellProvider).state = myList; //update the state with a new list
},
I am new to flutter/dart and am trying to get a value to update for a loading bar I would like to make. The value is updating within a provider widget I created, but when I try to utilize it, I am unable to figure out what is missing. Any assistance would be greatly appreciated.
class LoadingProvider extends ChangeNotifier{
int _downloadPercentage = 0;
int get downloadPercentage => _downloadPercentage;
void getPercentage(int input) {
_downloadPercentage = input;
notifyListeners();
print(downloadPercentage);
}
}
Here is the page I wish to display on
class ContourScreen extends StatefulWidget {
final Note note;
ContourScreen(this.note);
#override
State<StatefulWidget> createState() => new _ContourScreenState();
}
class _ContourScreenState extends State<ContourScreen> with TickerProviderStateMixin{
#override
void initState() {
super.initState();
}
#override
dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
final loadingProvider = Provider.of<LoadingProvider>(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(),
body: Scrollbar(
child: SingleChildScrollView(
child: Container(
child: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ChangeNotifierProvider<ValueNotifier<int>>(
create: (context) => ValueNotifier<int>(0),
child: Text('${loadingProvider.downloadPercentage}'),),
],
),
),
),
),
),
);
}
}
Here is the git hub to all pages for reference https://github.com/mattcubs/loadinggif
I need to copy a value of a Text Widget and copy this to another.
I tried to this using keys, but I don't know how to acess the Text Widget in this case.
Is it possible to do in Flutter, using the onPressed property?
import 'package:flutter/material.dart';
class TextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Value to be copied",
key: Key('text1')
),
RaisedButton(
onPressed: (){
// code here
},
child: Text("Copy value"),
),
SizedBox(height: 40),
Text(
"",
key: Key('text2')
)
],
),
),
);
}
}
Answering your question directly: you can access text inside Text widget using its data property.
Text widget = Text('text value');
String text = widget.data;
print(text); // text value
Next, you can't access widgets by their key properties. At least you shouldn't, because they were designed for different purpose: here's a video and an article about keys in Flutter.
What you can do here is turn your TextWidget from StatelessWidget into StatefulWidget and render contents of your second Text based on the state. Good introduction into what the state is and why you should use it can be found on official Flutter website: Start thinking declaratively.
Then you can save your first Text widget in a variable and then access its contents directly using data property update, then update state of the whole widget.
Example 1 on DartPad
More canonical and in general preferrable approach is to render contents of both buttons based on the state and get desired text from state variable and not from the widget itself, as proposed by Sebastian and MSARKrish.
Example 2 on DartPad
Note that you can't change data attribute of a Text widget imperatively, like you would do in JavaScript DOM API with innerText:
_textWidget.data = "New text"; // Doesn't work
because its data is final. In Flutter you have to think declaratively, and it worth it.
Try this
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _text = "Value to be copied";
bool _buttonToggle;
#override
void initState() {
super.initState();
_buttonToggle = false;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_text),
SizedBox(height: 40),
RaisedButton(
onPressed: _toggle,
child: Text("Copy value"),
),
Switch(
value: _buttonToggle,
onChanged: (_) => _toggle(),
),
SizedBox(height: 40),
Text(_buttonToggle ? _text : '')
],
);
}
void _toggle() {
setState(() => _buttonToggle = !_buttonToggle);
}
}
class TextWidget extends StatefulWidget {
#override
_TextWidgetState createState() => _TextWidgetState();
}
class _TextWidgetState extends State<TextWidget> {
String text1Value = "text to be copied";
String text2Value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text1Value,
),
RaisedButton(
onPressed: () {
setState(() {
text2Value = text1Value;
});
},
child: Text("Copy value"),
),
SizedBox(height: 40),
Text(
text2Value,
)
],
),
),
);
}
}