Consumer not updating the state? - flutter

I am trying to create an Icon with a number indicator on top of it and the number indicator receives its data via a Consumer provider. The problem is that the state is not being updated by the consumer function and I don't understand why (if I update the state with a hot reload everything works just fine).
Here is the code for my main file:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => TestData())
// I use more providers but deleted them here for brevity
],
child: TestScreen3(),
),
);
}
}
The test screen 3 widget
class TestScreen3 extends StatefulWidget {
#override
_TestScreen3State createState() => _TestScreen3State();
}
class _TestScreen3State extends State<TestScreen3> {
#override
Widget build(BuildContext context) {
final testData = Provider.of<TestData>(context);
return Scaffold(
appBar: AppBar(
title: Text('Test app 3'),
actions: [
Consumer<TestData>(builder: (_, data, __) {
return IconButton(
icon: Badge(num: data.items.length.toString()),
onPressed: () => print(data.items.length));
})
],
),
body: Center(
child: ElevatedButton(
child: Text('Increase'),
onPressed: () {
testData.addItem();
},
),
),
);
}
}
The badge widget
class Badge extends StatelessWidget {
Badge({#required this.num});
final String num;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Icon(Icons.assessment),
Positioned(
child: Container(
padding: EdgeInsets.all(2),
child: Text(
num,
style: TextStyle(fontSize: 8),
textAlign: TextAlign.center,
),
constraints: BoxConstraints(
minHeight: 12,
minWidth: 12,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.red,
),
),
),
],
);
}
}
and the data model I am using
class Item {
Item(this.id);
final String id;
}
class TestData with ChangeNotifier {
List<Item> _items = [];
List<Item> get items => [..._items];
void addItem() {
_items.add(Item(DateTime.now().toString()));
}
notifyListeners();
}
The imports work just fine, I left them out for brevity. I followed along a this tutorial: https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/ and it uses a key argument for the badge that looks like this:
class Badge extends StatelessWidget {
const Badge({
Key key,
#required this.child,
#required this.value,
this.color,
}) : super(key: key);
final Widget child;
final String value;
final Color color;
However, the use of key or super is not explained in the tutorial and when I add these parameters to my code they don't seem to make a change.
Many thanks in advance, I probably missed something super obvious...

Add notifyListeners(); inside addItem() method
void addItem() {
_items.add(Item(DateTime.now().toString()));
notifyListeners();
}

Related

Flutter setstate method doesn't add my numbers

I built custom app for training. I created buttons with gesture detector and i assigned number to them and i created global variable "score". I want buttons to add their numbers to "score" variable and i want to show the variable in a container but Somehow it does not work.Can it be about states?Does anyone help me?
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Colors.deepPurple,
centerTitle: true,
elevation: 3,
),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Application"),
),
body: const Body(),
);
}
}
int score = 5;
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
width: 200,
decoration: const BoxDecoration(color: Colors.grey),
child: Center(child: Text(score.toString())),
),
const SizedBox(
height: 70,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 1,
),
Numb(
color: Colors.pink,
numb: 2,
),
Numb(
color: Colors.pink,
numb: 3,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 4,
),
Numb(
color: Colors.pink,
numb: 5,
),
Numb(
color: Colors.pink,
numb: 6,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 7,
),
Numb(
color: Colors.pink,
numb: 8,
),
Numb(
color: Colors.pink,
numb: 9,
),
],
),
],
);
}
}
class Numb extends StatefulWidget {
final int? numb;
final Color? color;
const Numb({
Key? key,
required this.numb,
required this.color,
}) : super(key: key);
#override
State<Numb> createState() => _NumbState();
}
class _NumbState extends State<Numb> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
score += widget.numb!;
});
},
child: Container(
margin: projectPadding.allPad * 0.5,
decoration: BoxDecoration(color: widget.color),
height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width * 0.1,
child: Center(
child: Text(widget.numb.toString()),
),
),
);
}
}
class projectPadding {
static const EdgeInsets horizantalPad = EdgeInsets.symmetric(horizontal: 20);
static const EdgeInsets verticalPad = EdgeInsets.symmetric(vertical: 20);
static const EdgeInsets allPad = EdgeInsets.all(20);
}
Numb setState only update the _NumbState ui. in order to update parent widget, you can use callback method that will trigger setState on parent UI.
class Numb extends StatefulWidget {
final int? numb;
final Color? color;
final Function(int) callBack;
const Numb({
Key? key,
required this.numb,
required this.color,
required this.callBack,
}) : super(key: key);
#override
State<Numb> createState() => _NumbState();
}
///....
onTap: () {
setState(() {
score += widget.numb!;
});
widget.callBack(score);
},
And you can add logic and others operation like
Numb(
color: Colors.pink,
numb: 1,
callBack: (p0) {
setState(() {});
},
),
I will also recommend starting state-management like riverpod / bloc
Why doesn't it work?
When you use the setState method, it triggers a rebuild of the widget where it is called.
In your code, you call the setState method in your Numb widget, which will therefore be rebuilt.
The problem is that the score value that you display on screen is located in your Body widget, and you want this widget to be rebuilt whenever the score changes.
So how do we do that?
How to make it work?
This is a State Management issue and there are multiple ways to solve it. You can find in the official documentation a good example to understand how this works.
Lifting the State Up and Callbacks
Following the previous logic described above, you would have to call the setState method in the Body widget to trigger a rebuild, and this whenever the score changes, which means whenever a Numb widget is pressed.
For that you can take advantage of callbacks which are basically functions that you can pass as parameters, and that will run the code they contain when they are called (you can see the official documentation's example about accessing a state using callbacks).
class Numb extends StatelessWidget { //<-- you can turn Numb into a StatelessWidget which is much simpler since you don't have to call the 'setState' method in it
final int? numb;
final Color? color;
final Function(int) callback; //<-- add your callback as a field...
const Numb({
Key? key,
required this.numb,
required this.color,
required this.callback, //<-- ... and in the constructor...
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => callback(numb!), //<-- ... and simply call it this way in the onTap parameter, giving it the numb value
child: Container(
margin: projectPadding.allPad * 0.5,
decoration: BoxDecoration(color: color),
height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width * 0.1,
child: Center(
child: Text(numb.toString()),
),
),
);
}
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
var updateScoreCallback = (int number) => setState(() => score += number); //<-- your callback takes an int as parameter and call setState adding the input number to the score
return Column(
children: [
...,
Numb(
color: Colors.pink,
numb: 1,
callback: updateScoreCallback, //<-- give your callback to your Numb widgets
),
Numb(
color: Colors.pink,
numb: 2,
callback: updateScoreCallback,
),
...
}
}
Like that, pressing any of your Numb widget will call the callback, that will call the setState method in the appropriate Body widget.
State Management with Packages
The previous method to handle state management with callbacks is good when your case is simple, but if you need for example to access a state from multiple places in your code, it can be quickly too difficult to manage and inefficient. For that, there are multiple packages that are available to make things easier. The official recommandation to start with is the Provider package as it is simple to use, but depending on your need you may want to look for other options like BLoC, Redux, GetX, etc. (full list of state management approaches.
Using the Provider approach, start by adding the package in your project (flutter pub add provider), and add the following changes to your code:
import 'package:provider/provider.dart';
class ScoreData with ChangeNotifier { //<-- create a structure to hold your data, and use the mixin 'ChangeNotifier' to make ScoreData able to notify its listeners for any changes
int _score = 5;
int get score => _score;
void addToScore(int number) {
_score += number;
notifyListeners(); //<-- this method comes from ChangeNotifier (which comes from the Flutter SDK and not from the Provider package), and notify all listeners
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Application"),
),
body: ChangeNotifierProvider<ScoreData>( //<-- this says that at this level of the widget tree, you provide a ScoreData that can notify its listeners for any changes...
create: (context) => ScoreData(), //<-- ... and you provide the ScoreData here by creating a new one (you can provide an existing one using the "value" parameter)
child: const Body(),
),
);
}
}
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
width: 200,
decoration: const BoxDecoration(color: Colors.grey),
child: Center(
child: Text(Provider.of<ScoreData>(context).score.toString())), //<-- this enables you to retrieve the provided ScoreData higher in the widget tree, and to listen to its value
),
...
],
);
}
}
class Numb extends StatelessWidget {
final int? numb;
final Color? color;
const Numb({
Key? key,
required this.numb,
required this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () =>
Provider.of<ScoreData>(context, listen: false).addToScore(numb!), //<-- here as well you retrieve the provided ScoreData, but you only want to call its method "addToScore" without needing to listen to its changes, so add the "listen: false" parameter to make it work
child: Container(
...
),
);
}
}

