Flutter:Instance of 'SharedPreferences' - flutter

I was using shared_preferences plugin in my Flutter application, I want to save data when the user selects the city.
But when I try to print it just says;
Instance of 'SharedPreferences' Unhandled Exception: setState()
callback argument returned a Future
. (Even if I remove my setState part I get the same error)in my console.
does anyone know the reason??
My text in the in card widget
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: Text(
snapshot.data.name,
style: TextStyle(fontSize: 16),
),
),
and when clicked where I saved it
onTap: () {
setState(() async {
final country =
await SharedPreferences.getInstance();
String name;
name=snapshot.data.name;
country.setString('name', name);
print('here $country');
});
},

Try
onTap: () async {
final country =
await SharedPreferences.getInstance();
setState(() {
String name;
name=snapshot.data.name;
country.setString('name', name);
print('here $name);
});
},

Related

Data from setstate not accessible Flutter even though its there

I have a textAheadField and successfully get data from it, i call setState so the data can be saved locally in statefullwidget. and i want to store it in database firestore but inside the update method firestore the variable that i want (imageuniversitycollege) is empty and has not been update like in the setstate should be.
This is the textAheadField
String imageuniversitycollege = "";
Widget CollegeBuildTextAhead() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: TypeAheadField<SchoolData?>(
hideSuggestionsOnKeyboardHide: true,
debounceDuration: Duration(milliseconds: 500),
textFieldConfiguration: TextFieldConfiguration(
controller: collegeAheadController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.school),
border: OutlineInputBorder(),
hintText: 'Search your school',
),
),
suggestionsCallback: SchoolApi.getUserSuggestions,
itemBuilder: (context, SchoolData? suggestion) {
final datasugestion = suggestion!;
return ListTile(
leading: Container(
width: 40,
height: 40,
child: Image.network(
datasugestion.image,
fit: BoxFit.cover,
),
),
// leading for image
title: Text(datasugestion.university),
);
},
onSuggestionSelected: (SchoolData? choose) {
final datasugestion = choose!;
collegeAheadController.text = datasugestion.university; //this works fine in the update
final String imageuniversitycollege = datasugestion.image;
setState(() {
final String imageuniversitycollege = datasugestion.image;// this is the data i want
print(imageuniversitycollege); //i print it in here its get the usual link of image
});
},
),
);
}
The usual elevated button
Center(
child: ElevatedButton(
child: const Text(
"Confirm",
),
onPressed: () {
updateEducationCollege();
},
),
)
this is the method update, it works but the image is not filled
Future updateEducationCollege() async {
try {
print(FirebaseAuth.instance.currentUser?.uid);
await FirebaseFirestore.instance
.collection("education")
.doc(FirebaseAuth.instance.currentUser!.uid)
.set({
"uid": FirebaseAuth.instance.currentUser?.uid,
"College": collegeAheadController.text,
"imageCollege": imageuniversitycollege,
}).then((value) => print("Data changed successfully"));
} on FirebaseException catch (e) {
Utils.showSnackBar(e.message);
}
}
The collegeAheadController.text seems fine still successfully retrieve the data like the image bellow
what should i do? to get this data??
Just change
setState(() {
final String imageuniversitycollege = datasugestion.image;
});
to
setState(() {
imageuniversitycollege = datasugestion.image;
});
Instead of updating the existing state variable, you are creating a new local variable. Thats the issue.
Happy coding:)
When you try update your variable you are define new one, change your onSuggestionSelected to this:
onSuggestionSelected: (SchoolData? choose) {
final datasugestion = choose!;
collegeAheadController.text = datasugestion.university;
final String imageuniversitycollege = datasugestion.image;
setState(() {
imageuniversitycollege = datasugestion.image; //<-- change this
print(imageuniversitycollege);
});
},

How to place a Loader on the screen while an API action is being performed in Flutter

