Flutter update state coming from another screen - flutter

I have two Screens. In the first one, there is a ListView. In the first element I display the value of the variable selectedObject.
When the ListTile is pressed, a second Screen is opened. I want to update the selectedObject value after returning from the second screen.
I need to assign the value of result to the selectedObject variable.
I think I have to call the setState method but I don't know how.
Here is my code:
class _FilterTaskState extends State<FilterTask> {
List taskList;
String selectedObject = "initial value";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: _buildAppBar(context, "AppBar Title"),
body: Center(
child: new ListView(
children: <Widget>[
new ListTile(
leading: new Icon(Icons.home, color: Colors.black),
title: new Text("Selected Object", style: styleTitle),
subtitle: new Text(selectedObject),
trailing: new Icon(Icons.play_arrow, color: Colors.black),
onTap: () => _navigateToFilterObject(context),
),
...
],
)
),
);
}
}
_navigateToFilterObject(BuildContext context) async {
final result = await Navigator.push(context,
MaterialPageRoute(builder: (context) => FilterObject()),);
/// I want to set the 'selectedObject' value
/// selectedObject = result;
}

On your FilterObject widget returns the value when you select the item like this:
Navigator.of(context).pop(theValueYouWantToReceive);
And you will get the result inside result variable:
final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => FilterObject()),);
Final code
_navigateToFilterObject(BuildContext context) async {
final result = await Navigator.push(context,
MaterialPageRoute(builder: (context) => FilterObject()),);
//refresh the state of your Widget
setState(() {
selectedObject = result;
});
}
_navigateToFilterObject must be inside your _FilterTaskState class
Read about Navigator pop : https://docs.flutter.io/flutter/widgets/Navigator/pop.html

Related

Flutter, set state() from parent widget called by not rendering

I have three screen.
Home screen. 2 Mortgage Screen. 3. New branch Screen. [Each Mortgage can have one or more branches]
The home screen shows a list of all current mortgages a user ended, with a summary of each the branches in each mortgages.
When the user clicks on one of the mortgages in the list in screen 1, he gets to screen 2 which shows all the details of the branches of that mortgage. User can add new branch by clicking floating action button, to get to page 3.
In page 3, the user fills out a form to add a new branch. Once a branch is added, page 3 is popped, and page 2 is still appearing.
When page 3 is done, a new branch is added to the selected mortgage, and it is supposed to update the data displayed in page 2 and in page 1. I have done this by passing callback methods into pages 2 and 1, and then calling set state in both classes.
Page 2 is updated and displays fine. However, when I go back from page 2 to page 1, page 1 has not updated. Even though the setState method is called in page 1.
I hope its clear, I will add the code of page 1, and maybe you can help me see why the page is not rerendering.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<MaslulModel> savedMaslulim = <MaslulModel>[];
List<MortgageModel> savedMortgages = <MortgageModel>[];
// THIS METHOD IS CALLED FROM PAGE 2.
notifyHomeScreen() async {
print('2124: notifyHomeScreen called in home_screen');
savedMaslulim.clear();
savedMortgages.clear();
savedMaslulim = await SharedPrefsMethods.getMaslulListFromPrefs();
for (var i = 0; i < savedMaslulim.length; i++) {
print(savedMaslulim[i].getDetails());
}
savedMortgages = sortOutMaslulimToMortgages(savedMaslulim);
setState(() {
print('2124: Set state. Maslul at 0 List size: ${savedMortgages[0].maslulList.length}');
});
}
TextEditingController _textFieldController = TextEditingController();
String codeDialog = '';
String valueText = '';
#override
initState() {
super.initState();
print('InitState');
asyncGetSavedMortgages();
}
void asyncGetSavedMortgages() async {
savedMaslulim = await SharedPrefsMethods.getMaslulListFromPrefs();
savedMortgages = sortOutMaslulimToMortgages(savedMaslulim);
print(savedMortgages.length);
setState(() {
print('Set state called');
});
}
#override
Widget build(BuildContext context) {
for (var i = 0; i < savedMortgages.length; i++) {
if(savedMortgages[i].name=='tonight'){
print('2124: From HOME: ${savedMortgages[i].maslulList.length}');
}
}
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.translate('my_mortgages'))),
drawer: MainDrawer(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Navigator.pushNamed(context, '/new_mortgage_screen');
_displayTextInputDialog(context);
},
label: Text('הוסף משכנתא'),
icon: Icon(Icons.add),
backgroundColor: Colors.pink,
),
body: ListView.builder(
itemCount: savedMortgages.length,
key: Key(savedMortgages.length.toString()),
itemBuilder: (context, index){
for (var i = 0; i < savedMortgages.length; i++) {
if(savedMortgages[i].name=='tonight'){
print('2124: From HOME itemBuilder: ${savedMortgages[i].maslulList.length}');
}
}
return MortgageSummaryWidget(savedMortgages[index], notifyHomeScreen: notifyHomeScreen );
},
),
);
}
Future<void> _displayTextInputDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('הכנס שם של המשנכתא:'),
content: TextField(
onChanged: (value) {
setState(() {
valueText = value;
});
},
controller: _textFieldController,
decoration: InputDecoration(hintText: "שם"),
),
actions: <Widget>[
FlatButton(
color: Colors.white,
textColor: Colors.red,
child: Text('בטל'),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
),
FlatButton(
color: Colors.blue,
textColor: Colors.white,
child: Text('בצע'),
onPressed: () {
setState(() {
codeDialog = valueText;
if(codeDialog.isEmpty){
showAlertDialog(context, 'שגיאה', 'לא הכנסת שם מסלול');
return;
}
Navigator.pop(context);
// Navigator.pushNamed(context, '/new_mortgage_screen');
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => NewMortgageScreen(notifyParent: notifyHomeScreen, title: codeDialog,)));
// Navigator.pushNamed(
// context,
// '/new_mortgage_screen',
// arguments: {'mortgageName': codeDialog}
// );
});
},
),
],
);
});
}
}
All the values are updated, but the screen display isn't.
I cannot figure this out. Thanks
I realised the problem, I was sending a parameter into the State, and this wan't getting updated. I changed it to get the parameter by using widget.parameter.

