How can I move forward and backward data between to different stateful widgets in Flutter? - flutter

There is count variable in the first stateful widget, I have passed it to Setting class. And, Setting class passes it toSettingStateBuilder. Then, its value is changed in incrementing() in SettingStateBuilder. I want the updated value to return back to HomePageBody for further work. How can I do that?
The first stateful widget is created as follow:
class HomePage extends StatefulWidget {
#override
HomePageBody createState() => HomePageBody();
}
class HomePageBody extends State<HomePage> {
int count=0;
#override
Widget build(BuildContext context) {
...
new Setting(count);
}
}
The second stateful widget is created as follow:
class Setting extends StatefulWidget {
int count;
Setting(this.count);
#override
SettingStateBuilder createState() => SettingStateBuilder(count);
}
class SettingStateBuilder extends State<Setting> {
int count;
SettingStateBuilder(this.count);
#override
Widget build(BuildContext context) {
return new Container(
new Text(count.toString());
....
onPressed: () => setState(() => incrementing(context))),
);
}
incrementing(context) { count += 1; }
}

You could add a Function property to the Settings widget that will be called when the counter is incremented, and pass that function when you create the widget so in HomePage you can update the counter:
class HomePage extends StatefulWidget {
#override
HomePageBody createState() => HomePageBody();
}
class HomePageBody extends State<HomePage> {
int count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
MaterialButton(
child: Text('Settings'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => Settings(
count,
(newCount) {
setState(
() {
count = newCount;
},
);
},
),
),
);
},
)
],
),
body: Center(
child: Text('$count'),
),
);
}
}
class Settings extends StatefulWidget {
final int count;
final Function(int) onCounterChanged;
Settings(this.count, onCounterChanged);
#override
SettingsStateBuilder createState() => SettingsStateBuilder(count);
}
class SettingsStateBuilder extends State<Settings> {
int count;
SettingsStateBuilder(this.count);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$count'),
MaterialButton(
child: Text('Increment'),
onPressed: () => setState(
() {
increment();
},
),
),
],
),
),
);
}
increment() {
count += 1;
widget.onCounterChanged(count);
}
}
If you are dealing with a more complex use case I suggest you to read for how to approach state management in Flutter, some resources:
https://flutter.dev/docs/development/data-and-backend/state-mgmt/intro
https://flutter.dev/docs/development/data-and-backend/state-mgmt/options

Related

Flutter setState function doesn't work when used to change class member