I am trying to show a loader when a form is submitted to the server so that there isn't another submission of the same form until and unless the API sends back a response. I have tried something like the below code but it just doesn't seem to work as the Circular Progress indicator seems to not show up and rather, the screen remains as it is until the server sends back a response. As a result of this, the user gets confused as to whether or not their requests got submitted, and in the process, they end up posting the same form another time only to find out later that their were multiple submissions. I will include snippets of the code that has the CircularProgressIndicator() to prevent another submission and the widget that has the API call code.
bool isSelected = false;
isSelected
? const CircularProgressIndicator() : Container(
child: Center(
child: AppButtonStyle(
label: 'Submit',
onPressed: () {
if (_key.currentState!.validate()) { //This is the key of the Form that gets submitted
setState(() {
isSelected = true;
});
List<String> date = [
dateFormat.format(_dateTimeStart!).toString(),
dateFormat.format(_dateTimeEnd!).toString()
];
Map<String, dynamic> data = {
'leave_type': _selectedItem,
'dates': date,
'description': add
};
if (kDebugMode) {
print('DATA: $data');
}
Provider.of<LeaveViewModel>(context, listen: false)
.postLeaveRequests(data, context) //This here makes the API call
.then((value) {
setState(() {
isSelected = false;
_textController.clear();
_dateTimeStart = null;
_dateTimeEnd = null;
});
});
}
},
),
),
)
The API module:
class LeaveViewModel with ChangeNotifier {
final leaveRepository = LeaveRequestRepository();
Future<void> postLeaveRequests(dynamic data, BuildContext context) async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
String authToken = localStorage.getString('token').toString();
leaveRepository.requestLeave(authToken, data).then((value) {
print('LEAVEEEEEE: $value');
Flushbar(
duration: const Duration(seconds: 4),
flushbarPosition: FlushbarPosition.BOTTOM,
borderRadius: BorderRadius.circular(10),
icon: const Icon(Icons.error, color: Colors.white),
// margin: const EdgeInsets.fromLTRB(100, 10, 100, 0),
title: 'Leave Request Submitted',
message: value.data.toString()
).show(context);
}).onError((error, stackTrace) {
Flushbar(
duration: const Duration(seconds: 4),
flushbarPosition: FlushbarPosition.BOTTOM,
borderRadius: BorderRadius.circular(10),
icon: const Icon(Icons.error, color: Colors.white),
// margin: const EdgeInsets.fromLTRB(100, 10, 100, 0),
title: 'Leave Request Failed',
message: error.toString()
).show(context);
});
}
}
Any help will be appreciated. Also, I'm open to the concept of using easy_loader 2.0.0 instead of CicularProgressIndicator() and would be very glad to read suggestions about it's usage in my code.
One problem in your code seems to be that you define isSelected in your build method. Every time you call setState, the build method is called to regenerate the widgets. And with each new call isSelected gets false as initial value. Define isSelected as class variable, so that it is not always on false.
The more elegant solution would be to work with a FutureBuilder
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

How to wait for value before calling method Flutter/Dart

I am having a pretty hard time with certain actions in flutter. I currently have a method in an outside class that updates a db that my widget relies on for displaying info. I am correctly updating the values in the db and updating the UI correctly. BUT I am having a hard time getting an input first, THEN having that method function. I have tried having it all in the same body and no dice, I have tried to have the addStock method show the input and does not work. The only thing that has been a ban-aid has been to use Navigator.push to the screen again or using a time delayed. Both have produced undesired consequences. I have also tried having the addStock method inside the displayAmountToADD on pressing okay and does not update UI.
//a button inside the UI
onPressed: () async {
displayAmountToAdd(context, index);
setState(() {});
},
....
Future<void> displayAmountToAdd(
BuildContext context,
int index,
) async {
final _textFieldController = TextEditingController();
double materialKG = 0;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Enter amount to add'),
content: Row(
children: <Widget>[
Expanded(
child: TextField(
onChanged: (materialQuanity) {
materialKG = double.parse(materialQuanity);
},
controller: _textFieldController,
decoration: InputDecoration(hintText: "KG"),
),
),
],
),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
materialKG = double.parse(_textFieldController.text);
addStock(context, mapM[index]['quanity'], mapM[index]['name'],
materialKG);
Navigator.pop(context);
},
),
TextButton(
child: Text("Cancel"),
onPressed: () {
Navigator.pop(context);
})
],
);
},
);
//return Future.delayed(Duration(seconds: 4),()=>materialKG); //TRYING TO AVOID THIS
}
//outside the ui file
addStock(
BuildContext context,
double currentQuanity,
String name,
double amountToAdd
) async {
//final db = await database;
double newStock;
late double materialKG;
newStock=currentQuanity+amountToAdd;
await db.rawUpdate(
'UPDATE materials SET quanity = $newStock WHERE name = "$name" ');
mapM = await db.query('materials'); //update values
//the following is only because setState is not working properly on other screen
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Added $amountToAdd KG to $name"),
));
}
displayAmountToAdd and showDialog method are future. Use await before theses to hold method to finish.
A sample example:
const oneSecond = Duration(seconds: 1);
// ยทยทยท
Future<void> printWithDelay(String message) async {
await Future.delayed(oneSecond);
print(message);
}
Learn more about async-await.

How to Store locally Increment Counter in ListView? flutter

