Change Variable values in Flutter - dart

I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.

The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.

Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.

Related

How to sort data from api?

There are two dropdown button with the list of countries and types of sport. If on them somsething is chosen it is need to show listTile with the leagues on it is chosen to the country and/or sport and if on them nothing is chosen - show all leagues.
But I get:
Dart Error: Unhandled exception:
setState () called after dispose (): _SportLigPageState # b5830 (lifecycle state: defunct, not mounted)
This is what happens if you see the widget tree (e.g.). This error can occur when a call is made. Dispose () callback. It is necessary to ensure that the object is still in the tree.
This can be a memory card if it’s not. To avoid memory leaks, consider dispose ().
Api with leagues: https://www.thesportsdb.com/api/v1/json/1/all_leagues.php:
class LigList extends StatefulWidget {
#override
_LigListState createState() => _LigListState();
}
class _LigListState extends State<LigList> {
String sport;
String country;
List data;
Future<String> getJsonData() async {
http.Response response;
if (sport != null) {
if (country != null) response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport&s=$country'), headers: {"Accept": "application/json"});
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport'), headers: {"Accept": "application/json"});}
else if (country == null){ response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php'), headers: {"Accept": "application/json"});}
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$country'), headers: {"Accept": "application/json"});
var convertDatatoJson = json.decode(response.body);
data = convertDatatoJson['leagues'];
return "Success";
}
static const menuItems = countriesList;
final List<DropdownMenuItem<String>> _dropDownItems = menuItems
.map((String CountruValue) =>
DropdownMenuItem<String>(
value: CountruValue,
child: Text(CountruValue),
),
).toList();
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(children: <Widget>[
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {});
},
);}),
SizedBox(width: 5),
FutureBuilder(
future: _getSports(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? DropdownButton(
value: sport,
hint: Text("Choose a sport league of which you want to find"),
items: snapshot.data,
onChanged: (value) {
sport = value;
print(sport);
setState(() {});
},
)
: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: CircularProgressIndicator());
}),
Flexible(
child:FutureBuilder(
future: getJsonData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return ListView.separated(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int i) {
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment
.stretch,
children: <Widget>[
ListTile(
title: Text(data[i]['strLeague']),
subtitle: Text(
data[i]['strSport']),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (
BuildContext context) =>
new ComandListScreen()
// (data[i])
));
},
),
]
)
)
);
});
}))
]),
),
);
}
}
Any assistance is very much appreciated.
There's a lot of things wrong with your code. The first child in your code is wrapped in a FutureBuilder but you're not using any Future functionality.
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {}); // Remove this line
},
);}),
In addition to that you also are calling setState() randomly in your onChanged callback with nothing inside of it. I'd suggest you take that widget out of the FutureBuilder and just use the DropdownButton on it's own.
Then also in this line
itemCount: data == null ? 0 : data.length,
You're using data, which is set in the future that you call there. You might want to read up on how to properly use the FutureBuilder widget. Just return the data object from your _getJsonData() Future because it's always returning "Success" anyway. Return the list you want from the Future and then access it using snapshot.data
And lastly there's literally only one setState call in there so remove it and you'll be fine. My assumption is that there's some additional dispose you're calling or navigating away and the app crashes. Will need a lot more info to figure out, but you'll have to fix the way you use Futures and the Future builder so we can ensure it's not because of latent threads coming back and setting the state once you've left the view you were on.

Need to close a Modal Bottom Sheet that contains a SimpleDialog flutter

I need a way to close a Modal Bottom Sheet when an action is performed on the SimpleDialog, I have something like this:
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
child: this._getCurrentUserPlaceStatus());
},
),
new ListTile(
leading: new Icon(Icons.share),
title: new Text(Language.of(context).share),
onTap: () {
Share.share(Utility.format(
Language.of(context).placeInvitation,
[_place.title, 'GooglePlay']));
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
//Here are an API call to get some data, we will name this variable as data
var data = getAPIData();
if (data == null) return null; //Also here I need a way to not show the modal and close the modal bottom sheet
return SimpleDialog(
title: Text(data['getCurrentUserPlaceStatus']['status'] == 2
? 'You are going to '
: 'You are in ' +
data['getCurrentUserPlaceStatus']['place']['name']),
children: <Widget>[
FlatButton(
child: Text(Language.of(context).no),
onPressed: () {
Navigator.pop(context, DialogActions.cancel);
}),
FlatButton(
child: Text(Language.of(context).yes),
onPressed: () {
Navigator.pop(context, DialogActions.agree);
})
],
);
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) {
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
//Here I need to close the Modal Bottom
}
});
}
I need to close the modal bottom when an action is performed in the Simple Dialog, but also, when the return is null, I need to not display a simple modal(I mean just ignore the action) and close the modal bottom sheet.
I will appreciate any feedback.
Solution is just set Navigator.pop(context, DialogActions.cancel); in else case, and return Container();
And into showUserStatusDialog, into the then, use this Navigator.pop(context);