Changing the state Widget of one through another Widget

MyHomePageState:
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: bgColor,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(),
],
),
connectedStatusText(),
],
));
}
I'm trying to change the status of connectedStatusText() from mainWidget()!
My connectedStatus:
class connectedStatusText extends StatefulWidget
{
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', style: connectedStyle, children: [
TextSpan(text: status, style: disconnectedRed)
]),
),
);
}
}
I want to change the $status text to "connected" through ontap of mainWidget().
mainWidget:
....
class mainWidget extends StatefulWidget
{
MyED createState() => new MyED();
}
class MyED extends State<mainWidget> {
child: new GestureDetector(
onTap: () => setState(() {
//change here
}
tried to set a global variable to connectedStatus:
GlobalKey<connectedStatus> key = GlobalKey<connectedStatus>();
and change by ontap...
child: new GestureDetector(
onTap: () => setState(() {
//change here
key.currentState.status = "CONNECTED";
}
)
}
but it does not work!
Any help for me to change this text through another place?
Please refer to below example code to update state using ValueNotifier and ValueListenableBuilder.
ValueNotifer & ValueListenableBuilder can be used to hold value and update widget by notifying its listeners and reducing number of times widget tree getting rebuilt.
import 'package:flutter/material.dart';
const 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: Screen2(),
);
}
}
class Screen2 extends StatefulWidget {
final String userId; // receives the value
const Screen2({Key key, this.userId}) : super(key: key);
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
final ValueNotifier<bool> updateStatus = ValueNotifier(false);
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Colors.blue,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(
updateStatus: updateStatus,
),
],
),
connectedStatusText(
updateStatus: updateStatus,
),
],
),
); // uses the value
}
}
class connectedStatusText extends StatefulWidget {
final ValueNotifier<bool> updateStatus;
connectedStatusText({
Key key,
this.updateStatus,
}) : super(key: key);
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: /*
In order update widget we can use ValueListenableBuilder which updates the particular widget when the value changes (ValueNotifier value)
*/
ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', children: [
TextSpan(
text: (widget.updateStatus.value == true)
? "Active"
: status,
)
]),
);
}),
);
}
}
class mainWidget extends StatefulWidget {
final String userId; // receives the value
final ValueNotifier<bool> updateStatus;
mainWidget({
Key key,
this.userId,
this.updateStatus,
}) : super(key: key);
#override
_mainWidgetState createState() => _mainWidgetState();
}
class _mainWidgetState extends State<mainWidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.updateStatus.value = !widget.updateStatus.value;
},
child: ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return Text(snapshot.toString());
}));
// uses the value
}
}

