I have a List of String stored in a variable like this:
var question = [
'whats your favorite name',
'whats your favorite color',
'whats your favorite shape',
];
And have function that increase a variable each time I call it
void _Press(){
setState((){
_PressedNo = _PressedNo + 1;
});
print(_PressedNo );
}
And in my scaffold iam checking if the _PressedNo is smaller that List so it rebuild the scaffold with the new question each time i press the button till the questions finished like this :
return MaterialApp(
home: Scaffold(
body:_PressedNo < question.length ? Container(
child: RaisedButton(
child: Text('yes'),
onPressed:() {
_Press();
},
),
) : // here i want to go to another page
And after the else : i want to go to another Flutter page, but I can only add widgets here so any solution how to do that..
You'll have to take a bit of a different approach. The build method should only provide things that appear on the screen and not directly trigger a specific action or state change, such as navigating to another page.
You can handle this in the _Press() method:
void _Press() {
if(_PressedNo < questions.length) {
setState(() {
_PressedNo = _PressedNo + 1;
});
} else {
Navigator.of(context).push(...); // navigate to another page
// optionally set the _PressedNo back to 0
setState(() {
_PressedNo = 0;
});
}
}
Your build method should not change, since on this page it should always show the same button.
return MaterialApp(
home: Scaffold(
body: Container(
child: RaisedButton(
child: Text('yes'),
onPressed:() {
_Press();
},
),
),
);
Some other pointers:
use lowerCase for functions and variables: _press() and _pressedNo.
You can directly pass in the function to onPressed like onPressed: _press, (note that there are no parentheses since the function is not called but just passed along)
Related
I am using the favorite_button Widget to add items to the list of favorites.
For that matter, I have a listview and for each row, I added the option to add it as a favorite. I also have a condition in the backend that if the number of favorites is more than 10, the responsecode equals to 2 and then shows a dialogbox in the flutter and does not add it to the favorite.
Everything works perfectly. The only problem that I have is that in conditions with more than 10 favorites, when I click, it marks as favorite and then shows the dialog box but I could not find a way to undo this action. However, it does not add it to the list (when I refresh the page, it shows as unmarked). How could I unmark it from marked as favorite, for example, when user closes the showDialog?
Any other approach is also appreciated like simulating the onpressed action to undo the action of favoriting.
Thank you in advance.
StarButton(
isStarred: UserFavorite
.Users!
valueChanged: (_isStarred) {
if (_isStarred == true)
setState(() async {
var resp =
await add(
widget.oauth,
widget.Id,);
if (resp.responseCode ==
2) {
await showDialog(
context: context,
builder:
(alertDialogContext) {
return AlertDialog(
title: Text(
'Limit Reached'),
content: Text(
'You are allowed to add up to 10 sensors as your favorite.'),
actions: [
TextButton(
child: Text(
'Ok'),
onPressed:
() {
Navigator.pop(
alertDialogContext);
},
),
],
);
},
);
}
});
else
setState(() {
delete(
widget.oauth,
widget.Id,
);
});
},
)
I add a button to add a list of widget in my screen, like this:
List<Widget> timeWidget = [];
buildTime() {
setState(
() {
timeWidget.add(Padding(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
HourScheduleItem(
enabled: true,
day: _day,
onRemove: () {
timeWidget.remove(data);
},
onChanged: (date, hour) {
setState(
() {
_day = date;
_hour = hour;
print(_hour);
},
);
},
)
],
),
));
},
);
}
When a click onTap button buildTime(); the return is a constructor into Column in my screen:
Column(children: timeWidget.map((data) {
return data;
}).toList()),
But when I choose an option in the next widget added, the option chosen is shown only in the first widget, I believe this must be due to the fact that it does not get an index?
enter image description here
This due to how states and widgets in the widget tree are linked together. The flutter YouTube channel has a great in depth explanation of this topic, but TL;DR flutter doesn't "know" which widget in the list you actually clicked on unless you give each widget a unique identifier to tell them apart!
In almost all cases, you can do this by adding a key: UniqueKey() parameter to the constructor of the widget you're putting in a list. In your case, you would pass it in to your HourScheduleItem which would then pass it into the super() constructor.
I have a Menu button that has 3 icons, and I want to hover the icon of the current page.
The code of the widget is:
class MenuButton extends StatelessWidget {
int current;
MenuButton({#required this.current}) {}
#override
Widget build(BuildContext context) {
Widget cirMenu = FabCircularMenu(
children: <Widget>[
IconButton(
icon: Image.asset('img/performance.png'),
onPressed: () {
print('Favorite');
}),
IconButton(
icon: Image.asset('img/shop.png'),
onPressed: () {
print('Home');
}),
IconButton(
icon: Image.asset('img/category.png'),
onPressed: () {
print('Favorite');
})
],
ringDiameter: 230,
ringWidth: 90,
animationCurve: Cubic(1, 1.65, .62, .83),
);
return cirMenu;
}
I would like to hover the image of the current page, but I don't know how to access the Widget attribute. The final functionality should be something like this (though it is not compiling), that is just adding a conditional to change the image:
if (current == 0) {
cirMenu.children[0].Icon = Image.asset('img/performance-hover.png');
}
if (current == 1) {
cirMenu.children[1].Icon = Image.asset('img/shop-hover.png');
}
if (current == 2) {
cirMenu.children[2].Icon = Image.asset('img/category-hover.png');
}
return cirMenu;
How can I accomplish that?
You can't modify properties of a widget once it's built. It's immutable. In other words you need to rebuild it every time that you want to change something about it.
One potential solution would be to modify your code to use proper background on each rebuild.
Image getBackground(int current) {
if (current == 0) {
return Image.asset('img/performance-hover.png');
}
if (current == 1) {
return Image.asset('img/shop-hover.png');
}
if (current == 2) {
return Image.asset('img/category-hover.png');
}
//handle other indices
}
Then depending on your desired outcome you can use this method when constructing MenuButton.
Remember - you don't need (or even can't) modify widget as it's built. The data (i.e. index or image) must flow from top to bottom of the widget tree.
if you just want a message you can use tooltip attribute that is already inside IconButton()
like this
IconButton(
icon: Image.asset('img/performance.png'),
onPressed: () {
print('Favorite');
}, tooltip: 'Favorite')
if you want a color change on hover you can change hoverColor attribute like this
IconButton(
icon: Image.asset('img/performance.png'),
onPressed: () {
print('Favorite');
}, hoverColor: Colors.red)
In my flutter application, I am displaying a dialog with a series of fields controlled by a JSON representation of the form. The function that controls the form widgets is outside of the dialog implementation (so that it is reusable in widgets other than dialogs).
Inspired by the jsonToForm module (https://github.com/VictorRancesCode/json_to_form) my code is as follows:
List<Widget> jsonToForm() {
List<Widget> widgetList = new List<Widget>();
for (var count = 0; count < formItems.length; count++) {
FormItem field = FormItem.fromJson( formItems[count] );
switch( field.type ) {
case FieldType.input:
case FieldType.password:
case FieldType.email:
case FieldType.numeric:
case FieldType.textarea:
widgetList.add( _buildLabel(field.title) );
widgetList.add( _buildTextField(field) );
break;
case FieldType.radiobutton:
widgetList.add( _buildLabel(field.title) );
for (var i = 0; i < field.fields.length; i++) {
widgetList.add( _buildRadioField( field, field.fields[i] ));
}
break;
case FieldType.color:
widgetList.add( _buildColorPicker(field) );
break;
case FieldType.toggle:
widgetList.add( _buildSwitch(field) );
break;
case FieldType.checkbox:
widgetList.add( _buildLabel(field.title) );
for (var i = 0; i < field.fields.length; i++) {
widgetList.add( _buildCheckbox(field, field.fields[i] ) );
}
break;
}
}
return widgetList;
}
When a form value changes, it calls _handleChanged and then passes the form field list to the parent through an event callback.
void _handleChanged( FormItem field, { FieldItem item } ) {
var fieldIndex = formItems.indexWhere( (i) => i['name'] == field.name );
if( fieldIndex != -1 ) {
// If specified, update the subitem
if( item != null ) {
var itemIndex = field.fields.indexWhere( (i) => i.title == item.title );
field.fields.replaceRange(itemIndex, itemIndex+1, [item]);
}
// Now update the state
this.setState(() {
formItems.replaceRange(fieldIndex, fieldIndex+1, [ field.toJson() ]);
});
// Notify parents
widget.onChanged(formItems);
}
}
The problem with this approach (especially for a text field), there is no onComplete event which is only fired after all text has been entered. onChanged as well as the TextEditingController approach fire as each character is typed. Yet, I don't want to have to put the dialog button in the jsonToForm routine because then it becomes no longer reusable in other (non-dialog) screens.
Also I am not fond of the form performing an onChanged callback returning until all fields when only some have been updated. By signaling on each change event, it also ends up re-building on each character that is typed (something I would also like to avoid).
What would be ideal, is to only perform a callback withing jsonToForm when all editing for all fields are complete, but without the button, there is nothing in the jsonToForm module which can signal "I'm done".
var response;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Settings"),
actions: <Widget>[
DialogButton(
child: new Text( 'Save', style: TextStyle(color: Colors.white,fontWeight: FontWeight.w600, fontSize: 16)),
color: Colors.blue,
width: 110,
onPressed: () {
var data = List<Property>();
if( response != null ) {
response.forEach((item) => data.add( Property( name: item['name'], value: item['value'],
type: AppUtils.enumFromString( PropertyType.values, item['type'], PropertyType.string ) )));
}
var lib = this.widget.item.libraryItem;
var logger = AppConfig.newLogger(lib.id, lib.valueType, properties: data);
Navigator.pop(context, logger);
})
],
),
body: SingleChildScrollView(
child: Container(
child: Column(children: <Widget>[
JsonForm(
form: json.encode(this.widget.item.formItems),
onChanged: (dynamic response) {
this.setState(() => this.response = response);
},
),
]),
),
),
);
}
Am I stuck with putting the scaffold/widget build in the jsonToForm and then replicating this widget for screens, sub-widgets, etc or is there a more elegant solution to split the form from the container?
Seems I have discovered what the issue actually is.
The problem with the jsonToForm library that I modeled my code after puts the creation of the form in the Build function rather than in the InitState function. This causes the form to be rebuilt on each change (very bad) and subsequently if you add a TextController to a TextInputField, it goes crazy.
Anyone using the jsonToForm will need to update the code.
I also don't like the fact that it creates an array of widgets instead of using a future builder approach. .... I will slay that dragon on another day....
How does setState actually work?
It seems to not do what I expect it to do when the Widget which should have been rebuilt is built in a builder function. The current issue I have is with a ListView.builder and buttons inside an AlertDialog.
One of the buttons here is an "AutoClean" which will automatically remove certain items from the list show in the dialog.
Note: The objective here is to show a confirmation with a list of "Jobs" which will be submitted. The jobs are marked to show which ones appear to be invalid. The user can go Back to update the parameters, or press "Auto Clean" to remove the ones that are invalid.
The button onTap looks like this:
GeneralButton(
color: Colors.yellow,
label: 'Clear Overdue',
onTap: () {
print('Nr of jobs BEFORE: ${jobQueue.length}');
for (int i = jobQueue.length - 1; i >= 0; i--) {
print('Checking item at $i');
Map task = jobQueue[i];
if (cuttoffTime.isAfter(task['dt'])) {
print('Removing item $i');
setState(() { // NOT WORKING
jobQueue = List<Map<String, dynamic>>.from(jobQueue)
..removeAt(i); // THIS WORKS
});
}
}
print('Nr of jobs AFTER: ${jobQueue.length}');
updateTaskListState(); // NOT WORKING
print('New Task-list state: $taskListState');
},
),
Where jobQueue is used as the source for building the ListView.
updateTaskListState looks like this:
void updateTaskListState() {
DateTime cuttoffTime = DateTime.now().add(Duration(minutes: 10));
if (jobQueue.length == 0) {
setState(() {
taskListState = TaskListState.empty;
});
return;
}
bool allDone = true;
bool foundOverdue = false;
for (Map task in jobQueue) {
if (task['result'] == null) allDone = false;
if (cuttoffTime.isAfter(task['dt'])) foundOverdue = true;
}
if (allDone) {
setState(() {
taskListState = TaskListState.done;
});
return;
}
if (foundOverdue) {
setState(() {
taskListState = TaskListState.needsCleaning;
});
return;
}
setState(() {
taskListState = TaskListState.ready;
});
}
TaskListState is simply an enum used to decide whether the job queue is ready to be submitted.
The "Submit" button should become active once the taskListState is set to TaskListState.ready. The AlertDialog button row uses the taskListState for that like this:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
if (taskListState == TaskListState.ready)
ConfirmButton(
onTap: (isValid && isOnlineNow)
? () {
postAllInstructions().then((_) {
updateTaskListState();
// navigateBack();
});
: null),
From the console output I can see that that is happening but it isn't working. It would appear to be related to the same issue.
I don't seem to have this kind of problem when I have all the widgets built using a simple widget tree inside of build. But in this case I'm not able to update the display of the dialog to show the new list without the removed items.
This post is getting long but the ListView builder, inside the AleryDialog, looks like this:
Flexible(
child: ListView.builder(
itemBuilder: (BuildContext context, int itemIndex) {
DateTime itemTime = jobQueue[itemIndex]['dt'];
bool isPastCutoff = itemTime.isBefore(cuttoffTime);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
userDateFormat.format(itemTime),
style: TextStyle(
color:
isPastCutoff ? Colors.deepOrangeAccent : Colors.blue,
),
),
Icon(
isPastCutoff ? Icons.warning : Icons.cached,
color: isPastCutoff ? Colors.red : Colors.green,
)
],
);
},
itemCount: jobQueue.length,
),
),
But since the Row() with buttons also doesn't react to setState I doubt that the problem lies within the builder function itself.
FWIW all the code, except for a few items like "GeneralButton" which is just a boilerplate widget, resides in the State class for the Screen.
My gut-feeling is that this is related to the fact that jobQueue is not passed to any of the widgets. The builder function refers to jobQueue[itemIndex], where it accesses the jobQueue attribute directly.
I might try to extract the AlertDialog into an external Widget. Doing so will mean that it can only access jobQueue if it is passed to the Widget's constructor....
Since you are writing that this is happening while using a dialog, this might be the cause of your problem:
https://api.flutter.dev/flutter/material/showDialog.html
The setState call inside your dialog therefore won't trigger the desired UI rebuild of the dialog content. As stated in the API a short and easy way to achieve a rebuild in another context would be to use the StatefulBuilder widget:
showDialog(
context: context,
builder: (dialogContext) {
return StatefulBuilder(
builder: (stateContext, setInnerState) {
// return your dialog widget - Rows in ListView in Container
...
// call it directly as part of onTap of a widget of yours or
// pass the setInnerState down to another widgets
setInnerState((){
...
})
}
);
EDIT
There are, as in almost every case in the programming world, various approaches to handle the setInnerState call to update the dialog UI. It highly depends on the general way of how you decided to manage data flow / management and logic separation. As an example I use your GeneralButton widget (assuming it is a StatefulWidget):
class GeneralButton extends StatefulWidget {
// all your parameters
...
// your custom onTap you provide as instantiated
final VoidCallback onTap;
GeneralButton({..., this.onTap});
#override
State<StatefulWidget> createState() => _GeneralButtonState();
}
class _GeneralButtonState extends State<GeneralButton> {
...
#override
Widget build(BuildContext context) {
// can be any widget acting as a button - Container, GestureRecognizer...
return MaterialButton(
...
onTap: {
// your button logic which has either been provided fully
// by the onTap parameter or has some fixed code which is
// being called every time
...
// finally calling the provided onTap function which has the
// setInnerState call!
widget.onTap();
},
);
}
If you have no fixed logic in your GeneralButton widget, you can write: onTap: widget.onTap
This would result in using your GeneralButton as follows:
...
GeneralButton(
...
onTap: {
// the desired actions like provided in your first post
...
// calling setInnerState to trigger the dialog UI rebuild
setInnerState((){});
},
)