Flutter - Why slider doesn't update in AlertDialog?

I doing a AlertDialog, so when I tried to insert Slider widget inside the state of value sound realy stranger, and this doesn't happens if Slider is outside of AlertDialog
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
The complete widget code of AlertDialog
Future<Null> _showDialog() async {
await showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: const Text('Criar novo cartão'),
actions: <Widget>[
new FlatButton(onPressed: () {
Navigator.of(context).pop(null);
}, child: new Text('Hello'))
],
content: new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('Deseja iniciar um novo cartão com quantos pedidos ja marcados?'),
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
],
),
),
);
}
);
}
and everything is under State class of StatefullWidget.
Its look like doesn't update the value and when try to change the value keep in same position.
Update 1
The problem is there are 2 required parameters in Slider (onChanged, value), So I shoud update this or UI keep quite, see the video how the aplication is running
Video on Youtube
Update 2
I've also opened a issue to get help with this at Github repository, if someone wants to get more information can go to issue #19323
The problem is that it's not your dialog that holds the state. It's the widget that called showDialog. Same goes for when you call setState, you are calling in on the dialog creator.
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
Instead, you should make your dialog stateful. Hold the data inside that dialog. And then use Navigator.pop(context, sliderValue) to send the slider value back to the dialog creator.
The equivalent in your dialog would be
FlatButton(
onPressed: () => Navigator.of(context).pop(sliderValue),
child: Text("Hello"),
)
Which you can then catch inside the showDialog result :
final sliderValue = await showDialog<double>(
context: context,
builder: (context) => MyDialog(),
)
I've come up with the same issue with a checkbox and that's my solution, even if it's not the best approach. (see the comment in the code)
Future<Null>_showDialog() async {
return showDialog < Null > (
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("title"),
content: Container(
height: 150.0,
child: Checkbox(
value: globalSearch,
onChanged: (bool b) {
print(b);
globalSearch = b;
Navigator.of(context).pop(); // here I pop to avoid multiple Dialogs
_showDialog(); //here i call the same function
},
)),
);
},
);
}
Easiest and least amount of lines:
Use StatefulBuilder as top widget of Content in the AlertDialog.
StatefulBuilder(
builder: (context, state) => CupertinoSlider(
value: brightness,
onChanged: (val) {
state(() {
brightness = val;
});
},
),
));
I had similar issue and resolved by putting everything under AlertDialog in to a StatefullWidget.
class <your dialog widget> extends StatefulWidget {
#override
_FilterDialogState createState() => _FilterDialogState();
}
class _<your dialog widget> extends State<FilterDialog> {
#override
Widget build(BuildContext context) {
return AlertDialog(
//your alert dialog content here
);
}
}
create a statefull class with the slider at the return time and the double value should declare inside the statefull class thus the setstate func will work.
here is an example i done this for my slider popup its same for alert dialog use can declare the variable as global thus it can be accessed by other classes
class _PopupMenuState extends State<PopupMenu> {
double _fontSize=15.0;
#override
Widget build(BuildContext context) {
return Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
onChanged: (value) {
setState(() {
print(value);
_fontSize = value;
});
},
),
);
}
}

flutter stepper widget - validating fields in individual steps