flutter dependOnInheritedWidgetOfExactType() returns null

I am trying to use InheritedWidget approach to share state down the Widget tree. For this, I am making a simple counter app. You can add, subtract, multiply or divide the counter however you like.
It's a small demo so best practices are not followed. The line with code context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>() seem to be null for some reason. When looking at samples and doc, it should find the MyInheritedWidget in the widget tree and return it. However, I am getting complaints from flutter tool that it is null. And, in deed it is null when asserted as well.
What is the reasoning here for failed return here? And, how do I need to do it such that I can receive the instance?
File: main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Counter(),
);
}
}
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App'),
centerTitle: true,
),
body: MyInheritedWidget(
counterState: this,
child: Builder(
builder: (BuildContext innerContext) {
return CounterViewer(
counterState: MyInheritedWidget.of(context).counterState);
},
),
),
);
}
void addCounter(int value) {
setState(() {
counter++;
});
}
void subtractCounter(int value) {
setState(() {
counter--;
});
}
void multiplyCounter(int value) {
setState(() {
counter *= value;
});
}
void divideCounter(int value) {
setState(() {
counter = (counter / value).toInt();
});
}
}
class MyInheritedWidget extends InheritedWidget {
final CounterState counterState;
const MyInheritedWidget(
{Key? key, required Widget child, required this.counterState})
: super(key: key, child: child);
static MyInheritedWidget of(BuildContext context) {
final MyInheritedWidget? widget =
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
assert(widget != null);
return widget!;
}
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return true;
}
}
class CounterViewer extends StatelessWidget {
final CounterState counterState;
const CounterViewer({Key? key, required this.counterState}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
color: Colors.green.shade200,
width: MediaQuery.of(context).size.width,
height: 180,
child: Center(
child: Text(
'220',
style: TextStyle(
color: Colors.grey.shade50,
fontSize: 60,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
color: Colors.grey.shade300,
padding: EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {},
child: Text('Add'),
),
ElevatedButton(
onPressed: () {},
child: Text('Subtract'),
),
ElevatedButton(
onPressed: () {},
child: Text('Multiply'),
),
ElevatedButton(
onPressed: () {},
child: Text('Divide'),
),
],
),
)
],
);
}
}
Update: I seem to have passed the wrong context to the dependOnInheritedWidgetOfExactType() method. Changing from context to innerContext fixed the issue.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App'),
centerTitle: true,
),
body: MyInheritedWidget(
counterState: this,
child: Builder(
builder: (BuildContext innerContext) {
return CounterViewer(
counterState: MyInheritedWidget.of(innerContext).counterState);
},
),
),
);
}

