How to Store locally Increment Counter in ListView? flutter - 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)

Related

SharedPreferences data does not have time to be displayed in the widget

I created a model class for Provider. Which will fit the function of getting data from SharedPreferences
Future getDataPerson() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
id = prefs.getInt('id') ?? 000;
name = prefs.getString('name') ?? "Фамилия Имя Отчество";
phone = prefs.getString('phone') ?? "3231313";
email = prefs.getString('email') ?? "";
accessToken = prefs.getString('accessToken') ?? "нет токенааа";
_login = prefs.getString('login') ?? "нет логинааа";
_password = prefs.getString('password') ?? "нет пароляяя";
notifyListeners();
}
That's how I implement my Provider
body: MultiProvider(
providers: [
ChangeNotifierProvider<ApiClient>(create: (_)=>ApiClient()),
ChangeNotifierProvider<FinalModel>(create: (_)=>FinalModel()),
],
child: FinalScreenState(),
),
In initState, I call this function.
#override
void initState() {
super.initState();
finalModel = Provider.of<FinalModel>(context,listen: false);
getDataPerson();
}
Future getDataPerson() async{
return await finalModel.getDataPerson();
}
And in the code I get these variables and paste them into Text
idController.text = finalModel.getId.toString();
return Padding(padding: EdgeInsets.only(top: 15, bottom: 10,right: 1,left: 1),
child:Center(
child: TextField(
controller: idController,
))
);
However, only the values that I wrote in the code are inserted into the text. In this line it is "3231313"
prefs.getString('phone') ?? "3231313";
I tried calling the get Data Person function in different places in the code. In the build method itself.
The data I want to insert I take from json immediately after the user logs in using the button and receives a response from the api
var statusOne =await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
Map<String, dynamic> map = jsonDecode(rawJson);
bool status = map['status'];
if (status == true) {
//Entry in SharedPreferences
setDataPerson();
//The screen on which the data is displayed
Navigator.pushReplacementNamed(context, 'final');}
method setDataPerson()
void setDataPerson() async {
final prefs = await SharedPreferences.getInstance();
var statusOne =
await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
var rawJson = AuthModel.fromJson(statusOne.data);
await prefs.setInt('id', rawJson.data!.performerId!);
await prefs.setString('name', rawJson.data!.fullName!);
await prefs.setString('phone', rawJson.data!.phone!);
await prefs.setString('accessToken', rawJson.data!.accessToken!);
}
Build method
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IdWidget(),
NameWidget(),
PhoneWidget(),
EmailWidget(),
],
);
}
PhoneWidget
class PhoneWidget extends StatelessWidget {
Widget build(BuildContext context) {
var finalModel = Provider.of<FinalModel>(context,listen: true);
return Padding(padding: EdgeInsets.only(top: 10, bottom: 10,right:
1,left: 1),
child: Consumer<FinalModel>(
builder: (context, model, widget) {
finalModel.phoneController.text = model.phone;
return Center(
child: TextField(
controller: finalModel.phoneController,
enabled: false,
decoration: const InputDecoration(
labelText: "Телефон",
contentPadding: EdgeInsets.only(left: 10, top: 10, bottom: 10),
border: OutlineInputBorder(),
),
));
})
);
}
}
However, even if you do not receive this data from the network and just write a regular string, the data will not have time to be displayed on the screen.
It's worth noting here that when I restart the screen. And that is, I update the initState and build method. Then the data is updated and immediately displayed on the screen
I am not considering inserting the listen:true parameter into provider, , because the getData Person function will be called too often.
Wrap Consumer widget to your Columnwidget so registered listeners will be called.
Widget IdWidget() {
return Consumer<YourModelClass>(
builder: (context, model, widget) => Padding(
padding: EdgeInsets.only(top: 15, bottom: 10, right: 1, left: 1),
child: Center(
child: TextField(
controller: model.id,
))));
}

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

Only update UI when there are changes in the server, using streambuilder

