I am trying to update a Boolean value declared in build method using a function which is being called on onPressed event.
Widget build(BuildContext context) {
DatabaseFunctions databaseFunctions = new DatabaseFunctions();
bool isLiked = false;
updateLikes() async{
if(!isLiked){
isLiked = true;
await databaseFunctions.updateLikes(postId, likeCount+1);
}else{
await databaseFunctions.updateLikes(postId, likeCount-1);
isLiked = false;
}
}
return IconButton(
onPressed: (){
updateLikes();
print("like status: $isLiked");
},
icon: Icon(FontAwesomeIcons.heart),),
}
the function updateLikes is updating a collection in the database and trying to switch the isLiked value.
The database function:
databaseFunctions.updateLikes(String postId, int likeCount) async{
return await firestore.collection('posts')
.doc(postId).update({'likes': {'likeCount' : likeCount}});
}
The problem is the value of isLiked is not changing and the number of likes in the database keeps increasing on clicking the button.
Try moving these outside of the "build()" method:
the "updateLikes()" method
the " isLiked" variable
Also add "await" before calling the " updateLikes" method.
Let me know if these workout for you
defining variable inside build function is incorrect, you should move it outside the build method. this is due to everytime flutter rerender your widget this parameter will reinitialize in the build method. Also, use setState to change the state of the widget as follow:
bool isLiked = false;
updateLikes() async{
if(!isLiked){
isLiked = true;
await databaseFunctions.updateLikes(postId, likeCount+1);
}else{
await databaseFunctions.updateLikes(postId, likeCount-1);
isLiked = false;
}
setState((){});
}
Widget build(BuildContext context) {
DatabaseFunctions databaseFunctions = new DatabaseFunctions();
return IconButton(
onPressed: (){
updateLikes();
print("like status: $isLiked");
},
icon: Icon(FontAwesomeIcons.heart),),
);
Related
I have a page on which I display data via BloC. I added the isLoading variable to display the loading indicator, which is true after two seconds, this value changes to false and the loading indicator disappears and the data is displayed. But I got an error because I use setState(). Tell me how to fix this error?
Widget _child(Size size, BuildContext context, double topPadding) {
return BlocBuilder<MycarsCubit, MycarsState>(
builder: (context, stateElectricvehicles) {
final ElectricvehiclesCubit cubit =
BlocProvider.of<ElectricvehiclesCubit>(context);
if (stateElectricvehicles is MycarsInitial) {
carNumber.text = stateElectricvehicles.number;
carNumber.selection = TextSelection.fromPosition(
TextPosition(offset: carNumber.text.length),
);
if (stateElectricvehicles.id == 0) {
savedCarId = stateElectricvehicles.id;
}
Future.delayed(const Duration(seconds: 2), () {
setState(() {
isLoading = false;
});
});
final SharedPrefs prefs = SharedPrefs();
return FutureBuilder<int?>(
Always add a check mounted (available in StatefulWidget) before calling setState in async function.
Future.delayed(const Duration(seconds: 2), () {
if(mounted) {
setState(() {
isLoading = false;
});
}
});
See if this solves it:
Future.delayed(const Duration(seconds: 2), () {
// Wrap setState in an if:
if (mounted) {
setState(() {
isLoading = false;
});
}
});
This should make sure setState() is not called unless the context is still mounted.
In later versions of flutter lints, you will get a warning about this, saying something like "Do not use context over async gaps!". Meaning if you have an async delay, i.e. an "await", you have to check afterwards if the context is still alive before you use it. 🙂
I need to hide button after 1 mins but while reloading and again restarting application it will not show again once it hide it will not repeated
Use shared_preferences (or any persist memory) to persist the state of the button over restarts.
See Store key-value data on disk for details.
You need to use db to handle restarting application case.
You can use shared_preferences to store a flag about visibility.
class _GState extends State<G> {
static const String _buttonVisibilityKey = "DoneWIthButtonX";
bool showButton = false;
#override
void initState() {
super.initState();
_buttonActionChecker().then((value) async {
// return false if button never showed up, activate the timer to hide
print(value);
if (!value) {
showButton = true;
setState(() {});
Future.delayed(Duration(minutes: 1)).then((value) async {
showButton = false;
SharedPreferences.getInstance()
.then((value) => value.setBool(_buttonVisibilityKey, true));
setState(() {});
});
}
});
}
#override
void dispose() {
super.dispose();
}
/// is the button have been shown already return true
Future<bool> _buttonActionChecker() async {
return SharedPreferences.getInstance().then((value) {
return value.getBool(_buttonVisibilityKey) ?? false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: showButton
? FloatingActionButton(
onPressed: () {
setState(() {});
},
)
: null,
);
}
}
I have a problem implementing a login button, when I press it, it will not function at first, but when I press it again (same value fields) it works.
here's my code
Button:
Center(child: RaisedButton(onPressed: (){
setState(() {
onPressedLogin(userName.text,password.text);
});
}
The OnPressedLogin():
void onPressedLogin(String userName,String password) async{
bool isValid = false;
var value = await dataBaseHelper.getUserList();
for(User userOB in value){
//print(userOB.password+" "+password),
if(userName == userOB.username && password == userOB.password) {
isValid = true;
this.password.clear();
this.userName.clear();
inputTextColor = Colors.grey[850];
invalidCredentials = "";
print("YES");
//Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
break;
}
}
if(!isValid){
inputTextColor = Colors.red[800];
invalidCredentials = "Invalid Credentials";
}
You are using a Future but in setState() you are not waiting for it so that's way it work in the second press (the value take time to change).
To make it work with single press you have to a wait the Future to complete before rebuilding, here how:
First change the return type of the function
Future<void> onPressedLogin(String userName,String password)
Then in the RaisedButton
onPressed: () async {
await onPressedLogin(userName.text,password.text);
setState(() {});
},
The moment you setState(), the UI will refresh!
Probably that's the issue, let me explain:
What you should do is to call your function before setState(), so that the screen is refreshed with the new info.
Center(child: RaisedButton(onPressed: (){
onPressedLogin(userName.text,password.text);
setState(() {
//Variables that change for the refresh.
});
}
In your specific case, I don't see the need for SetState() as you are only printing values in log, not changing the UI.
Hope it is helpful.
I have a Card() widget which contains a ListTile() widget.
One of the ListTile() widget's properties is enabled. I would like to dynamically set the value of this enabled property by using the outcome of a Future<bool> which uses async and await. Is this possible?
Here is the Card() widget with the ListTile() in it
Card myCard = Card(
child: ListTile(
title: Text('This is my list tile in a card'),
enabled: needsToBeEnabled(1),
),
);
Here is my Future
Future<bool> cardNeedsToBeEnabled(int index) async {
bool thisWidgetIsRequired = await getAsynchronousData(index);
if (thisWidgetIsRequired == true) {
return true;
} else {
return false;
}
}
Other attempts
I have tried to use a Future Builder. This works well when I'm building a widget, but in this case I'm trying to set the widget's property; not build a widget itself.
You cannot do that for two reason:
enable does not accept Future<bool> but bool
you need to update the state after result is received (you need a StatefullWidget)
There are 1 million way to do what you want to do and one of this is FutureBuilder but if you want to not rebuild all widget you can use this flow (your main widget need to be Statefull):
create a local variable that contains your bool value, something like bool _enabled
on initState() method override you can launch the call that get asynchronous data and using the then() extension method you can provide the new state to your widget when the call will be completed.
Something like:
#override
void initState() {
super.initState();
getAsynchronousData(index).then((result) {
if (result == true) {
_enabled = true;
} else {
_enabled = false;
}
setState(() {});
});
}
assign the boolean var to the ListTile widget
I have an InkWell which uses onTap to perform some actions. When the button is tapped, I like an indicator to be shown (in case the action is long-running). However, the setState in the InkWell does not trigger its children to be re-rendered. The code is as follows:
class PrimaryButtonState extends State<PrimaryButton> {
bool _apiCall;
Widget getWidget() {
if(_apiCall) {
return new CircularProgressIndicator();
} else {
return Text(
widget.label,
);
}
}
#override
Widget build(BuildContext context) {
final List<Color> colors = //omitted
return InkWell(
child: Container(
decoration: // omitted
child: getWidget(), // not updated when _apiCall changes !!!!!
),
onTap: () {
setState(() {
_apiCall = true;
});
widget.onTab(context);
setState(() {
_apiCall = false;
});
}
);
}
}
How can I solve this that getWidget returns the correct widget dependent on _apiCall?
EDIT:
The widget.onTap contains the following:
void performLogin(BuildContext context) {
final String userName = _userName.text.trim();
final String password = _password.text.trim();
UserService.get().loginUser(userName, password).then((val) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => MainLayout()));
}).catchError((e) {
// omitted
});
}
it is passed with the widget:
class PrimaryButton extends StatefulWidget {
final bool isPrimary;
final String label;
final Function(BuildContext context) onTab;
PrimaryButton(this.label, this.isPrimary, this.onTab);
#override
State<StatefulWidget> createState() => PrimaryButtonState();
}
My main concern is, that the given onTap method should not know it is "bound" to a UI widget and therefore should not setState. Also, as this is a general button implementation I like it to be interchangeable (therefore, onTap is not hardcoded)
It looks like your problem is because you are calling setState() twice in your onTap() function. Since onTap() is not an async function it will set _apiCall = true in the first setState, then immediately run widget.onTab(context) and then immediately perform the second setState() to set _apiCall = false so you never see the loading widget.
To fix this you will need to make your onTab function an async function and await for a value in your onTap function for your InkWell:
onTap: () async {
setState(() {
_apiCall = true;
});
await widget.onTab(context);
setState(() {
_apiCall = false;
});
}
This will also let you use the results of your onTab function to show errors or other functionality if needed.
If you are unsure how to use async functions and futures here is a good guide on it that goes over this exact kind of use case.