Manage items in a List with their ids - list

Let me explain, I have two List Views with Items. At the moment I don’t give an id to these items but I have a problem. In fact, when I remove an item from my favorites list, it doesn’t change the icon (favorite or not favorite) for the right item on the home_screen.
I want to get the place of the item in the menu screen so I can change the icon from the favorites list. I’m using the provider package.
And so I wonder if it wouldn’t be better to create an id for each item and store a List<int> and then create a List<Item> in my favorites list. Also, I can use this id to change the right icon.
But I don’t know how to use these ids to create a List and then change the right icon.
Illustrations of what I said :
Black heart = in favorite and White heart = not in favorite.
It is the wrong item which is deleting.
My code on Github ans some relevant parts of my code :
favModel.dart
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
addInFavorite(title, description, index){
Item item = Item(title: title, description: description, );
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
notifyListeners();
}
implement(){
isInFav.add(false);
}
}
favorite_screen.dart
class Favorite extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorite'),
),
body: Consumer<FavModel>(
builder: (context, favModel, child) {
return ListView.builder(
itemCount: favModel.favList.length,
itemBuilder: (context, index) {
return TextObject(favModel.favList[index].title,
favModel.favList[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
Icons.favorite,
color: Colors.red,
size: 32,
),
onTap: () {
favModel.removeOfFavorite(index, index);
}),
),
});
},
),
);
}
}
home_screen.dart
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return Favorite();
},
),
),
),
],
),
body: Consumer<FavModel>(builder: (context, favModel, child) {
return ListView.builder(
shrinkWrap: false,
itemCount: itemData.length,
itemBuilder: (context, index) {
favModel.implement();
return TextObject(
itemData[index].title, itemData[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
favModel.isInFav.elementAt(index)
? Icons.favorite
: Icons.favorite_border,
color:
favModel.isInFav[index] ? Colors.red : null,
size: 32,
),
onTap: () {
favModel.isInFav[index]
? null
: Provider.of<FavModel>(context,
listen: false)
.addInFavorite(
itemData[index].title,
itemData[index].description,
index,
);
}),
);
});
}),
);
}
}
Where I want to get the index is in the favorite_screen.dart at this line favModel.removeOfFavorite(index, index);

I would suggest you to add bool isFavorite to your class Item and add an id for the class also. So you can avoid having two arrays.
And using the id will help you using some awesome methods like findWhere and removeWhere
EDIT
You can iterate the List using for
for(int i = 0;i<favList.length;i++){
if(favList[i].id == selectedItem.id){
favList[i].isSelected = true;
break;// break the loop no need to continue
}
}
notifyListeners()
Notice that now you have to pass Item instead of index

Related

How can I make my search history not have the same elements repeated many times in flutter/dart?