I am using streambuilder to check whether a new order is placed or not.
I am checking the order status, if the order status is unknown I want to show a pop up, which works fine. but if i don't select an option to update the order status, streambuilder refreshes after a few seconds, and show another pop up on top of it.
Get Orders Function:
Future<Orders> getOrders() async {
String bsid = widget.bsid;
try {
Map<String, dynamic> body = {
"bsid": bsid,
};
http.Response response = await http.post(
Uri.parse(
"**API HERE**"),
body: body);
Map<String, dynamic> mapData = json.decode(response.body);
Orders myOrders;
print(response.body);
if (response.statusCode == 200) {
print("Success");
myOrders = Orders.fromJson(mapData);
}
return myOrders;
} catch (e) {}
}
Here's the stream function:
Stream<Orders> getOrdersStrem(Duration refreshTime) async* {
while (true) {
await Future.delayed(refreshTime);
yield await getOrders();
}}
StreamBuilder:
StreamBuilder<Orders>(
stream: getOrdersStrem(
Duration(
seconds: 2,
),
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
CircularProgressIndicator.adaptive(),
);
}
var orders = snapshot.data.statedatas;
return ListView.builder(
itemCount: orders.length,
itemBuilder: (BuildContext context, int index) {
var orderResponse =
snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponse == "0") {
print("order status unknown");
Future.delayed(Duration.zero, () {
_playFile();
showCupertinoDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Center(
child: Text(
"#${orders[index].ordrAutoid}",
),
),
content: Row(
children: [
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.green; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "accept");
// setState(() {});
},
child: Text('Accept'),
),
),
SizedBox(
width: 15,
),
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.red; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "Reject");
// setState(() {});
},
child: Text('Reject'),
),
),
// TextButton(
// onPressed: () async {
// _stopFile();
// Navigator.pop(context);
// await changeOrderStatus(
// orders[index].orid, "reject");
// },
// child: Text('reject'),
// ),
],
),
),
);
}).then((value) {
_stopFile();
print("ENDING");
});
}
return Container();
Create a variable to check for the last known order status, outside your if statement, and when a new value comes, compare it to the old value first, then do the if statement logic.
//This is outside the stream builder:
String orderResponseCheck = "";
.
.
.
//This is inside your streambuidler, if the orderResponseCheck is still equal to "", the if statement will be executed,
//and the value of orderResponse wil be assigned to it. This will only show the alert dialog if the orderResponse status changes from the one that previously triggered it.
var orderResponse =snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponseCheck != orderResponse && orderResponse == "0") {
orderResponseCheck = orderResponse;
.
.
.
//logic same as before
You shouldn't call showCupertinoDialog (and probably _playFile()) from your build method. Wrapping it with Future.delayed(Duration.zero, () { ... }) was probably a workaround for an error that was given by the framework.
The build method can get executed multiple times. You probably want a way to run _playFile and show the dialog that isn't depending on the UI. I don't think StreamBuilder is the right solution for this.
You could use a StatefulWidget and execute listen on a stream from the initState method. initState will only be called once.
From what I'm reading, you're querying your API every two seconds.
Every time your API answers, you're pushing the new datas to your StreamBuilder, which explains why you're having multiple pop-ups are stacking.
One simple solution to your problem would be to have a boolean set to true when the dialog is displayed to avoid showing it multiple times.
bool isDialogShowing = false;
...
if (orderResponse == "0" && !isDialogShowing) {
isDialogShowing = true;
...
But there are a few mistakes in your code that you should avoid like :
Infinite loops
Querying your API multiple times automatically (it could DDOS your service if plenty of users are using your app at the same time)
Showing your Dialog in a ListView builder

Favourite Button in Flutter got unFavourited on Relaunch app

I have a ListView Item with Favourite icon and I want to add functionality so that I can add list into favourite list. data is successfully added to favourite list.
Here is HomePage
body: ListView.builder(
itemCount: 100,
cacheExtent: 20.0,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => ItemTile(index),
),
and My ListTile class I used
var favoritesList = Provider.of<Favorites>(context);
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
),
title: Text(
'Item $index',
key: Key('text_$index'),
),
trailing: IconButton(
key: Key('icon_$index'),
icon: favoritesList.items.contains(index)
? Icon(Icons.favorite, color: Colors.redAccent)
: Icon(Icons.favorite_border),
onPressed: () {
!favoritesList.items.contains(index)
? favoritesList.add(index)
: favoritesList.remove(index);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(favoritesList.items.contains(index)
? 'Added to favorites.'
: 'Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
I have a model class favourites.dart
class Favorites extends ChangeNotifier {
final List<int> _favoriteItems = [];
List<int> get items => _favoriteItems;
void add(int itemNo) {
_favoriteItems.add(itemNo);
notifyListeners();
}
void remove(int itemNo) {
_favoriteItems.remove(itemNo);
notifyListeners();
}
}
and in my favouritePage. I am getting everything perfect and also can remove favourited item but when I reopen my app I did not get any favourited item.
here is my page FavouritePage.
body: Consumer<Favorites>(
builder: (context, value, child) => ListView.builder(
itemCount: value.items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
),
),
FavouriteItemTile
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('favorites_text_$itemNo'),
),
trailing: IconButton(
key: Key('remove_icon_$itemNo'),
icon: Icon(Icons.close),
onPressed: () {
Provider.of<Favorites>(context, listen: false).remove(itemNo);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
please provide the solution and can I use shared preferences with provider.
Yes. You should be using SharedPreferences. Add the preference library and these pieces of code
Object.dart
class Object1{
bool isLiked;
String name;
const Object1(this.name,this.isLiked);//Whatever fields you need
factory User.fromJson(Map<String, dynamic> parsedJson) {
return new Object1(
name: parsedJson['name'] ?? "",
isLiked: parsedJson['isLiked'] ?? "");
}
Map<String, dynamic> toJson() {
return {
"name": this.name,
"isLiked": this.isLiked
};
}
}
Main.dart
void main(){
setData();
runApp(MyApp);
}
void setData() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
List dataList = [Object1("Name",false).toJson()];//Your items in this format
if prefs.getStringList("lists") == null:
Map decode_options = jsonDecode(dataList);
prefs.setStringList(jsonEncode(Object1.fromJson(decode_options)));
}
Now instead of a custom class for favourites, we will get all the data where we can filter. To retrieve the data afterwards, use this code
SharedPreferences prefs = await SharedPreferences.getInstance();
Map objectMap = jsonDecode(await shared_User.getStringList('list'));
List itemList = [];
for (item in objectMap):
itemList.append(User.fromJson(item));
Now you can use this Item list with the properties and the isLiked feature which is a boolean to check whether it is showed or not.
This may seem complicated but is perfectly simple though your work would be much easier if you used a database like firebase and stored these as documents
One option can be that you can store according to index value in shared preference and query that index value in order to see whether it is added as favourite or not. However it won't be efficient as the number of favourites increases, though still an option.
If you want to store on device us File(pathToFile).write(dataAsString)
You might want to save the data as json using jsonEncode(listOfNumbers) and decode using jsonDecode()
Explanation:
To save data, convert it to json and save to File on device
// NOTE: dataAsMap should be instance of class Map which stores the data you want to save
Directory localDirectory = await getApplicationDocumentsDirectory();
File(localDirectory.path + “/“ + “fileName.json”).writeAsStringSync(jsonEncode(dataAsMap));
To get data:
Map jsonData = jsonDecode(File(localDirectory.path + “/“ + “fileName.json”).readAsStringSync())

Flutter:Instance of 'SharedPreferences'

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