I am talking an API request from website And there is an vote which I would like to locally store in flutter. I already implemented increment & decrement of votes but I want to store that votes locally in the phone, in a flutter.
How to locally store increment & decrement counter in listView?
class MoviesModel {
int vote;
MoviesModel({this.vote});
int increaseCounter() {
vote++;
return vote;
}
void decreaseCounter() {
if (vote > 0) {
vote--;
}
}
}
Below is the listView Builder
ListView.builder(
itemCount: _movies.length,
padding: EdgeInsets.all(4),
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
final moviess = _movies[index];
return Column(
children: [
IconButton(
icon: Icon(
Icons.keyboard_arrow_up_outlined,
),
color: Colors.white,
onPressed: () async {
final prefs = await SharedPreferences
.getInstance();
setState(() {
final vote =
moviess.increaseCounter();
prefs.setInt('vote', vote);
print(vote);
});
},
),
SizedBox(
height: 10,
),
// moviess.vote.toString(),
Text(moviess.vote.toString() ?? " ",
style: TextStyle(
color: Colors.white,
fontSize: 20)),
SizedBox(
height: 10,
),
IconButton(
icon: Icon(
Icons.keyboard_arrow_down_outlined,
),
color: Colors.white,
onPressed: () {
setState(() {
moviess.decreaseCounter();
});
// decreaseCount();
},
),
],
);
}
),
First of all add shared_preferences.
Create a variable in your class as SharedPreferences prefs;
Initialise the instance in the initState() like: prefs = await SharedPreferences.getInstance()
When the counter is clicked / increased / decreased, save it like this:
await prefs.setInt('counter', counterValue);
When you open the app next time, check in the initState() if there is a value saved in preferences. If yes, then use that else use 0.
Example:
int counter = (prefs.getInt('counterValue') ?? 0);
Now use this counter variable to display text.
You can use shared_preferences
Future<void> _storeIncrement(int yourValue) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', yourValue);
}
there is also more option to store as per your datatype as below
prefs.setBool(key, value)
prefs.setString(key, value)
prefs.setDouble(key, value)
prefs.setStringList(key, value)
below code is to get data
Future<void> _getIncrement() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int storedValue = prefs.getInt('counter');
print('your data is $storedValue');
}
get data as per your datatype
prefs.getBool(key)
prefs.getString(key)
prefs.getDouble(key)
prefs.getStringList(key)

Dart/Flutter: Strings of element inside a List becomes empty when passing as an argument (Why??)

Strings of element inside a List becomes empty when passing as an argument.
It was working before. I don't know what happened that it stopped working, and started passing empty.
I have a model called SubjectiveList, it is the list I am talking about.
class SubjectiveList {
String id;
String name;
List<Item> items;
SubjectiveList({this.id, this.name, this.items});
}
This list has the property items. What becomes empty is the properties inside the Item object.
class Item {
String id;
String name;
Content content;
Item({this.id, this.name, this.content});
}
On the debugger, The newList instance appears fine, with the object names (ps: the ID is okay to be null at this point because it will come from Firestore Database later)
Here is the code with the screenshots:
Future<dynamic> showListInfoDialog() {
final userData = Provider.of<UserData>(context, listen: false);
GlobalKey<FormState> _addListInfoFormKey = GlobalKey<FormState>();
final ValueNotifier<int> tabIndex =
Provider.of<ValueNotifier<int>>(context, listen: false);
TempListViewModel tempList =
Provider.of<TempListViewModel>(context, listen: false);
return showDialog(
context: context,
child: SimpleDialog(
title: Text("List Info"),
children: <Widget>[
Padding(
padding: const EdgeInsets.all(defaultSpacing),
child: Form(
key: _addListInfoFormKey,
child: Column(
children: <Widget>[
TextFormField(
onChanged: (val) => tempList.setListName(val),
validator: (val) => val.isEmpty ? 'Write a name' : null,
decoration: InputDecoration(
prefixIcon: Icon(Icons.featured_play_list),
labelText: "List Name",
),
),
SizedBox(height: defaultSpacing),
SizedBox(
width: double.infinity,
child: RaisedButton(
child: Text("Create List"),
color: successColor,
onPressed: () {
if (_addListInfoFormKey.currentState.validate()) {
final newList = SubjectiveList(
name: tempList.list.name,
items: tempList.list.items);
DatabaseService(uid: userData.uid)
.addListToDatabase(newList); // <-- HERE
tempList.init();
tabIndex.value = 0;
Navigator.of(context).pop();
}
},
),
)
],
),
),
),
],
),
);
}
And then it appears empty when coming to the function!!
Future addListToDatabase(SubjectiveList list) async { <-- HERE
DocumentReference listDocument =
await userDocument.collection('lists').add({'name': list.name});
[...]
}
Thanks #edenar
Now I understand what happened. In Flutter the line "final newList = SubjectiveList(name: tempList.list.name, items: tempList.list.items);" makes a pointer reference, and not an declaration of the current value. So, when it goes to the next line and executes tempList.init() it is clearing the list before getting the argument in the function.
So it worked putting await in that line.