Favourite Button in Flutter got unFavourited on Relaunch app - flutter

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())

Related

How to display all data in array?

I'm trying to display data from an array in firestore. I displayed it, but only [0] in the array is showing. I'm trying to get all the data in the array to show.
builder: (_, AsyncSnapshot<List<DocumentSnapshot>> snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
Expanded(
child: Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.all(1),
decoration: BoxDecoration(border: Border.all(color:Colors.black)),
child: Center(
child: ListTile(
title: Text(post['postText'], style: TextStyle(color: Colors.white),),
subtitle: Text(post['fromUser'], style: TextStyle(color: Colors.white),),
),
),
),
)
);
}
return Expanded(
child: ListView(
children: tiles,
),
);
}),
);
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
enter image description here
Edit
To answer your qn about newest to oldest:
I suggest you put a FieldValue.timestamp field in your group chat documents! Then, you can order them like this:
Future<List<DocumentSnapshot>> getDoc(groupID) async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true) // <- Here!
.get();
return qn.docs;
}
(All of that I copied by hand, since you hadn't provided this code as text, as I asked you to!... 😆)
If you don't have a timestamp field, there is a way to still find out when a document was created... but I don't know how. Plus, in this case, I guess you want the time a certain FIELD was created in the document...! I don't know if that's possible. In fact, for that you'll probably have to do:
List<Map> posts = snapshot.data![index]['posts'];
// Sort list according to the 'date' field in each Map in the list:
posts.sort((mapA, mapB){
return mapA['date'].compareTo(mapB['date']);
});
// Then you'll use posts in your for-loop instead of snapshot.data![index]['posts']:
for (Map post in posts) {
tiles.add( /*etc*/);
}
Btw, if you want it to update when new messages come in, you can do like this:
import 'dart:async';
// Put the below in the State of a StatefullWidget:
StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? qn;
List<DocumentSnapshot>? eventDocs;
void getDocStream(groupID) async {
var firestore = FirebaseFirestore.instance;
qn = firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true)
.snapshots().listen((event) {
// Put here everything you want to happen when new things happen in the stream!
// For example:
setState(() {
eventDocs = event.docs;
});
// Now, you can use eventDocs instead of getDoc(groupID), as you did before.
// Just remember that it will be null at first!
});
}
#override
void dispose() {
if (qn != null) qn!.cancel(); // This is to prevent the stream from going on, after you've left the page or even closed the app...
super.dispose();
}
Old answer:
But you're telling it to display only post [0]!...
If there are more posts in each document, and you want to display all of them, you need to make a for-loop or something. For example:
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
ListTile(
title: Text(post['postText']),
subtitle: Text(post['fromUser']),
));
}
return Expanded(
child: Column(
children: tiles,
),
);
}),
And btw... Next time you ask a qn, plz paste your code as text rather than an image! So that we can copy-paste it into our answer, rather than having to retype it from the image. It's so easy to make a mistake and then you get an error coz we didn't copy it right.
try this
title: Text(snapshot.data![index]['posts']['postText']),

How to get data from list of map to display in Recordable list view

Please please help me ..
I found this code from a software site, but only used list of string but I have List tasks; so I can't view the data of List tasks;
In the Recordable list view and also the value of the key in the Recordable list view I didn't understand it..Does anyone have an idea to solve this?
final List<String> _products =
List.generate(100, (index) => "Product ${index.toString()}");
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Kindacode.com'),
),
body: ReorderableListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final String productName = _products[index];
return Card(
key: ValueKey(productName),
color: Colors.amberAccent,
elevation: 1,
margin: const EdgeInsets.all(10),
child: ListTile(
contentPadding: const EdgeInsets.all(25),
title: Text(
productName,
style: const TextStyle(fontSize: 18),
),
trailing: const Icon(Icons.drag_handle),
onTap: () {/* Do something else */},
),
);
},
// The reorder function
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex = newIndex - 1;
}
final element = _products.removeAt(oldIndex);
_products.insert(newIndex, element);
});
}),
);
}
}
I found this code from a software site
If you're new to Flutter and programming in general, while it's good to read code, it might be not a good idea to just copy and paste code. It would be good to actually understand it and also to understand the programming language you're using. I suggest reading this.
but only used list of string but I have List tasks
You didn't provide what your "Task" class is, so I can just help you with some pseudocode. In your ReordableListView's itemBuilder function you have to extract your data, somehow:
final yourTask = _products[index];
final yourTaskName = yourTask.name; // example
Unluckily without further info this is the best I can do

