I have the following code where I generate a list of items(data is taken from Firebase). I would like to implement a functionality to remove items but I don't know how to access the list and how to remove items:
class _MyOfferState extends State<MyOffer> {
List<Widget> items = [];
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
...
body: SingleChildScrollView(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Offers')
builder: (BuildContext context, snapshot) {
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(name, category, context,...));
});
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
},
),
}
}
This is a dynamic class where I have GestureDetector. The item should be deleted when a user clicks on the it.
dynamic offer(name, category, context,) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: () {
// remove item should be here
},
);
}
Removing the offer from within itself is not the best practice but you can accomplish it in a number of ways. The first I can think of is to pass a function that removes it when creating the offer like this:
items.add(offer(name, category, context,..., () {
setState(() {
FirebaseFirestore.instance
.collection('Offers')
.doc(element['id'])
.delete();
items.remoev(index);
});
}));
You'll need to create the index beforehand and increase it each time but I don't recommend doing it.
The way I would done do this is change the offer to be:
dynamic offer(name, category, context,) {
return Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
);
}
And when creating the offer wrap it in the GestureDetector like this:
items.add(GestureDetector(
child: offer(name, category, context,...)),
onTap: () {
setState(() {
FirebaseFirestore.instance
.collection('Offers')
.doc(element['id'])
.delete();
items.remoev(index);
});
},
);
You'll have to do the same thing with the index but I consider it a better approach since the child has no power over the parent and can't change its state which is a good practice.
you need to pass index of item and delete by index:
int index = 0;
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(index, name, category, context,...));
index++;
});
Widget offer(int index, string name, string category, BuildContext context,) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: () {
// remove item should be here
items.removeAt(index);
setState((){});
},
);
}
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
Your list is getting build by Stream data the one you provided to your StreamBuilder, do create new list you need to change Stream value, I suggest to keep FirebaseFirestore.instance.collection('Offers') instance in a stream and modify the stream.
class _MyOfferState extends State<MyOffer> {
List<Widget> items = [];
StreamController _controller = StreamController();
#override
void initState() {
super.initState();
_controller.addStream( FirebaseFirestore.instance
.collection('Offers').snapshots());
}
// dont forgot to close stream
#override
void dispose() {
_controller.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
...
body: SingleChildScrollView(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: _controller.stream,
builder: (BuildContext context, snapshot) {
snapshot.data.docs.forEach((element) {
element.get('items').forEach((item) {
String _name = element['name'];
String _category = item['category'];
items.add(offer(name, category, context,(){
// remove function is here
snapshot.data.docs.removeWhere((e) => e.id == element.id);
_controller.add(snapshot.data);
});
});
}
);
}
return new Column(
children: List.unmodifiable(() sync* {
yield* items;
}()),
);
},
),
}
}
Also pass onTap to your widget function
dynamic offer(name, category, context, onTap) {
return GestureDetector(
child: Container(
child: Row(
children: [
Text(name),
Text(category),
],
),
),
),
onTap: onTap,
);
}
Related
I have a list like this a = [{'one': 'one', 'two': null, 'three': [{'four': 'four'}]}]
I send it to a function to use it in a post request which in the body should receive a Map, so what I did was this to a[0], the problem is that I get this error The getter 'length' was called on null
I start to review and it treats all the property values as if they were Strings, even the nested list 'three': [{'four': 'four'}], I have tried to send the post in this way http.post (url, body: (recurrence [0] as Map)) but it has not worked, it always gives me the same error, even if in the body I put the properties by hand in the body: {'new property': a [0] [' tres']}, how should one act to solve this problem? Thank you very much for your help
Code:
void _ordersGet() async {
await http.get(url).then((value) {
setState(() {
orders = jsonDecode(value.body);
}
}
orders is sent to a new widget: orderList(orders)
orderList is a listView
ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: orders.length,
itemBuilder: (orders, index) {
return return Card(
elevation: 5,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(orders[index]['facts']),
SizedBox(
width: 4,
),
Text('Cantidad : '),
Text(orders[index]['ITEMS'][0]['jeans']),
SizedBox(
width: 4,
),
IconButton(
onPressed: () => _reorderData(context, orders[index]),
icon: Icon(
Icons.replay_outlined,
color: Theme.of(context).accentColor,
)),
],
),
);
},
);
_reorderData is a function that make a get request, the info in shipped to ReorderModal
ReorderModal it only shows the information and has a button
void _reorderData(BuildContext ctx, order) async {
var data;
var url = 'serverAddress/${order['facts']}';
await http.get(url).then((value) {
data = jsonDecode(value.body);
data[0]['CORPORATION'] = order['corporation'];
showModalBottomSheet(
context: ctx,
builder: (_) {
return ReorderModal(data);
});
}).catchError((onError) {});
}
class ReorderModal extends StatelessWidget {
final List data;
ReorderModal(this.data);
void orderSend(orderInfo) async {
var url = 'serverAddress';
await http.post(url, body: orderInfo[0]).then((value) {
print(jsonDecode(value.body));
}).catchError((onError) {
print(onError);
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Column(
children: [
ElevatedButton(
onPressed: () {
orderSend(data);
//print(data);
},
child: Text('ONE Click'))
]),
);
}
}
when i press the ONE Click button execute the function orderSend, orderSend make a post request and the problem described above
This is the simplified code, I know it must be something very simple, but it is giving me a lot of work to solve
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
so i was creating this app which is an item catalogue for an online shop using StaggeredGridView, here i add a function to sort the item from a list, so that i can sort it by price or make it back to default sorting, but i encounter a problem where the list cant go back to default sorting when i select it, here is the code :
class ItemCatalogState extends State<ItemCatalog>{
sortedBy sorted = sortedBy.init;
bool isInit = true;
var selectedList = [];
void sortItem(List<Item> initialList){
switch (sorted) {
case sortedBy.lowestPrice:
setState(() {
selectedList.sort((a,b){return a.price.compareTo(b.price);});
});
break;
case sortedBy.highestPrice:
setState(() {
selectedList.sort((a,b){return b.price.compareTo(a.price);});
});
break;
case sortedBy.init:
//NOT WORKING
setState(() {
selectedList = initialList;
});
break;
default:
}
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
List<Item> itemList = Provider.of<List<Item>>(context);
if(isInit&&itemList!=null){
selectedList = itemList.map((e) => Item(image: e.image,name: e.name,isReady: e.isReady,price: e.price,seller: e.seller)).toList();
isInit = false;
}
sortItem(itemList);
return Container(
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: sorted,
items: [
DropdownMenuItem(
child: Text('Default',),
value: sortedBy.init,),
DropdownMenuItem(
child: Text('Lowest',),
value: sortedBy.lowestPrice,),
DropdownMenuItem(
child: Text('Highest'),
value: sortedBy.highestPrice,),
],
onChanged: (value){
setState(() {
sorted = value;
});
}),
),
),
Container(
decoration: BoxDecoration(
color: kGrayMainColor,
),
child: StaggeredGridView.countBuilder(
itemCount: selectedList!=null?selectedList.length:itemList.length,
crossAxisCount: 2,
itemBuilder: (context, index) {
Item currentItem = selectedList!=null?selectedList[index]:itemList[index];
return ItemTile(item: currentItem, size: size,);
},
),
),
),
]),]
);
}
It was supposed to call the original List when i click the default drop down, but nothing changed, could i possibly wrong at copying the list to selectedList? Thankyou and any advice regarding my other bad code practice is appreciated since im still learning.
Inside sortItems you set selectedList to be equal to initialList. From that point on, both variables are now pointing at the same collection of objects, which means anything you do to one, you will also do to the other. And since you are getting the collection through provider, these changes will also affect the original list that was provided.
Instead of a direct assignment, copy initialList again so that the two lists are never pointing to the same collection.
Incidentally, there's a much easier way to create copies of lists:
selectedList = List.from(initialList);
On another note, I'm assuming that the default value of sorted is sortedBy.init. That makes the initial copy within your build method redundant as the sortItems method immediately overwrites the value of selectedList. Instead, just depend on sortItems to generate your list without having to worry about initialization:
void sortItem(List<Item> initialList){
switch (sorted) {
case sortedBy.lowestPrice:
selectedList = List.from(initialList)
..sort((a,b) => a.price.compareTo(b.price));
break;
case sortedBy.highestPrice:
selectedList = List.from(initialList)
..sort((a,b) => b.price.compareTo(a.price));
break;
case sortedBy.init:
selectedList = List.from(initialList);
break;
}
setState(() {}); // Just call this once to reduce code duplication and verbosity
}
#override
Widget build(BuildContext context) {
...
List<Item> itemList = Provider.of<List<Item>>(context);
sortItems(itemList);
...
}
This is how I would solve it (I changed your List<Item> to a List<String> for simplicity, but you get the gist):
Load the original list into the _sortedList variable (initially in didChangeDependencies) via the Provider and repeat this whenever you are in need of the original list again.
(My Provider returns ['aa', 'dd', 'cc'], so I could get in that map() you do on the list as well :) )
enum SortOrder { ascending, descending, original }
class _TestState extends State<Test> {
var _sortedList = List<String>();
#override
void didChangeDependencies() {
super.didChangeDependencies();
_loadOriginalList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Ascending'),
onPressed: () => setState(() => _sortList(SortOrder.ascending)),
),
RaisedButton(
child: Text('Descending'),
onPressed: () => setState(() => _sortList(SortOrder.descending)),
),
RaisedButton(
child: Text('Original'),
onPressed: () => setState(() => _sortList(SortOrder.original)),
),
],
),
ListView.builder(
shrinkWrap: true,
itemCount: _sortedList.length,
itemBuilder: (context, index) => Center(
child: Text(_sortedList[index]),
),
),
],
),
);
}
void _sortList(SortOrder sortOrder) {
switch (sortOrder) {
case SortOrder.ascending:
_sortedList.sort();
break;
case SortOrder.descending:
_sortedList.sort();
_sortedList = _sortedList.reversed.toList();
break;
case SortOrder.original:
_loadOriginalList();
break;
}
}
void _loadOriginalList() {
final originalList = Provider.of<List<String>>(context, listen: false);
_sortedList.clear();
_sortedList.addAll(originalList.map((e) => e.substring(0, 1)));
}
}
Im making a list of radio buttons. But cant figure a way to map them. The current method making one by one which is too much and with 10+ radio buttons this is taking up hundreds of lines of code.
enum SingingCharacter {char1, char2, char3, char4}
class _HomeScreenState extends State<HomeScreen> {
SingingCharacter _character = SingingCharacter.char1;
final List myList= ['One','Two','Thre' ];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Container(
child: Column(children: <Widget>[
RadioListTile<SingingCharacter>(
title: Text('${myList[0]}'),
value: SingingCharacter.char1,
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
),
RadioListTile<SingingCharacter>(
title: Text('${myList[1]}'),
value: SingingCharacter.char2,
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
)
....
]),
));
}
}
Any method to loop through this and show in children?
Thanks
You could change your List to a Map and then use this to map your enum values to Widgets. Like this (disclaimer: Code not tested, but something like this should be possible):
enum SingingCharacter {char1, char2, char3}
class _HomeScreenState extends State<HomeScreen> {
SingingCharacter _character = SingingCharacter.char1;
final Map<SingingCharacter, String> radioMap = {SingingCharacter.char1: 'One', SingingCharacter.char2: 'Two', SingingCharacter.char3: 'Three'};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Container(
child: Column(
children: _generateRadioButtons()
),
)
);
}
List<Widget> _generateRadioButtons() {
return SingingCharacter.values.map((char) {
return RadioListTile<SingingCharacter>(
title: Text('${radioMap[char]}'),
value: char
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
);
}).toList();
}
}
You can take advantage of the spread operator and unwrap directly a for loop on your Column.
final List myList= ['One','Two','Thre' ];
return Column(children: [
for (int i = 0; i < myList.length; i++)
RadioListTile<SingingCharacter>(
title: Text(myList[i]),
value: SingingCharacter.values[i],
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
),
]);
Hello I am having issue to understand how to manage properties of a class when it is part of a list of classes
Here are my classes
class BasketItem {
final String itemDescription;
final String itemNote;
BasketItem(
{this.itemDescription,
this.itemNote});
}
class AppData {
static final AppData _appData = new AppData._internal();
List<BasketItem> basketList = [];
factory AppData() {
return _appData;
}
AppData._internal();
}
final appData = AppData();
And here is my List
List<Container> _buildBasketList() {
return appData.basketList.map((bList) {
var container = Container(
child: Builder(
builder: (context) => Dismissible(
key: Key(UniqueKey().toString()),
background: Container(
margin: EdgeInsets.all(8.0),
color: kColorAccent,
child: Align(
alignment: Alignment(-0.90, 0.00),
child: Icon(Icons.add_comment)),
),
onDismissed: (direction) {
final newItemToAdd = BasketItem(
itemDescription: bList.itemDescription,
itemNote: 'xxxxx',);
appData.basketList.add(newItemToAdd);
setState(() {});
appData.basketList.remove(bList);
},
child: Stack(...)
),
),
);
return container;
}).toList();
}
I would like to do the following: when onDismissed get executed I would like to amend the property itemNote to 'xxxxx'. How can I do it? At the moment I remove the BasketItem I have swiped and I create a new BasketItem and I add it to the list. The problem is that this does not seem efficient and it also add the item at the end of the list while I would like to keep it at the same position/index where it was.
Thanks
Approach 1
Make fields in BasketItem non final. So you can amend them.
class BasketItem {
final String itemDescription;
/*final*/ String itemNote;
BasketItem(
{this.itemDescription,
this.itemNote});
}
// onDismissed will change itemNote.
....
onDismissed: (direction) {
setState(() {
bList.itemNote = 'xxxxx';
});
},
...
Approach 2
Replace list contents inline. Don't remove and add
List<Container> _buildBasketList() {
return appData.basketList.asMap().map((index, bList) {
var container = Container(
child: Builder(
builder: (context) => Dismissible(
key: Key(UniqueKey().toString()),
background: Container(
margin: EdgeInsets.all(8.0),
color: kColorAccent,
child: Align(
alignment: Alignment(-0.90, 0.00),
child: Icon(Icons.add_comment)),
),
onDismissed: (direction) {
setState(() {
appData.basketList[index] = BasketItem(
itemDescription: bList.itemDescription,
itemNote: 'xxxxx',);
});
},
child: Stack(...)
),
),
);
return container;
}).toList();
}