Alert Dialog not shown on button press

Class for Alert Dialog
class AlertWindow extends StatelessWidget {
final String title;
const AlertWindow({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Builder(
builder:(BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}
}
Its been called in a aysnc function like this
Future<ParseUser> SignUP(username, pass, email) async {
var user = ParseUser(username, pass, email); // You can add columns to user object adding "..set(key,value)"
var result = await user.create();
if (result.success) {
setState(() {
_parseUser = user; // Keep the user
});
print(user.objectId);
new AlertWindow(
title: "Signup success " + user.objectId,
);
} else {
print(result.error.message);
new AlertWindow(
title: "Signup error " + result.error.message,
);
}
}
on running this, I can see the print statements in the console, but the AlertWindow doesn't show up.
I've got a hunch that its probably got something to do with the parent BuildContext not been passed to the AlertDialog when I'm creating it.
Try to use function instead of using a widget
Create a new function which return a future
Future<dynamic> _showDialog(BuildContext context){
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}
You need to call the function showDialog for the AlertDialog to appear:
class AlertWindow {
final String title;
final BuildContext context;
const AlertWindow({Key key, this.title, this.context});
void widget(){
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}
}
Working example:
https://dartpad.dev/051f787e1737de84609a390d31c36ee0
https://api.flutter.dev/flutter/material/showDialog.html

How can I use "showDialog" in order to propagate data backwards in Flutter?

Future<bool> show(BuildContext context) async {
return Platform.isIOS
? await showCupertinoDialog<bool>
(context: context, builder: (context)=>this)
:await showDialog<bool>(
context: context,
builder: (context) => this,
);
}
Can anyone help me to understand the term 'this',what does 'this' refer to and how does showDialog works that it returns Future.I tried to read documentation but still couldn't understand it?Is it the same as AlertDialog widget?
well, it's pretty much what the documentation said, it shows a material dialog above the current content of your app, as for this it passes the current widget as child for the dialog, as for the returned value is just like normal page navigation that when you call pop(context, {value}) method you can also return a value, so that value that inside pop will be returned from the dialog.
here is an example below:
class DialogTest extends StatefulWidget {
#override
_DialogTestState createState() => _DialogTestState();
}
class _DialogTestState extends State<DialogTest> {
// the value that will be typed to the dialog
String dialogText;
// the value that will be returned from the dialog
String returnedFromDialog;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: Center(
child:
Text('You got this value from the dialog => $returnedFromDialog'),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
returnedFromDialog = await showDialog<String>(
context: context,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
onChanged: (value) => dialogText = value,
),
FlatButton(
onPressed: () {
setState(() => Navigator.pop(context, dialogText));
},
child: Text(
'Close dialog',
style: TextStyle(color: Colors.red),
),
)
],
),
);
});
},
child: Icon(Icons.open_in_browser),
),
);
}
}

How to use dynamic global list in flutter