Flutter FutureBuilder Snapshot is null but Future Does return data

While working with Flutter for a new application client for Kanboard, I encountered the following problem. I have a FutureBuilder that should return a select dropdown menu with items but, for some reason, the Snapshot data is null, although the Future method does resolves and has data on return.
Full page.dart code here: https://pastebin.com/J48nxsdZ
The block having the problem is the following:
Widget _columnSelect() {
return FutureBuilder(
future: columnProvider.getColumns(task.projectId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> columnList = [];
if (snapshot.hasData) {
columnList.add(DropdownMenuItem<String>(
child: Text('Select Column'), value: 0.toString()));
_columns = snapshot.data;
} else {
columnList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_columns.forEach((column) {
columnList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
column.title,
),
),
value: column.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.view_column, color: Colors.blue),
),
items: columnList,
value: _columnId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_columnId = newValue;
},
),
);
},
);
}
This is a duplicate of a widget in the same form for a user dropdown select. The original widget (in the same page) is the following:
Widget _ownerSelect() {
return FutureBuilder(
future: userProvider.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> usernameList = [];
if (snapshot.hasData) {
usernameList.add(DropdownMenuItem<String>(
child: Text('Select Owner'), value: 0.toString()));
_users = snapshot.data;
} else {
usernameList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_users.forEach((user) {
usernameList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
user.name,
),
),
value: user.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.person, color: Colors.blue),
),
items: usernameList,
value: _ownerId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_ownerId = newValue;
},
),
);
},
);
}
For some reason, the "_columnSelect" AsyncSnapshot is null always, even when the Future method is working fine:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kanboard/src/models/column_model.dart';
import 'package:kanboard/src/preferences/user_preferences.dart';
class ColumnProvider {
final _prefs = new UserPreferences();
Future<List<ColumnModel>> getColumns(projectId) async {
final Map<String, dynamic> parameters = {
"jsonrpc": "2.0",
"method": "getColumns",
"id": 887036325,
"params": {"project_id": projectId}
};
final credentials = "${_prefs.username}:${_prefs.password}";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials);
final resp = await http.post(
Uri.parse(_prefs.endpoint),
headers: <String, String>{"Authorization": "Basic $encoded"},
body: json.encode(parameters),
);
final decodedData = json.decode(utf8.decode(resp.bodyBytes));
final List<ColumnModel> columns = [];
final List<dynamic> results = decodedData['result'];
if (decodedData == null) return [];
results.forEach((column) {
final columnTemp = ColumnModel.fromJson(column);
columns.add(columnTemp);
});
print(columns);
return columns;
}
}
The output of "print(columns)" returns:
I/flutter ( 9486): [Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel']
I don't know what I'm missing here. The form has 2 users dropdown select (with the original FutureBuilder Widget) which works just fine. The Column widget with the Future Builder is the one with the "null" problem in snapshot.data.
Thank you in advance for your time and support with this!
I just found where the problem was:
In the form page(new Task page), The columnProvider.getColumns(task.projectId)) wasn't executing because the "task.projectId" parameter is a String, but the API needs an int.
I was confused because the method were being called by the previous page (A project Page with all the tasks) and the getColumn's argument was indeed an integer: int.parse(projectId).
The Kanboard API doesn't return an error code if the ID parameter is other than INT with this specific call "getColumns" (for some reason).
Of course, Flutter (or Dart) is waiting for a response from http.post that would never arrive. When comparing the two calls from the two pages, I noticed the difference.
So, in conclusion, I specified the int data type argument in the getColumn definition in order to avoid any confusion:
Future<List<ColumnModel>> getColumns(int projectId) async {
Best Regards!

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.