I'm trying to do a search history using a search delegate but I'm having a problem.
When I perform a search, that element can appear several times in the history and what I want is that it not be repeated.
If I search 3 times for the same person, in the search history it appears 3 times
And I only want it to appear once.
How could I do it?
help would be appreciated.
Code and image::
class MPState extends State<MP>{
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: new Scaffold(
resizeToAvoidBottomInset : false,
appBar: AppBar(
title: Text("App"),
backgroundColor: Colors.redAccent,
elevation: 0,
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () async{
final busqueda = await showSearch(
context: context,
delegate: SearchDelegateMP("Buscar...",this.historialMenuPrincipal)
);
if(busqueda != null ) {
if (this.historialMenuPrincipal.length > 0) {
// this.historialMenuPrincipal.add(busqueda);
/*historialMenuPrincipal.removeWhere((item) =>
item.email == busqueda.email); // si elimina*/
for (int i = 0; i < historialMenuPrincipal.length; i++) {
if(historialMenuPrincipal[i].email== busqueda.email){
print(historialMenuPrincipal[i].email);
break;
}else{
this.historialMenuPrincipal.add(busqueda);
break;
}
}
}else{ this.historialMenuPrincipal.add(busqueda);}
}
}
class SearchDelegateMPextends SearchDelegate<SearchDelegateM>{
#override
List<Widget> buildActions(BuildContext context) {
return [
//code
];
}
#override
Widget buildResults(BuildContext context) {
//code
}
Widget buildSuggestions(BuildContext context) {
return StatefulBuilderSuggestions(context,this.historialMenuPrincipal);
}
Widget StatefulBuilderSuggestions(BuildContext context ,List <SearchDelegateM> historialMenuPrincipal){
return Container(
child:StatefulBuilder(
builder:(context,setState)
{
return Container(
child: ListView.builder(
itemCount: historialMenuPrincipal.length,
itemBuilder: (context,i)
{
contentPadding: EdgeInsets.symmetric(vertical: 12,horizontal: 16);
leading:CircleAvatar(
radius: 32,
backgroundImage: NetworkImage(
"https://2.bp.blogspot.com/-3ZzNt8ZsjQk/WR9W4IFn4II/AAAAAAAAAJw/_inTVynhS60V7F5IZ-461-pda7WArTStwCEw/s1600/ANA.jpg"),
);
return
ListTile(
title: Text(historialMenuPrincipal[i].email ),
trailing: IconButton(
icon: Icon(Icons.cancel,color: Colors.black,),
onPressed: () {
setState(() {
historialMenuPrincipal.remove(historialMenuPrincipal[i]);
});
},)
);
}
),
);
}
)
);
}
enter image description here
Empty your list with every new search, before you start adding to it.
this.historialMenuPrincipal.clear();
What is happening is that the result is being added n number of times, even if the result is already there from previous searches.
N = the number of times the search is matched.
List can have repeated elements. You can parse your list to Set as set only contains unique elements.
List <SearchDelegateM> uniqueElementsList = historialMenuPrincipal.toSet().toList();
use this code before showing your elements in Listview.builder() and use uniqueElementsList in your builder.

How to display tasks that are not "checked" on the other screen?

I am looking at my code and wondering for 2 hours now without luck so I will ask for help here.
I have a button, when I press it, it displays a random item from the list view. The problem is I also have a check box on the list view with each item. I do not want it to (Shuffle through the items with the checkbox ticked) only to shuffle through the Task in the list view that are unchecked/unticked/are not done.
Here is my code
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: "item1"),
Task(name: "item2"),
Task(name: "item3"),
];
UnmodifiableListView<Task> get tasks {
return UnmodifiableListView(_tasks);
}
int get taskCount {
return _tasks.length;
}
// <<Here is the code that shuffles through list
Future<String> rann() async {
return (_tasks.toList()..shuffle()).first.name;
}
void addTask(String newTaskTitle) {
final task = Task(name: newTaskTitle);
_tasks.add(task);
notifyListeners();
}
void updateTask(Task task) {
task.toggleDone();
notifyListeners();
}
In another script I have this one
class Task {
final String name;
bool isDone;
Task({required this.name, this.isDone = false});
void toggleDone() {
isDone = !isDone;
}
}
In another script file I have this code
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 0),
child: FutureBuilder(
future: Provider.of<TaskData>(context).rann(),
builder: (context, snapshot) {
return Align(
alignment: Alignment.center,
child: Text(
"${snapshot.data}",
//softWrap: true,
textAlign: TextAlign.center,
//textWidthBasis: TextWidthBasis.longestLine,
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w700),
),
);
},
),
),
In another script I have this one
class TasksList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(
builder: (context, taskData, child) {
return ListView.builder(
itemBuilder: (context, index) {
final task = taskData.tasks[index];
return TaskTile(
taskTitle: task.name,
isChecked: task.isDone,
checkboxCallback: (checkboxState) {
taskData.updateTask(task);
},
);
},
itemCount: taskData.taskCount,
);
},
);
}
}
Any help would be appreciated!
Edit : I also forgot to include this part of code
class TaskTile extends StatelessWidget {
final bool isChecked;
final String taskTitle;
final Function(bool?) checkboxCallback;
final VoidCallback longPressCallback;
TaskTile(
{required this.isChecked,
required this.taskTitle,
required this.checkboxCallback,
required this.longPressCallback});
#override
Widget build(BuildContext context) {
return ListTile(
onLongPress: longPressCallback,
title: Text(
taskTitle,
// at the bottom, it sets decoration of text if isChecked is true, if its not its null
style: TextStyle(
decoration: isChecked ? TextDecoration.lineThrough : null),
),
trailing: Checkbox(
activeColor: Colors.blue,
value: isChecked,
onChanged: checkboxCallback,
),
);
}
}
updated:
class TaskData extends ChangeNotifier {
List<Task> _undoneTasksShuffled = []
// you don't need anymore the rann method() instead you should iterate over this listView
UnmodifiableListView<Task> get undoneTasksShuffled => UnmodifiableListView<Task>(_undoneTasksShuffled);
#override
void notifyListeners() {
//this updates _undoneTasksShuffled every time you call notifyListeners
_undoneTasksShuffled = _tasks.where((e)=> !e.isDone).toList()..shuffle();
super.notifyListeners();
}
...
}
I think you only need to filter the results before get a random element. you need to modify your rann method for something like
//you don't really need a future method because you don't have async code
String rann() {
final r = Random();
final undoneTasks = _tasks.where((e)=> !e.isDone).toList();
//this is for avoid RangeException on list. you can return any other thing
if(undoneTasks.isEmpty) return '';
// i think that you don't really need to shuffle whole list, you only need a random element
return undoneTasks[r.nextInt(undoneTasks.length)].name;
}
i hope this solves your question

Flutter, How to get Image URL from Firestore for respective Index Items and show in a list View