i have the following codes,
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(widget.text),
);
}
}
This is my custom widget,
class _MainState extends State<Main> {
var n = mWidget(text: "Hi");
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
n,
ElevatedButton(
onPressed: () {
setState(() {
n.text = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
And this is the code in the main.dart file.
The problem is that pressing the button doesn't change the output on the screen unless a hot reload even though I am calling the setState function.
I wonder why is that.
Thanks in advance!
You made a couple of mistakes in this!
In your code, you made a widget named mWidget and created an instance of it, it is not the right approach to access any widget using an instance, as state of instances cannot be updated.
You are using the state of mWidget outside of its scope, where it is not accessible.
You can use keys to achieve what you want. (It is not advisable to use this for large-scale project)
Here is a small code which can help you to achieve the functionality you want.
class mWidget extends StatefulWidget {
mWidget({Key? key, required this.text}) : super(key: key);
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
String text = "";
#override
void initState() {
text = widget.text;
super.initState();
}
void updateValue(String newData) {
setState(() {
text = newData;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(text),
);
}
}
class _Main extends StatefulWidget {
const _Main({Key? key}) : super(key: key);
#override
State<_Main> createState() => _MainState();
}
class _MainState extends State<_Main> {
GlobalKey<_mWidgetState> _mWidgetStateKey = GlobalKey(); // This is the key declaration of _mWidgetState type
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: "Hi", key: _mWidgetStateKey),
ElevatedButton(
onPressed: () =>
_mWidgetStateKey.currentState!.updateValue("Hello"), // Calling the method of _mWidgetState class.
child: Text("Click me"),
),
],
),
);
}
}
You can reinitialize the n on easy approach like
n = mWidget(text: "Hello");
Or use state-management property like riverpod/bloc. callback method may also help. I am using ValueNotifier, you dont need to make theses statefulWidget
class Main extends StatefulWidget {
const Main({super.key});
#override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
final ValueNotifier textNotifier = ValueNotifier('Hi');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: textNotifier),
ElevatedButton(
onPressed: () {
setState(() {
textNotifier.value = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
ValueNotifier text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: ValueListenableBuilder(
valueListenable: widget.text,
builder: (context, value, child) => Text(value),
));
}
}

Get the value of an integer from a stateful widget from another class in flutter

Learning Flutter and I am building a counter that I would like to use for a cart. I have a problem retrieving the integer value of the counter stateful widget I created and i'd like a Text to update itself with the value of the Counter.
Here is the Code for the Counter
import 'package:flutter/material.dart';
class TheCounter extends StatefulWidget {
int counter = 0;
int get counterValue => counter;
#override
_TheCounterState createState() => _TheCounterState();
}
class _TheCounterState extends State<TheCounter> {
void increaseCounter() {
setState(() {
if (widget.counter >= 0) {
widget.counter++;
}
});
}
void decreaseCounter() {
setState(() {
if (widget.counter > 0) {
widget.counter--;
}
});
}
#override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(icon: Icon(Icons.add), onPressed: increaseCounter),
Text('${widget.counter}'),
IconButton(icon: Icon(Icons.remove), onPressed: decreaseCounter)
],
);
}
}
And here is the main.dart file
import 'package:counter/counter.dart';
import 'package:flutter/material.dart';
void main() {
runApp(Count());
}
class Count extends StatelessWidget {
#override
Widget build(BuildContext context) {
int counting = TheCounter().counterValue;
return MaterialApp(
title: 'Counter',
home: Scaffold(
appBar: AppBar(
title: Text('Counter Test'),
),
body: Column(
children: [
TheCounter(),
Text('$counting'),
TheCounter(),
TheCounter(),
],
),
),
);
}
}
I'd like the Text to update itself with the value of the counter whenever the add or remove button is clicked. What do I do to achieve that?
Firstly, we need to make a decision where the update will happen. In this case the Count widget text need to update.
Therefore, need to convert this as StatefulWidget.
Next thing is how we can retrieve counter from TheCounter widget. You can use callback method, or state management package like provider, riverpod, bloc etc. You can check the doc
Here I am using a function that will get update value on Count whenever the count value change on TheCounter widget.
void main() {
runApp(MaterialApp(title: 'Counter', home: Count()));
}
class Count extends StatefulWidget {
const Count({Key? key}) : super(key: key);
#override
State<Count> createState() => _CountState();
}
class _CountState extends State<Count> {
int initNumberOfCounter = 4;
late List<int> countersValue = List.generate(initNumberOfCounter, (index) => 0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter Test'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
countersValue.add(0);
});
},
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: countersValue.length,
itemBuilder: (context, index) {
return Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TheCounter(
initCountValue: countersValue[index],
countValueCallback: (v) {
setState(() {
countersValue[index] = v;
});
},
),
const SizedBox(
width: 40,
),
Text("${countersValue[index]}"),
],
);
},
),
)
],
),
);
}
}
class TheCounter extends StatefulWidget {
final Function(int) countValueCallback;
final int initCountValue;
const TheCounter({
Key? key,
required this.countValueCallback,
this.initCountValue = 0,
}) : super(key: key);
#override
_TheCounterState createState() => _TheCounterState();
}
class _TheCounterState extends State<TheCounter> {
///this use within the current state
late int counter;
#override
void initState() {
super.initState();
///set counter value for the 1st time
counter = widget.initCountValue;
}
void increaseCounter() {
setState(() {
if (counter >= 0) {
counter++;
}
});
/// back to parent widget
widget.countValueCallback(counter);
}
void decreaseCounter() {
setState(() {
if (counter > 0) {
counter--;
}
});
widget.countValueCallback(counter);
}
#override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(icon: Icon(Icons.add), onPressed: increaseCounter),
Text('${counter}'),
IconButton(icon: Icon(Icons.remove), onPressed: decreaseCounter)
],
);
}
}

Force rebuild of a stateful child widget in flutter

