Related
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.
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
I am creating a List in Flutter and displaying it in a Column, When I Run it is just Empty and when I print the list it just prints an Array
I/flutter (24613): []
I am using this code to create the List:-
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
This is My User Class:-
class User {
final String id;
final String username;
final String email;
final String photoUrl;
User({
this.id,
this.username,
this.email,
this.photoUrl,
});
factory User.fromDocument(DocumentSnapshot doc) {
return User(
id: doc.data()['id'],
username: doc.data()['username'],
email: doc.data()['email'],
photoUrl: doc.data()['photoUrl'],
);
}
}
The Code Is Showing No Errors and the Column Is not Displaying, Also When I print The length of the List it Shows it is Zero:-
I/flutter (24613): 0
What Could be The problem ??
I guess we need to tweak some of your code little bit to make the logic working. :)
builder param should be specified with Type otherwise it will be of type dynamic. To be in safer side in this case it will be QuerySnapshot. So,
builder: (context, snapshot) in your code becomes
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot).
Next, there is no need of looping through foreach and instead you can try something like below.
snapshot.data.docs.map((document) { .... }
snapshot.data.documents in your code is not valid way of getting the Firestore Documents. Please refer official doc
And you need to return a widget from builder which you have done correctly. But, by mistake you are passing the List<UserList> to Column which will be expecting List<Widget>
return Column (children: usersList);
Here I can see you are passing usersList which is of type List<UserList>. So you can replace Column with ListView or similar kind of other widget since, Column doesn't support scroll.
So combining all these bits and pieces you will get the below snippet.
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('timestamp', descending: true)
.limit(30)
.snapshots(), // Just for simplicity.
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//When there is no data returned from the firebase.
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.docs.map((document) {
return Text("Title: " + document['username']);
}).toList(),
);
},
);
For simplicity, I have returned the Text widget. You can implement your own UI there.
NOTE : This is the basic working example and you need to fine tune accordingly like using model classes instead of directly accessing based on your requirements.
Your Code
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
It is because you have to await for the json to actually get parse to the dart model. Second thing is forEach method is synchronous it doesn't wait for the async operation to complete, this is the reason why your list is empty.
This SO question has lot of different ways to make a list work asynchronously in flutter.
Column shows data before fetching data, so it shows empty list. For this use setstate according to your state management type ("notifylisteners" in provider) after getting data, so by this the screen will be updated and column also shows the updated list.
I'm not very sure how you're handling the scope of the variable.
Here's my minimal reproducible code which can give you some idea on how to add the items to the list.
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final List<Widget> _list = [FlutterLogo()];
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick >= 2) timer.cancel();
setState(() => _list.add(FlutterLogo()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: _list),
);
}
}
As 'Ashutosh patole' said, 'forEach' method does not wait iteration's complete.
I think that because of this reason, although you made a 'usersList',
there is no data when build widget in 'usersList'.
To fix this, you'd better change from 'forEach' to 'for'.
void main() async {
List<String> data = [ 'a', 'b', 'c'];
List<String> result = [];
data.forEach((data) async {
await Future.delayed(Duration(seconds: 1));
result.add(data);
});
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
print('-----------------');
result = [];
for (var item in data) {
await Future.delayed(Duration(seconds: 1));
result.add(item);
}
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
}
In your code, you can change like below.
List<UserList> usersList = [];
for (var doc in snapshot.data.documents) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
}
Before calling the data, check all fields:
Firestore Docs
Add a print() to see where the problem
FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
//This
if (snapshot.hasError) {
return Text("Something went wrong");
}
print(snapshot.data);
//This
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
print(snapshot.data);
//This
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
This is what i typically use.Try out this! Please balance the brackets in the code
FutureBuilder(
future: users.orderBy('timestamp', descending: true).limit(30),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
List<User>ulist=snapshot.data;
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(top: 25,bottom: 35),
itemCount: evlist==null?0:evlist.length,
itemBuilder: (BuildContext context, int index) {
String evtime=evlist[index].fromdate.substring(11,16);
String ontime=evlist[index].fromdate.substring(0,16);
return Container(
decoration: BoxDecoration(
border: Border.all(width: 1.8,color: Colors.indigo[900]),
borderRadius: BorderRadius.circular(12.0),
color: Colors.grey[200]
),
margin:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 4.0),
child: ListTile(
leading: Icon(Icons.notifications),
title: Text(ulist[index].username.toString()),
subtitle:Text("next data"),
),
);
},
);
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
I tried to create a list of items in an order cart. But it shows only the first item in the list....
Here is my code:
.......
class CartItem {
String name;
String quantity;
String price;
CartItem({
this.name,
this.quantity,
this.price,
});
}
List<CartItem> cartItem = [];
var _quantity = TextEditingController();
........
Widget cartList() {
if (cartItem.length != 0) {
for (var item in cartItem) {
return new Text('${item.name}');
}
}
return Text('Nothing in Cart');
}
.......
RaisedButton(
elevation: 1,
color: Colors.blueGrey,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext contex) {
return AlertDialog(
content: Column(
children: [
Text('Your Order List'),
Container(
child: cartList(),
),
],
),
);
},
);
},
child: Text(
'Conferm Order',
textAlign: TextAlign.end,
),
),
I also tried with listview.builder but I have failed. It gives me some error. Something like "Assertion failed" or "Rendering" type error.
Widget cartList() {
if (cartItem.length != 0) {
return ListView.builder(
itemCount: cartItem.length,
itemBuilder: (context, index) {
return Text(cartItem[index].name);
},
);
}
return Text('Nothing in Cart');
}
this is the error when I try with Listiew.builder:
The following RenderObject was being processed when the exception was fired:
RenderIntrinsicWidth#3fbd2 relayoutBoundary=up6 NEEDS-LAYOUT NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE:
creator: IntrinsicWidth ← Semantics ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
_InkFeatures-[GlobalKey#eb4f8 ink renderer] ←
NotificationListener ←
CustomPaint ← _ShapeBorderPaint ← PhysicalShape ← _MaterialInterior ← Material ←
ConstrainedBox ←
⋯
parentData: (can use size)
constraints: BoxConstraints(280.0<=w<=711.0, 0.0<=h<=343.0)
size: MISSING
stepWidth: null
stepHeight: null
This RenderObject had the following descendants (showing up to depth 5):
child: RenderFlex#22eec NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
child 1: RenderPadding#42cc4 NEEDS-LAYOUT NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
child: RenderFlex#09e2b NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
child 1: RenderParagraph#afac8 NEEDS-LAYOUT NEEDS-PAINT
text: TextSpan
child 2: RenderRepaintBoundary#14459 NEEDS-LAYOUT NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
child: RenderCustomPaint#ddfde NEEDS-LAYOUT NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
And this:
Another exception was thrown: Assertion failed:
file:~flutter/packages/flutter/lib/src/rendering/mouse_tra
cking.dart:392:12
So first off, the moment you return in almost any programming language, you go out of the function. So you won't get a list but only a single Text widget.
And about the ListView.builder, you're missing the .name (if you want to show more things like price you have to use another widget or concatenate them into a single string since Text only accepts a String):
Widget cartList() {
if (cartItem.length != 0) {
return ListView.builder(
itemCount: cartItem.length,
itemBuilder: (context, index) {
return Text(cartItem[index].name);
},
);
}
return Text('Nothing in Cart');
}
Instead of Text widget you could potentially use ListTile which has placeholders for many Text widgets usually useful for lists.
Now for the layout error, the simplest fix would be to add height and width properties for the Container containing your cartList:
return AlertDialog(
content: Column(
children: [
Text('Your Order List'),
Container(
height: 300, // or any other number
width: 300, // or any other number
child: cartList(),
),
],
),
);