I am trying to create an app which displays a dynamic FireStore List. I have made the List with a Future Builder, what I am trying to achieve is to add a Icon to every list of which the URL should be fetched from firesotre of the same documents Index. For Eg. Flipkart is a Document in FireStore it has a "images" field with the URL to Flipkart logo. I want the List View "leading" property to display this image by getting the URL from FireStore Database.
ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Card(
margin: EdgeInsets.fromLTRB(10, 2, 10, 2),
elevation: 3,
borderOnForeground: true,
child: ListTile(
title: Text(snapshot.data[index].data['title']),
subtitle: Text(snapshot.data[index].data['description']),
onTap: () => navigateToDetails(snapshot.data[index]),
),
);
});
The Code would go something like
ListTile(
leading: "Code Goes Here"
title: Text(snapshot.data[index].data['title']),
subtitle: Text(snapshot.data[index].data['description']),
Please share your valuable thoughts. I would like this to work, New to programming.
Full Code For FireStore Get Data:
class OfferScroll extends StatefulWidget {
#override
_OfferScrollState createState() => _OfferScrollState();
}
class _OfferScrollState extends State<OfferScroll> {
Future _data;
Future getOffers() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection('Offers').getDocuments();
return qn.documents;
}
navigateToDetails (DocumentSnapshot offers) {
Navigator.push(context, MaterialPageRoute(builder: (context) =>
OfferDetails(offers: offers,)));
}
#override
void initState() {
super.initState();
_data = getOffers();
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: _data,
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: AwesomeLoader(
loaderType: AwesomeLoader.AwesomeLoader3,
color: Colors.green[900],
),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Card(
margin: EdgeInsets.fromLTRB(10, 2, 10, 2),
elevation: 3,
borderOnForeground: true,
child: ListTile(
title: Text(snapshot.data[index].data['title']),
subtitle: Text(snapshot.data[index].data['description']),
onTap: () => navigateToDetails(snapshot.data[index]),
),
);
});
}
}),
);
}
}
When you have the URL as a string in your FireStore you can retrieve the URL with someone like this: snapshot.data[index].data['imageFieldName'] and then you can give the return of this (the URL) to a NetworkImage to display the image.

Search bar with ListView possible in Flutter?

I want to implement a searchbar in my flutter application. I have to go through a listview out of ListTiles. Here I want to check if the title of the listtile contains the letters in the search field. Is this possible with a List?
It does not have to be with the title. It could be something else with what I can identify the Tile. But please, not the index, the user would not know it.
Is a List the right widget or do I have to use something else to implement a search Engine in my Application
Rather than using a 3rd party package, you can use native showSearch() function :
showSearch(context: context, delegate: ListSearchDelegate());
And then a class extending SearchDelegate:
class ListSearchDelegate extends SearchDelegate{
ListSearchDelegate({Key key,}): super() ;
List<String> listItems = <String>['One', 'Two', 'Three', 'Four', 'Five'] ;
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
List<String> subList ;
subList = query != '' ? listItems.where((item) => item.contains(query)).toList() :
listItems ;
return ListView.builder(
itemCount: subList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(subList[index]),
);
}
);
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
Try https://pub.dev/packages/flutter_search_panel
List<SearchItem<int>> data = [
SearchItem(0, 'This'),
SearchItem(1, 'is'),
SearchItem(2, 'a'),
SearchItem(3, 'test'),
SearchItem(4, '.'),
];
FlutterSearchPanel<int>(
padding: EdgeInsets.all(10.0),
selected: 3,
title: 'Demo Search Page',
data: data,
icon: new Icon(Icons.check_circle, color: Colors.white),
color: Colors.blue,
textStyle: new TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20.0, decorationStyle: TextDecorationStyle.dotted),
onChanged: (int value) {
print(value);
},
),

Cannot map a list to a widget

I have a hardcoded list that I want to map it to a list of widgets. The code below shows squiggly lines with the error The return type 'List<ItemWidget>' isn't a 'Widget', as defined by anonymous closure.dart(return_of_invalid_type_from_closure).
.................MISSING CODE....................
ListView.builder(
itemBuilder: (context, index) => items.map((item) => ItemWidget(item: item)).toList();
)
..................MISSING CODE....................
class ItemWidget extends StatelessWidget{
final Item item;
ItemWidget({this.item});
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: FittedBox(
fit: BoxFit.fill,
child: Image.asset(item.iconPath)
)
),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Text(item.name),
)
],
);
}
}
EDIT:
This is the list of items, currently I hold just an item for testing purposes.
List<Item> items = [
Item(name: 'Medicines', iconPath: '$ICON_BASE_PATH/medicines.svg'),
];
If you have any idea please let me know, thanks!
The issues is using the ListView.builder, the builder function expects you to return one Widget at a time corresponding to the index provided. Use ListView directly instead.
Example:
ListView(
children: items.map((item) => ItemWidget(item: item)).toList(),
);
Hope that helps!
If you want to use ListView.builder then you can use as following. This may helps you.
body: ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (BuildContext context, int index) {
return ItemWidget(
item: items[index],
);
},
),