How to use a Checkbox to change text in Flutter/Dart using an array/list (troubles with onChanged/setState)

Thanks in advance, I am fairly new to this and have followed a few tutorials and read countless posts here.
I am working on a personal project and have struck a dead end.
I wish to change the text within a text widget by use of a checkbox, in essence turning it on and off.
Unfortunately what at first seemed simple has driven me insane for the last week or so, I apologize if this has been answered but I can't find the help I need, I have found things similar but nothing has worked so far.
I'm trying to change my int value with an onchanged and set staff function with a list/array and an int value, I effectively want to set the value from 0 to 1 and then back again.
var textChange = [" could be ", " have become "];
int t = 0;
with;
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked){
t = 0;
}
}
);
},
I have made sure I am extending a Stateful Widget tried setting up different functions and methods to pass through, I have tried using a material button instead of a checkbox but still hit the same issue.
I have tried a few different ways to write my onset function including something as simple as [t++ or t = (d + 1) % textChange.length;] but these would ultimately end in error once the int = >2, but I couldn't even get the value of my int to change.
If I changed it manually and run the program, it works. however, I can't seem to get my onchanged and set state code to affect my array or int.
I even had my original array as List, changed it to var in hopes it would make it mutable.
I hope I said that right, I am clearly missing something but I've looked at so much I'm just lost.
Below is the code I have used with the fat trimmed from the rest of my project. I'm using android studios with the latest updates.
Thanks for your help and time.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'alternate text code',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'alternate text'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
Text(
'I am having trouble getting some code to work, I have tried '
'a lot of things but can't work it out.'
'I would like the check box to change the line of text below,
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
ChangeText(),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
Text(
'you' + textChange[t] + 'a App Developer.',
),
),
],
),
),
);
}
}
bool boxChecked = false;
class ChangeText extends StatefulWidget {
const ChangeText({Key? key}) : super(key: key);
#override
State<ChangeText> createState() => _ChangeText();
}
var textChange = [" could be ", " have become "];
int t = 0;
class _ChangeText extends State<ChangeText> {
#override
Widget build(BuildContext context) {
return Expanded(
child:
Container(
child: CheckboxListTile(
title: const Text('learn Flutter', style: TextStyle(fontSize: 14),),
value: boxChecked,
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked){
t = 0;
}
}
);
},
),
),
);
}
}
From ChangeText widget, you are calling setState mean it will only change the UI within ChangeText widget. However, Text reflect need to happened on MyHomePage. You can use global key to change the parent state or just use callback method like
class ChangeText extends StatefulWidget {
final Function callBack;
const ChangeText({
Key? key,
required this.callBack,
}) : super(key: key);
........
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked) {
t = 0;
}
});
widget.callBack();
},
and use like
child: ChangeText(
callBack: () {
setState(() {});
},
),
Full Widgets
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'alternate text code',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'alternate text'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: Text(
'I am having trouble getting some code to work, i have tried '
'a lot of things but cant work it out.'
'I would like the check box to change the line of text below',
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: ChangeText(
callBack: () {
setState(() {});
},
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: Text(
'you' + textChange[t] + 'a App Developer.',
),
),
],
),
),
);
}
}
bool boxChecked = false;
class ChangeText extends StatefulWidget {
final Function callBack;
const ChangeText({
Key? key,
required this.callBack,
}) : super(key: key);
#override
State<ChangeText> createState() => _ChangeText();
}
var textChange = [" could be ", " have become "];
int t = 0;
class _ChangeText extends State<ChangeText> {
#override
Widget build(BuildContext context) {
return
// Expanded(
// child:
Container(
child: CheckboxListTile(
title: const Text(
'learn Flutter',
style: TextStyle(fontSize: 14),
),
value: boxChecked,
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked) {
t = 0;
}
});
widget.callBack();
},
),
// ),
);
}
}