Let's suppose that I have a Main screen (stateful widget) where there is a variable count as state. In this Main screen there is a button and another stateful widget (let's call this MyListWidget. MyListWidget initialize it's own widgets in the initState depending by the value of the count variable. Obviously if you change the value of count and call SetState, nothing will happen in MyListWidget because it create the values in the initState. How can I force the rebuilding of MyListWidget?
I know that in this example we can just move what we do in the initState in the build method. But in my real problem I can't move what I do in the initState in the build method.
Here's the complete code example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
});
},
),
),
MyListWidget(count),
],
));
}
}
class MyListWidget extends StatefulWidget {
final int count;
const MyListWidget(this.count, {Key? key}) : super(key: key);
#override
_MyListWidgetState createState() => _MyListWidgetState();
}
class _MyListWidgetState extends State<MyListWidget> {
late List<int> displayList;
#override
void initState() {
super.initState();
displayList = List.generate(widget.count, (int index) => index);
}
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(displayList[index].toString()),
),
itemCount: displayList.length,
),
);
}
}
I don't think the accepted answer is accurate, Flutter will retain the state of MyListWidget because it is of the same type and in the same position in the widget tree as before.
Instead, force a widget rebuild by changing its key:
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
});
},
),
),
MyListWidget(count, key: ValueKey(count)),
],
),
);
}
}
Using a ValueKey in this example means the state will only be recreated if count is actually different.
Alternatively, you can listen to widget changes in State.didUpdateWidget, where you can compare the current this.widget with the passed in oldWidget and update the state if necessary.
USE THIS:
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
MyListWidget myListWidget = MyListWidget(5);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
myListWidget = MyListWidget(count);
});
},
),
),
myListWidget,
],
));
}
}

Flutter switch between fragments by supporting back to previous fragment