i am using stepper widget in order to collect info from user and validate it, i need to call an API at each step hence validate each field in a step at every continue button ... i am using form state and form widget but the issue is that it validates entire fields in all steps in stepper... how can i validate only individual step in a stepper? i went through the documentation in Stepper and State classes in stepper.dart but there is no supporting function there
following is the code
class SubmitPayment extends StatefulWidget {
SubmitPayment({Key key, this.identifier, this.amount, this.onResendPressed})
: super(key: key);
final String identifier;
final String amount;
final VoidCallback onResendPressed;
#override
State<StatefulWidget> createState() {
return _SubmitPaymentState();
}
}
class _SubmitPaymentState extends State<SubmitPayment> {
final GlobalKey<FormState> _formKeyOtp = GlobalKey<FormState>();
final FocusNode _otpFocusNode = FocusNode();
final TextEditingController _otpController = TextEditingController();
bool _isOTPRequired = false;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Form(
key: _formKeyOtp,
child: Column(children: <Widget>[
Center(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
child: Text(
Translations.of(context).helpLabelOTP,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontStyle: FontStyle.italic),
))),
CustomTextField(
icon: Icons.vpn_key,
focusNode: _otpFocusNode,
hintText: Translations.of(context).otp,
labelText: Translations.of(context).otp,
controller: _otpController,
keyboardType: TextInputType.number,
hasError: _isOTPRequired,
validator: (String t) => _validateOTP(t),
maxLength: AppConstants.otpLength,
obscureText: true,
),
Center(
child: ButtonBar(
mainAxisSize: MainAxisSize.max,
alignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text(Translations.of(context).resendOtpButton),
color: Colors.white,
textColor: Theme.of(context).primaryColor,
onPressed: widget.onResendPressed,
),
RaisedButton(
child: Text(
Translations.of(context).payButton,
),
onPressed: _doPullPayment,
),
],
)),
])),
);
}
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return "";
}
bool _validateOtpForm() {
_formKeyOtp.currentState.save();
return this._formKeyOtp.currentState.validate();
}
Future<void> _doPullPayment() async {
setState(() {
_isOTPRequired = false;
});
if (!_validateOtpForm()) return false;
try {
setState(() {
_isOTPRequired = false;
});
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(),
title: Text(Translations.of(context).processingPaymentDialog),
),
),
);
TransactionApi api =
TransactionApi(httpDataSource, authenticator.sessionToken);
String responseMessage = await api.doPullPayment(
widget.identifier,
widget.amount,
_otpController.text,
TransactionConstants.transactionCurrency);
Navigator.of(context).pop();
await showAlertDialog(
context, Translations.of(context).pullPayment, '$responseMessage');
Navigator.pop(context);
} catch (exception) {
await showAlertDialog(context, Translations.of(context).pullPayment,
'${exception.message}');
Navigator.of(context).pop();
}
}
One approach is to use a separate Form for each step.
To handle that, use a list of GlobalKey<FormState> which you can index based on _currentStep, then call validate() in onStepContinue:
List<GlobalKey<FormState>> _formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), …];
…
Stepper(
currentStep: _currentStep,
onStepContinue: () {
setState(() {
if (_formKeys[_currentStep].currentState?.validate()) {
_currentStep++;
}
});
},
steps:
Step(
child: Form(key: _formKeys[0], child: …),
This implies the following:
Since you're calling an API at the end, you need to check if you're validating the last step, and save instead of just validating;
You probably want to factor our the Forms to several widgets. If you do so, do not confuse the key parameter that every Widget has. Pass the formKey as an unnamed parameter to avoid confusion.
So i solved this as follows:
The problem was that i was returning an *empty string ("") * if the my logic was valid, where as validate method of FormState expects each validator method, associated with TextFormField to return null if validation is passed.
i changed following
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return "";
}
to
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return null;
}
and it worked all fine then.
Refer to this link for details
"If there is an error with the information the user has provided, the validator function must return a String containing an error message. If there are no errors, the function should not return anything."
It's been long since this question was asked. I hope my answer can help. To do this, I created a List<GlobalKey> then in the onContinue of the Stepper I did something as
final List<GlobalKey<FormState>> _formKeys = [
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>()
]; continued() {
if(_formKeys[_currentStep].currentState!.validate()) {
switch(_currentStep){
case 0:
setSender();
break;
case 1:
setReceiver();
break;
}
}
}

State not updating

I'm trying to update a widget when I got some data from my database. The widget that I'm trying to change is defined as a class variable:
Widget openFriendRequestNotificationWidget = new Container();
I'm using an empty container because I really don't need to render anything at the beginning and leaving it at null is no option.
I've got two functions, one to create my page and the other one the update my openFriendRequestNotificationWidget:
Widget createFriendsPage() {
if (currentUser.friends == null) {
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
openFriendRequestNotificationWidget,
new Material(
child: new InkWell(
child: new Center(
child: new Text("Woops, looks like you have no friends yet.\nTap here to find some!", textAlign: TextAlign.center,),
),
onTap: () => createFriendsDialog(),
)
)
],
);
}
return new Column(
children: <Widget>[
openFriendRequestNotificationWidget,
new Text("ok")
],
);
}
void createReceivedFriendRequestsNotification() {
FirebaseDatabase.instance.reference().child("friend_requests").child(currentUser.uid).once().then((DataSnapshot snap) {
Map<String, Map<String, String>> response = snap.value;
if (response != null) {
this.setState(() {
print("Changing widget");
openFriendRequestNotificationWidget = new Container(
child: new Text("You've got ${response.length.toString()} new friend requests!"),
color: Colors.black,
);
});
}
});
}
The variable is updating in createReceivedFriendRequestsNotification but it is not re-rendering.
Could someone help out?
if you are calling createFriendsPage in initState(), then it means that the code inside initState() is called only once, which is to build the UI.
If it's possible, I suggest that you call your createFriendsPage inside the override method build()
class FriendPage extends StatefullWidget{
//instantiate your state .. }
class FriendsPageState extends State<FriendPage> {
#override
Widget build(Build context) {
return cteateFriendsPage();
}
//other methods here ...
}

Resources