I am new to Flutter and attempting sample mutual fund app to cover all basic widgets.
Requirement -> After selecting MF scheme, when user confirms on "buyNow" screen, corresponding scheme should get added to global dynamic list in "Cart" screen. This is basically a cart which is accessible to user on any screen, similar to shopping cart. I want to update cart list on "buyNow" screen and display same on "Cart" screen.
I have followed link to learn about 'provider' method of flutter to solve this, but not able to do.
PFB code
Main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MaterialApp(
home: Schemelist(),
routes: {
'/landing': (context) => Landing(),
'/schemelist': (context) => Schemelist(),
'/schemeBuy': (context) => SchemeBuy(),
'/buyNow': (context) => BuyNow(),
'/cart': (context) => Cart(),
},
),
),
);
}
Cartmodel.dart
import 'package:flutter/foundation.dart';
class CartModel with ChangeNotifier{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
List<CartModel> _cartList=[];
CartModel({this.amount,this.fromDate,this.schemeName,this.toDate,this.type});
void addToCart(CartModel cartObj){
_cartList.add(cartObj);
notifyListeners();
}
double get totalAmount =>
_cartList.fold(0, (total, current) => total + current.amount);
}
BuyNow.dart
RaisedButton(
onPressed: () {
_cart=new CartModel(amount:1000,fromDate:_dateTime.toString(),schemeName:widget.investmentObj.schemeName,toDate:_dateTime1.toString(),type:'SIP');
var cart = Provider.of<CartModel>(context);
cart.addToCart(_cart);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),
Cart.dart //where I will display dynamic list
Widget build(BuildContext context) {
var cart = Provider.of<CartModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: --not able to access list--
itemBuilder: (context, index) => ListTile(
title: Text(
-------
),
),
),
);
}
First we should modify CartModel class. The fields (such as schemeName) should belong to the CartItem class, and the CartModel should only do its own thing (addToCart and others).
class CartModel with ChangeNotifier {
List<CartItem> _itemList = [];
// An unmodifiable view of the items in the cart.
UnmodifiableListView<CartItem> get itemList => UnmodifiableListView(_itemList);
void addToCart(CartItem item) {
_itemList.add(item);
notifyListeners();
}
double get totalAmount => _itemList.fold(0, (total, current) => total + current.amount);
}
class CartItem{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
CartItem({this.amount, this.fromDate, this.schemeName, this.toDate, this.type});
}
Then, in Cart.dart
Widget build(BuildContext context) {
var itemList = Provider.of<CartModel>(context).itemList;
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: itemList.length,
itemBuilder: (_, index) {
var item = itemList[index];
return Text(item.schemeName);
},
),
);
}
You will get a error while click RaisedButton:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix it, edit BuyNow.dart:
RaisedButton(
onPressed: () {
var _item = CartItem(amount: 1000, fromDate: _dateTime.toString(), schemeName: widget.investmentObj.schemeName, toDate: _dateTime1.toString(), type: 'SIP');
//just set listen to false
var cart = Provider.of<CartModel>(context, listen: false);
cart.addToCart(_item);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),

How to get the number of routes in Navigator's stack

Is there a way to know if the current page is the last page in the Navigator Stack and calling Navigator.pop() at this point will close the app?
You can use this code to check if the route is the first :
ModalRoute.of(context).isFirst
so the full code will be
if(! ModalRoute.of(context).isFirst)
Navigator.pop();
It doesn't close the app it destroys the last route shows a black screen.
you can close the app using this: Flutter how to programmatically exit the app
and you can't access the stack or history because it's private in Navigator class Navigator._history but you can use this workaround to check if the current route is the last one or not:
Future<bool> isCurrentRouteFirst(BuildContext context) {
var completer = new Completer<bool>();
Navigator.popUntil(context, (route) {
completer.complete(route.isFirst);
return true;
});
return completer.future;
}
I found this in the source of the AppBar widget.
final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
final bool canPop = parentRoute?.canPop ?? false;
When canPop is false, you are on the root screen.
If you just want to handle something before the application exits. Like showing an confirm dialog you could use WillPopScope.
Example
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _showDialog,
child: Scaffold(
body: Center(
child: Text("This is the first page"),
),
),
);
}
Future<bool> _showDialog() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Are you sure?"),
content: Text("You want to exit app"),
actions: <Widget>[
FlatButton(
child: Text("Yes"),
onPressed: () => Navigator.of(context).pop(true),
),
FlatButton(
child: Text("No"),
onPressed: () => Navigator.of(context).pop(false),
)
],
);
}) ?? false;
}