in this link in SF, #martinseal1987 show us how can we use separated widgets link with android fragments.
I implemented this solution on my project and after running project i dont have any problem to show first widgets as an Fragment, but when i press to back button my screen goes to black and couldn't back to previous widgets as an fragment
i think that is should be this:
Problem is on navigateBack and customPop methods and i can attach fragment by pressing on button
import 'package:flutter/material.dart';
void main()
{
runApp(MaterialApp(
title: 'AndroidMonks',
home: Scaffold(
appBar: AppBar(
title: Text('Androidmonks'),
backgroundColor: Colors.orangeAccent,
),
body: Home(),
),
));
}
class Home extends StatefulWidget {
Home({
Key key,
}) : super(key: key);
#override
State<Home> createState()=>_Home();
}
class _Home extends State<Home> {
String title = "Title";
int _currentIndex = 0;
final List<int> _backstack = [0];
#override
Widget build(BuildContext context) {
navigateTo(_currentIndex);
//each fragment is just a widget which we pass the navigate function
List<Widget> _fragments =[Fragment1(),Fragment2(),Fragment3()];
//will pop scope catches the back button presses
return WillPopScope(
onWillPop: () {
customPop(context);
},
child: Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child:Text('PRESS'),
onPressed: (){
_currentIndex++;
navigateTo(_currentIndex);
},
),
Expanded(
child: _fragments[_currentIndex],
),
],
),
),
);
}
void navigateTo(int index) {
_backstack.add(index);
setState(() {
_currentIndex = index;
});
_setTitle('$index');
}
void navigateBack(int index) {
setState(() {
_currentIndex = index;
});
_setTitle('$index');
}
customPop(BuildContext context) {
if (_backstack.length - 1 > 0) {
navigateBack(_backstack[_backstack.length - 1]);
} else {
_backstack.removeAt(_backstack.length - 1);
Navigator.pop(context);
}
}
//this method could be called by the navigate and navigate back methods
_setTitle(String appBarTitle) {
setState(() {
title = appBarTitle;
});
}
}
class Fragment2 extends StatefulWidget {
#override
State<Fragment2> createState() => _Fragment2();
}
class _Fragment2 extends State<Fragment2> {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("_Fragment2"),
onPressed: (){
}),
);
}
}
class Fragment1 extends StatefulWidget {
#override
State<Fragment1> createState() => _Fragment1();
}
class _Fragment1 extends State<Fragment1> {
#override
Widget build(BuildContext context) {
return Center(
child: Text("_Fragment1"),
);
}
}
class Fragment3 extends StatefulWidget {
#override
State<Fragment3> createState() => _Fragment3();
}
class _Fragment3 extends State<Fragment3> {
#override
Widget build(BuildContext context) {
return Center(
child: Text("_Fragment3"),
);
}
}
I fixed some logic in your code please carefully check the changes, if you have any question don't hesitate, here is the working code
import 'package:flutter/material.dart';
void main()
{
runApp(MaterialApp(
title: 'AndroidMonks',
home: Scaffold(
appBar: AppBar(
title: Text('Androidmonks'),
backgroundColor: Colors.orangeAccent,
),
body: Home(),
),
));
}
class Home extends StatefulWidget {
Home({
Key key,
}) : super(key: key);
#override
State<Home> createState()=>_Home();
}
class _Home extends State<Home> {
String title = "Title";
List<Widget> _fragments =[Fragment1(),Fragment2(),Fragment3()];
int _currentIndex = 0;
final List<int> _backstack = [0];
#override
Widget build(BuildContext context) {
//navigateTo(_currentIndex);
//each fragment is just a widget which we pass the navigate function
//will pop scope catches the back button presses
return WillPopScope(
onWillPop: () {
return customPop(context);
},
child: Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child:Text('PRESS'),
onPressed: (){
_currentIndex++;
navigateTo(_currentIndex);
},
),
Expanded(
child: _fragments[_currentIndex],
),
],
),
),
);
}
void navigateTo(int index) {
_backstack.add(index);
setState(() {
_currentIndex = index;
});
_setTitle('$index');
}
void navigateBack(int index) {
setState(() {
_currentIndex = index;
});
_setTitle('$index');
}
Future<bool> customPop(BuildContext context) {
print("CustomPop is called");
print("_backstack = $_backstack");
if (_backstack.length > 1) {
_backstack.removeAt(_backstack.length - 1);
navigateBack(_backstack[_backstack.length - 1]);
return Future.value(false);
} else {
return Future.value(true);
}
}
//this method could be called by the navigate and navigate back methods
_setTitle(String appBarTitle) {
setState(() {
title = appBarTitle;
});
}
}
class Fragment2 extends StatefulWidget {
#override
State<Fragment2> createState() => _Fragment2();
}
class _Fragment2 extends State<Fragment2> {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("_Fragment2"),
onPressed: (){
}),
);
}
}
class Fragment1 extends StatefulWidget {
#override
State<Fragment1> createState() => _Fragment1();
}
class _Fragment1 extends State<Fragment1> {
#override
Widget build(BuildContext context) {
return Center(
child: Text("_Fragment1"),
);
}
}
class Fragment3 extends StatefulWidget {
#override
State<Fragment3> createState() => _Fragment3();
}
class _Fragment3 extends State<Fragment3> {
#override
Widget build(BuildContext context) {
return Center(
child: Text("_Fragment3"),
);
}
}
You can achieve this type of navigation using LocalHistoryRoute.of(context).addLocalHistoryEntry and Navigator.pop().

Flutter -How to Pass variable from one dart class to another dart class

I just want to pass my int and bool values into another class in another dart file.
I am trying to pass values the method.
Try this.
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
home: new MainPage(),
));
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => new _MainPageState();
}
class _MainPageState extends State<MainPage> {
int count = 0;
bool isMultiSelectStarted = false;
void onMultiSelectStarted(int count, bool isMultiSelect) {
print('Count: $count isMultiSelectStarted: $isMultiSelect');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.blue,
title: new Text('Home'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Go to SecondPage'),
onPressed: () {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
return new SecondPage(onMultiSelectStarted);
},
),
);
},
),
),
);
}
}
class SecondPage extends StatefulWidget {
int count = 1;
bool isMultiSelectStarted = true;
final Function multiSelect;
SecondPage(this.multiSelect);
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Center(
child: new RaisedButton(
child: new Text('Pass data to MainPage'),
onPressed: () {
widget.multiSelect(widget.count, widget.isMultiSelectStarted);
},
),
);
}
}