Flutter: How to change button depending on state? - flutter

I'm trying to do the following: I have a container, and a button inside it and upon clicking on the button (onPressed) I would like to display a different button (aka. change the widget).
Would appreciate some help in doing so, thanks in advance!!

define the initial value for example
double paddingLeft = 10.0;
And after clicking on that button update the state with actual value Like
setState(() {
paddingLeft = 20.0
});

There might be different ways. One of the methods is:
bool _val = true;
Container(
child: _val ? button1( // when _val is true button1 will be the child of container. So it will be visible.
onPressed: () {
_val = !_val;
setState(() {
});
})
: button2(), // when _val is false button2 will be the child of container. So it will be visible.
);

First of all, you need to make sure that the class you are in extends a StatefulWidget, because that means that the class will rerender when a state changes, which is what you need to be able to show another button.
Then you can define a state bool _showButton1 = true;
In your Container you can then have something like this:
Container(
child: _showButton1 ? _button1() : _button2();
)
(Meaning: if _showButton1 is true, show the widget _button1, else show _button2)
where _button1 would look something like this:
Widget _button1() {
return Button(
onPressed: () => setState(() {
_showButton1 = false;
})
);
}
(Meaning: when this button is pressed, update the state _showButton1 to false and rerender this class --> button 2 will be shown)

Related

flutter ListView.builder make onPressed works for only the concern item

I have a list of cards inside a ListView.builder and each card has a favorite IconButton which change it's color when clicked, but whenever i click on it all the favorite icon change their color too, i wanted to work on the concern item.
Thanks.
bool isPressed = false;
.
.
.
onPressed: () {
setState(() {
isPressed = true;
});
}
In your item you add a field isFavorite as bool type. You change the value of isFavorite and handle color based on isFavorite.
onPressed: (value) {
setState(() {
productItem[index].isFavorite = value;
});
}
For the color part you will check:
color: productItem[index].isFavorite?Colors.pinkAccent: Colors.grey,

How to display time on buttons in Flutter

Requirement:
I have 2 buttons, I want when I click on button 1 its text should be replaced by the current time, and the same for button 2.
Problem:
The problem is when I click on button 1 its text change to the current time, but when I click on button 2 after a few minutes its text changes to the same time that button 1 has.
Here is my code:
void initState() {
// TODO: implement initState
super.initState();
_getTime();
}
void _getTime() {
final String formattedDateTime =
DateFormat('kk:mm:ss').format(DateTime.now()).toString();
setState(() {
getTime = formattedDateTime;
print(getTime[0]);
});
}
var timeInText="Time in";
var timeOutText="Time out";
\\button 1
RoundedButton(icon: Icon(Icons.timer,color: Colors.white),
text:timeInText,
bgcolor: Colors.blue[500],
press:(){
setState(() {
timeInText=getTime;
});
})
//button 2
RoundedButton(icon: Icon(Icons.timer,color: Colors.white),
text:timeoutText,
bgcolor: Colors.blue[500],
press:(){
setState(() {
timeoutText=getTime;
});
})
please help to fix it. thanks
You call _getTime() only in initState. Time is stored on initialization and never updated after that. Since it's never updated it's showing the same time constantly.
To fix that add a _getTime() call to both of your onPressed functions like this:
onPressed: () {
_getTime();
setState(() {
timeoutText = getTime;
});
}),
You get the time one, when the button clicked. You have to write your method codes in setState too i think. And get the current time 🤔 i hope it will help

How to display a floating action button on long pressing a listview in flutter

I am creating an app that should contain a list on long pressing a list item the floating action button should be displayed on bottom of screen
Wrap you list item with GestureDetector it has a property called onLongPress .You can keep a bool value set to false and onLongPress set the bool to true and show the floating action button.
#Edit1
declare a bool,
bool showFab = false;
your FAB
floatingActionButton: showFab ? FloatingActionButton(...) : SizedBox(),
Your listtile
GestureDetector(
onLongPress: () {
setState((){
showFab = true;
})
},
child: YourListWidget(),
)

How do I interact with UI and async functions by a button press?

So I have a login button that would initiate an async function logMeIn() for logging in, which returns a Future<bool> indicating if logging in was successful.
What I want to achieve is after pressing the button, the text within the button would change into a CircularProgressIndicator, and become plain text when logMeIn() has finished.
I know that I could use a FutureBuilder for one-time async tasks, but I simply can't think of a way to use it here.
How am I supposed to achieve this?
Thanks in advance.
Edit
I didn't provide more information because I'm pretty sure it wasn't how it's supposed to be done. But I'd share what I tried whatsoever.
My original button looks somewhat like this:
RaisedButton(
child: Text("Login")
)
I have a async login function that returns a Future<bool> which indicates if the login is successful or not.
Future<bool> logMeIn() async {
// login tasks here
}
What I want to do is to change the button to the following and run logMeIn() when I press the button, and change it back to the plain text version after it's finished:
RaisedButton(
child: CircularProgressIndicator()
onPressed: () {}
)
What I tried
I tried adding a logging_in boolean as a flag to control it, here I call the button with plain text StaticButton(), the other named ActiveButton:
Then I wrapped the ActiveButton with a FutureBuilder:
logging_in ? FutureBuilder(
future: logMeIn(),
builder: (_, snapshot) {
if(snapshot.hasData)
setState(() {
logging_in = false;
});
else
return ActiveButton();
}
) : StaticButton();
and when I pressed the button it would setState and set logging_in = true.
StaticButton:
RaisedButton(
child: Text("Login"),
onPressed: () {
setState(() {
logging_in = true;
});
}
)
The above is what I tried. Should accomplish the effect, but is definitely not elegant. Is there a better way to achieve this?
What you have isn't that far off. You have a logging_in variable that tells you when to show the CircularProgressIndicator already, which is a good start. The only thing is that FutureBuilder has no use here. It's meant for retrieving data from Futures that need to be displayed in the UI.
What you need to do is call logMeIn in the onPressed of your "Static button" as shown here:
StaticButton:
RaisedButton(
child: Text("Login"),
onPressed: () async {
setState(() {
logging_in = true;
});
await logMeIn();
setState(() {
logging_in = false;
});
}
)
You don't need to switch out the whole button when logging_in changes, just the child of the button.
StaticButton(with child switching):
RaisedButton(
child: logging_in ? CircularProgressIndicator() : Text("Login"),
onPressed: () async {
setState(() {
logging_in = true;
});
await logMeIn();
setState(() {
logging_in = false;
});
}
)
You also probably want to add another bool to prevent multiple clicks of the button while its loading. As a note in using FutureBuilder and essentially any other builder in flutter, you always need to return a widget from the builder function, which your current implementation does not do.

setState does not seem to work inside a builder function

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