How to change state of page builded with Navigator - flutter

i tried to change the state of Page2 builded from HomePage with Navigator and MaterialPageRoute but the state didn't change. it seems that i can't change the state of another page, is there any solution to change the state of th Page2 from HomePage?
NB: i changed the page before the delay done.
HomePage :
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var text = 'pp';
#override
void initState () {
super.initState();
_changeStateAfterDelay();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text("ggg"),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _showPages,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
void _showPages() {
Navigator.of(context).push(MaterialPageRoute(
maintainState: false,
builder: (context) => Page2(title: text)
));
}
void _changeStateAfterDelay() {
Future.delayed(const Duration(milliseconds: 10000), () {
setState(() {
text += "tt";
});
});
}
}
Page2:
class Page2 extends StatefulWidget {
final String title;
const Page2({
Key? key,
required this.title,
}) : super(key: key);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(widget.title)
),
);
}
}

One possible way to change the state of Page2 from Page1 (in your case HomePage), it is to use a state manager like Provider.
If you want to change the state of Page1 from Page2, you can use a Callback set to Page2 as a parameter.

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),
));
}
}

Show Snackbar on top route after closing a page

In my app, I have some areas where I can open a new page on top of the current, that allow to edit data. Once editing is done, I want to close the page (i.e. via Navigator.pop(context);), and also show a Snackbar after closing (i.e. via ScaffoldMessenger.of(context).showSnackBar('X has been saved')). I am using a ScaffoldMessenger for that.
However, if after closing the edit-page only the top-route remains, the Snackbar will not be shown. If I open any other page fast enough, it will be shown there for the remaining time though. So it was triggered, it is just not shown on the top-route. Also, if I open the edit-page not from the top-route, but from any other page that was already opened on top, the Snackbar will show normally after closing the edit-page.
If I open a Snackbar directly on the top-route, it also works fine. So instead of opening the Snackbar from the edit-page, I could technically return the message and then trigger the Snackbar. But I would prefer not to to pass data around and call functionality at several places, but just call the method at one place (where it belongs).
I can reproduce this behaviour on a newly created App, just need to replace the _MyHomePageState with the following code. What am I doing wrong here?
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<ScaffoldMessengerState> _globalScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: _globalScaffoldMessengerKey,
child: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push<bool>(context, MaterialPageRoute(builder: (context) => const SubPage()));
},
child: const Text("Open Subpage"),
),
),
),
);
}
}
class SubPage extends StatelessWidget {
const SubPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Hello Snackbar')));
},
child: const Text("Close Subpage"),
),
),
);
}
}
Remove the scaffold Messenger widget from the first page
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => const SubPage()));
},
child: const Text("Open Subpage"),
),
),
);
}
}
class SubPage extends StatelessWidget {
const SubPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Hello Snackbar')));
},
child: const Text("Close Subpage"),
),
),
);
}
}
I checked this code and it shows snackbar in the page that exists after popping the subpage

Flutter - Update child state from parent

I would like to update a child's state when a button is clicked in the parent, so for example:
class Parent extends StatelessWidget{
Widget build(context){
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () => //somehow increment the child's counter,
icon: const Icon(Icons.add),
),
],
),
body: const Child(),
);
}
}
class Child extends StatefulWidget {
const Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
...
int counter = 0; //to be incremented when parent's button is clicked on.
...
}
Is there a common way to implement this? From the other posts I've read, people usually use the child to update the parent's state via callback, so if there is a way to refactor my code to acheive the same effect, that would help too.
You can create the field counter in the parent and pass it down to the child widget and update the child widget from the parent.
You can check the demo that I made here..
DartPad Demo Link
statemanagement Method
You can use provider,bloc,cubit,getx... package to update the child and parent value
setstate callback (here i mention)
Change you widget like this .your parent widget to stateful.
int counter = 0;
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
SampleCod Dartpad live code check here
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
int counter = 0;
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
}
Try this:
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 const MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
const Parent({Key? key}) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
tooltip: "Increment counter",
onPressed: incrementCounter,
icon: const Icon(
Icons.add,
),
),
],
),
body: Child(
counter: counter,
),
);
}
}
class Child extends StatefulWidget {
const Child({
Key? key,
required this.counter,
}) : super(key: key);
final int counter;
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
widget.counter.toString(),
style: const TextStyle(
fontSize: 30,
),
),
);
}
}

Flutter NullSafety cannot push Page after showing and change modalProgressHUD

I'm using Provider package to expose a simple boolean variabile that allow to change the status of variable "inAsyncCall" of the ModalProgressHUD widget.
When i try to do somethings before navigate to another page, and i want to display the circlular progress indicator during that computation, when the Future terminated, the current widget has been disposed and i cannot use Navigator.push():
Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
this is my Provider with ChangeNotifier class:
class CartProvider with ChangeNotifier {
bool _inAsync = false;
bool get inAsync => _inAsync;
void setInAsync(bool flag) {
this._inAsync = flag;
notifyListeners();
}
}
I inject the provider before the MaterialApp widget like this:
void main() async {
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => CartProvider(),
)
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
And this is the simple home page where i access via context the provider injected:
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final cartProvider = context.watch<CartProvider>();
return ModalProgressHUD(
inAsyncCall: cartProvider.inAsync,
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('HOME'),
FirstStatefulWidget(),
],
),
),
bottomNavigationBar: SecondStatefulWidget(),
),
);
}
}
class FirstStatefulWidget extends StatefulWidget {
FirstStatefulWidget({Key? key}) : super(key: key);
#override
_FirstStatefulWidgetState createState() => _FirstStatefulWidgetState();
}
class _FirstStatefulWidgetState extends State<FirstStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
Future doSomething() async {
cartProvider.setInAsync(true);
await Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
}
#override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('do call'),
onPressed: doSomething,
),
);
}
}
class SecondStatefulWidget extends StatefulWidget {
SecondStatefulWidget({Key? key}) : super(key: key);
#override
_SecondStatefulWidgetState createState() => _SecondStatefulWidgetState();
}
class _SecondStatefulWidgetState extends State<SecondStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
void goToAnotherPageAfterCall() async {
try {
cartProvider.setInAsync(true);
Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
} on Exception catch (e) {
cartProvider.setInAsync(false);
}
}
#override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('goToAnotherPage'),
onPressed: goToAnotherPageAfterCall,
),
);
}
}

how to change "text" to "text field" when clicking a button in Flutter

There is a regular "Text" and it changes to "TextField" when I click the button I would like to know how to do this
IconButton(
icon: Icon(
Icons.edit,
color: Color(0xFF8D8D8D),
),
onPressed: null),
try this
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isTextFild = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
isTextFild ? TextField() : Text('some text'),
FlatButton(
child: Text('Show Text Field'),
onPressed: () {
setState(() {
isTextFild = true;
});
},
)
],
),
),
);
}
}
This should be enough, a TextFormField that toggles between readOnly and not. This way, you'll ensure that your view doesn't "jump" by switching widgets and you'll be always using the same object, only toggling properties.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool _isEditing = false;
void _edit() {
setState(() => _isEditing = true);
}
#override
Widget build(BuildContext context) {
return TextFormField(
readOnly: _isEditing,
);
}
}
You can do something like this:
class Sample extends StatefulWidget {
#override
_SampleState createState() => _SampleState();
}
class _SampleState extends State<Sample> {
bool _editMode = false;
#override
Widget build(BuildContext context) {
return Row(children: <Widget>[
_editMode ? TextField(...) : Text(...),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
_editMode = !_editMode;
});
})
]);
}
}
Note: If you want to set an initial value for textfield you should use textformfield instead.