Get the rack to update after shuffle

In the example below, what is the best construct to use to get the rack to update after a shuffle?
It seems to me that when a StatefulWidget is created, with its corresponding State Object (SO), any method that you can call from elsewhere is a method that's attached to the widget itself (not to the SO).
But, to get the widget to update its display, the SetState() method can only go in the SO's method(s). So how does the method on the widget call a method on its SO?
import 'package:flutter/material.dart';
List<Block> g_blocks = [Block(Colors.red), Block(Colors.green), Block(Colors.blue)];
Rack g_rack = new Rack();
void main() => runApp(MyApp());
// This widget is the root of your application.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: 'PressStart',
),
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
MyHomeScreen({Key key}) : super(key: key);
createState() => MyHomeScreenState();
}
class MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(child: Text('Thanks for your help')),
backgroundColor: Colors.pink,
),
body: Center(
child: g_rack,
),
bottomNavigationBar: SizedBox(
height: 100.0,
child: BottomNavigationBar(
currentIndex: 0,
iconSize: 48.0,
backgroundColor: Colors.lightBlue[100],
items: [
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
],
onTap: (int indexOfItem) {
setState(() {
g_blocks.shuffle;
rack.updateScreen(); // ** How to get the rack to update? **
});
},
),
),
);
} // build
} // End class MyHomeScreenState
class Rack extends StatefulWidget {
#override
_rackState createState() => _rackState();
}
class _rackState extends State<Rack> {
#override
Widget build(BuildContext context) {
return Container(
height: 150.0,
color: Colors.yellow[200],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: g_blocks),
);
}
void updateRack(){
setState(() {
g_blocks.shuffle;
});
}
}
class Block extends StatelessWidget {
final Color color;
Block(this.color);
#override
Widget build(BuildContext context) {
return Container(height:50,width:50, color: color,);
}
}
Here is a solution where I try to decouple the State Management and Business Logic of the application from the User Interface.
I used the following packages:
freezed for the Domain Entities
hooks_riverpod for the State Management
1. Domain Layer: Entities
We need two Entities to model our Racks of Blocks.
Blocks are defined by their color.
Blocks have no business logic.
Racks are ordered lists of Blocks.
Racks can get shuffled.
Racks can be randomly created for a (random or given) number of Blocks
#freezed
abstract class Block with _$Block {
const factory Block({Color color}) = _Block;
}
#freezed
abstract class Rack implements _$Rack {
const factory Rack({List<Block> blocks}) = _Rack;
const Rack._();
static Rack create([int nbBlocks]) => Rack(
blocks: List.generate(
nbBlocks ?? 4 + random.nextInt(6),
(index) => Block(
color: Color(0x66000000 + random.nextInt(0xffffff)),
),
),
);
Rack get shuffled => Rack(blocks: blocks..shuffle());
}
We use the freeze package to have immutability and the precious copyWith method to manage our States.
2. Application Layer: State Management
We use Hooks Riverpod for our State Management. We just need one StateNotifier and its provider.
This StateNotifierProvider gives access to both the Rack State and the core functionalities that are deal() and shuffle().
class RackStateNotifier extends StateNotifier<Rack> {
static final provider =
StateNotifierProvider<RackStateNotifier>((ref) => RackStateNotifier());
RackStateNotifier([Rack state]) : super(state ?? Rack.create());
void shuffle() {
state = state.shuffled;
}
void deal() {
state = Rack.create();
}
}
3. Presentation Layer: User Interface
The User Interface is made of four Widgets:
AppWidget [StatelessWidget]
HomePage [HookWidget]
RackWidget [StatelessWidget]
BlockWidget [StatelessWidget]
As you see, the only Widget that really cares about the State of the Application is the HomePage.
3.1 AppWidget
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.amber,
accentColor: Colors.black87,
),
home: HomePage(),
);
}
}
3.2 HomePage
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rack = useProvider(RackStateNotifier.provider.state);
return Scaffold(
appBar: AppBar(
title: Row(
children: const [
Icon(Icons.casino_outlined),
SizedBox(
width: 8.0,
),
Text('Rack Shuffler'),
],
),
),
body: Center(
child: RackWidget(rack: rack),
),
bottomNavigationBar: BottomAppBar(
color: Theme.of(context).primaryColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.refresh),
iconSize: 48,
onPressed: () => context.read(RackStateNotifier.provider).deal(),
),
IconButton(
icon: Icon(Icons.shuffle),
iconSize: 48,
onPressed: () =>
context.read(RackStateNotifier.provider).shuffle(),
),
],
),
),
);
}
}
rack is provided by our StateNotifierProvider, in watch mode:
final rack = useProvider(RackStateNotifier.provider.state);
The Racks are dealt and shuffled using the same provider, in read mode:
...
context.read(RackStateNotifier.provider).deal(),
...
context.read(RackStateNotifier.provider).shuffle(),
...
3.3 RackWidget
class RackWidget extends StatelessWidget {
final Rack rack;
const RackWidget({Key key, this.rack}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: rack.blocks
.map((block) => BlockWidget(
block: block,
size: constraints.biggest.width / rack.blocks.length))
.toList(),
);
},
),
);
}
}
Basic StatelessWidget. We use a LayoutBuilder to define the size of the BlockWidgets.
3.4 BlockWidget
class BlockWidget extends StatelessWidget {
final Block block;
final double size;
const BlockWidget({
Key key,
this.block,
this.size,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Padding(
padding: EdgeInsets.all(size / 10),
child: Container(
decoration: BoxDecoration(
color: block.color,
border: Border.all(color: Colors.black87, width: size / 20),
borderRadius: BorderRadius.circular(size / 15),
),
),
),
);
}
}
Another basic StatelessWidget.
Full Application Code
Just copy-paste the following to try it out.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part '66053795.shuffle.freezed.dart';
Random random = Random();
void main() => runApp(ProviderScope(child: AppWidget()));
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.amber,
accentColor: Colors.black87,
),
home: HomePage(),
);
}
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rack = useProvider(RackStateNotifier.provider.state);
return Scaffold(
appBar: AppBar(
title: Row(
children: const [
Icon(Icons.casino_outlined),
SizedBox(
width: 8.0,
),
Text('Rack Shuffler'),
],
),
),
body: Center(
child: RackWidget(rack: rack),
),
bottomNavigationBar: BottomAppBar(
color: Theme.of(context).primaryColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.refresh),
iconSize: 48,
onPressed: () => context.read(RackStateNotifier.provider).deal(),
),
IconButton(
icon: Icon(Icons.shuffle),
iconSize: 48,
onPressed: () =>
context.read(RackStateNotifier.provider).shuffle(),
),
],
),
),
);
}
}
class RackWidget extends StatelessWidget {
final Rack rack;
const RackWidget({Key key, this.rack}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: rack.blocks
.map((block) => BlockWidget(
block: block,
size: constraints.biggest.width / rack.blocks.length))
.toList(),
);
},
),
);
}
}
class BlockWidget extends StatelessWidget {
final Block block;
final double size;
const BlockWidget({
Key key,
this.block,
this.size,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Padding(
padding: EdgeInsets.all(size / 10),
child: Container(
decoration: BoxDecoration(
color: block.color,
border: Border.all(color: Colors.black87, width: size / 20),
borderRadius: BorderRadius.circular(size / 15),
),
),
),
);
}
}
class RackStateNotifier extends StateNotifier<Rack> {
static final provider =
StateNotifierProvider<RackStateNotifier>((ref) => RackStateNotifier());
RackStateNotifier([Rack state]) : super(state ?? Rack.create());
void shuffle() {
state = state.shuffled;
}
void deal() {
state = Rack.create();
}
}
#freezed
abstract class Block with _$Block {
const factory Block({Color color}) = _Block;
}
#freezed
abstract class Rack implements _$Rack {
const factory Rack({List<Block> blocks}) = _Rack;
const Rack._();
static Rack create([int nbBlocks]) => Rack(
blocks: List.generate(
nbBlocks ?? 4 + random.nextInt(6),
(index) => Block(
color: Color(0x66000000 + random.nextInt(0xffffff)),
),
),
);
Rack get shuffled => Rack(blocks: blocks..shuffle());
}
Here is a solution using a GlobalKey.
It feels pretty inelegant. It surprises me that with the close relationship between the widget and its state object, there's no easy way for a widget's method to call a method on the SO. The "widget.blah" construct provides a way for the SO to access the widget's data, is there a reason for not having a similar "state.myMethod" construct?
Anyway, the following works:
import 'package:flutter/material.dart';
List<Block> g_blocks = [Block(Colors.red), Block(Colors.green),
Block(Colors.blue), Block(Colors.purple)];
GlobalKey g_key = GlobalKey();
Rack g_rack = new Rack(key: g_key);
void main() => runApp(MyApp());
// This widget is the root of your application.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: 'PressStart',
),
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
MyHomeScreen({Key key}) : super(key: key);
createState() => MyHomeScreenState();
}
class MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(child: Text('Thanks for your help')),
backgroundColor: Colors.pink,
),
body: Center(
child: g_rack,
),
bottomNavigationBar: SizedBox(
height: 100.0,
child: BottomNavigationBar(
currentIndex: 0,
iconSize: 48.0,
backgroundColor: Colors.lightBlue[100],
items: [
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
],
onTap: (int index) {
g_blocks.shuffle();
g_key.currentState.setState(() {
});
}
),
),
);
} // build
} // End class MyHomeScreenState
class Rack extends StatefulWidget {
Rack({Key key}) : super(key: key);
#override
_rackState createState() => _rackState();
}
class _rackState extends State<Rack> {
#override
Widget build(BuildContext context) {
return Container(
height: 150.0,
color: Colors.yellow[200],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: g_blocks),
);
}
void updateRack(){
setState(() {});
}
}
class Block extends StatelessWidget {
final Color color;
Block(this.color);
#override
Widget build(BuildContext context) {
return Container(height:50,width:50, color: color,);
}
}