I am working with Flutter and am struggling to remove a Dismissible object from the tree. Below is my code. I have created a custom class that is stored in the list 'newlist.' I seemingly remove the Dismissible object from the List and setState(), but it does not seem to work. Any help is greatly appreciated.
return new Dismissible(key: new Key("newlist"),
direction: DismissDirection.horizontal,
onDismissed: (DismissDirection direction) {
setState(() {
newlist.remove(newlist[index]);
print(newlist.length);
});
},
child: new ListTile(
leading: const
Icon(Icons.album),
title: new Text(newlist[index].amount),
subtitle: new Text(
newlist[index].name)));
})),
I have solved it using items name + lists length as a key. Because there could be some items with the same value
return Dismissible(
key: Key(item.name + _paths.length.toString()),
onDismissed: (direction) {
setState(() {
_paths.removeAt(index);
});
// Show a red background as the item is swiped away
background: Container(color: Colors.red),
child: Container(child: new Texts().tallText(item.name)),
);
I solved it. Essentially, I was using the same Key for every Dismissable. This makes Flutter think that the object I dismissed is still there. Hope this helps someone.
Yes, It because of Key only.
key: new Key("newlist") - wrong
it should be:
key: Key(newlist[index])
Related
I’m making a conversation starter app and inside this app, there are different categories of questions a user can choose from. This is how the home page of the app looks like after the user logs in:
The way I’m currently listing all these categories is by saving the category names as the document ID’s under a collection I call ‘users’. Then I use the following snippet of code to get all these document IDs/ categories and add them to a List. I then use a FutureBuilder to convert this List<String> to a List of buttons. The code below can help clarify what I am doing:
Step 1: get all document IDs/category names:
List<String> questionCategories = [];
Future getCategories() async {
await FirebaseFirestore.instance
.collection('users')
.get()
.then((snapshot) => snapshot.docs.forEach(
(document) {
questionCategories.add(document.reference.id);
));
}
Step 2: Use the questionCategories List<String> to create a List of buttons
FutureBuilder(
future: getCategories(),
builder: (context, snapshot) {
return SizedBox(
height: MediaQuery.of(context).size.height - 250,
child: ListView.builder(
itemCount: questionCategories.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: //questionPageInit,
() {
print(collectionList);
Navigator.push(context,
MaterialPageRoute(builder: (context) {
//return ForgotPasswordPage();
return CategoryPage(
categoryName: questionCategories[index],
);
}));
},
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(questionCategories[index],
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
))))),
);
},
),
);
},
),
Upon picking a category, questions are displayed one at a time on a question card, wherein below this card a user can switch between the next and previous questions and then shuffle. This page looks like so:
The way I’m getting these questions displayed is by getting a List of all the fields under a document ID and adding it to a List<String>. When the user presses shuffle, next, or previous, I just change a global index variable and set the state again to display a new question based on which question appears to be at that specific index in the List. The following code should help clarify what I am doing:
void printAllQuestionsList(snapshot) {
Map<String, dynamic> data = snapshot.data() as Map<String, dynamic>;
for (String key in data.keys) {
print(key + data[key]!);
questionsList.add(data[key]);
}
}
Future getQuestionList() async {
if (questIndex > 1) {
return;
}
if (widget.categoryName == "ALL") {
await FirebaseFirestore.instance
.collection('users')
.get()
.then(((snapshot) => snapshot.docs.forEach((document) {
print(document.reference.id);
FirebaseFirestore.instance
.collection('users')
.doc(document.reference.id)
.get()
.then((snapshot) => {printAllQuestionsList(snapshot)});
})));
} else {
await FirebaseFirestore.instance
.collection('users')
.doc(widget.categoryName)
.get()
.then((snapshot) => {printQuestionList(snapshot)});
}
}
Inside the widget Build function, I have this snippet of code:
FutureBuilder(
future: getQuestionList(),
builder: ((context, snapshot) {
// return TextField(
// decoration: InputDecoration(
// enabledBorder: OutlineInputBorder(
// borderSide: BorderSide(
// width: 5, //<-- SEE HERE
// color: Colors.greenAccent,
// ),
// borderRadius: BorderRadius.circular(50.0),
// ),
// ),
// );f
return Container(
margin: const EdgeInsets.all(15.0),
padding: const EdgeInsets.all(10.0),
width: 350,
height: 350,
decoration: BoxDecoration(
color: Colors.deepPurple[200],
borderRadius:
BorderRadius.all(Radius.circular(20))
// border: Border.all(color: Colors.blueAccent)
),
child: Align(
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Text(
questionsList[index],
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 32,
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
),
),
));
}))
I have a second page on this app that is used to submit questions to an existing or new Category (if they enter the right password that I set).
I use the following snippet of code to do so:
Future addQuestion(String category, String question) async {
var usersRef = questionCollection.doc(category);
await FirebaseFirestore.instance
.collection('users')
.get()
.then((snapshot) => snapshot.docs.forEach(
(document) {
existingQuestionCategories.add(document.reference.id);
},
));
if (existingQuestionCategories.contains(category)) {
print("Document Exists! ");
questionCollection.doc(category).update({question: question});
} else {
// FieldPath pathfield = FieldPath.fromString(category);
//String category2 = pathfield.category;
print('No such document exists so now about to set document anew!');
print(category);
FirebaseFirestore.instance
.collection("users")
.doc(category)
.set({question: question});
}
}
Here’s how my Firestore database is organized
Users -> Question Categories (Document IDs) -> question Key: question field
This is how I want to set it up:
Users -> Autogenerated ID -> Question Categories as collections -> question key (titled “question”): question key (“the actual question here)
This way under each collection I can also list fields pertaining to the question like if it’s light, medium, or deep that I may be able to add on to later.
I also want to do it this way because sometimes when I try to use my submit question page, the question I type does not get submitted and I think it may be because I’m submitting the question under a document ID and not under a collection.
In summary, my question to you is how do I list all the questions on my home page as a list of collections from my database? Also, how would this change the code I wrote to (1) view the questions on individual cards when clicking a category name and (2) submit new questions to a specific category/collection?
If what I’m trying to do cannot be done in the way I want it done, is there a more efficient way to do this?
I tried searching for how to get a list of collections on Firestore on Flutter but all the answers I found gave me a solution on how to get a List of fields under a document ID. This is why I'm asking this question.
Actually the Firebase SDK for Flutter ( and I'm assuming that for Android/IOS) doesn't have any pre-built methods to get a List of all collections in the firestore database.
But, as I know you can get them with a cloud function written as example with Node.js, refer to this and this.
if you're willing to write a cloud function to achieve this on your flutter project, then it's fine.
However, I can think about a practical solution, if that interest's you:
Create another collection/document where you list your firestore collections, for your precedent collections, I guess you have no option but to add them manually, but if you're creating new collections for the future in your project, you can implement general methods that check for the existence of a collection name, and act based on it.
Hello I'm still learning but cant find it for some reason,
My question is: I want to make a button that on press creates a new container and organizes it in a listview widget, how do I do that? do you have a tutorial or article about it that can help me?
Create a list:
List<Widget> widgets = [
widget1,
widget2,
],
Change the ListView's children to widgets:
ListView(
children: widgets,
),
Make a button that when pressed creates a new Container:
TextButton(
onPressed: () {
widgets.add(Container(child: child));
},
child: Text("Create a new container"),
),
You have to initialize list of type Widget
List<Widget> widget=<Widget>[];
On Button Tap call the following method:
void addContainer(){
setState(() {
widgetList.add(Padding(
padding: const EdgeInsets.all(8.0),
child: Container(height: 20,width: 30,color: Colors.red,),
));
});
}
Add Following code for create your list:
ListView.builder(
itemCount: widgetList.length,
itemBuilder: (BuildContext context, int index) {
return
widgetList.isNotEmpty?
widgetList.elementAt(index):SizedBox();
},
),
Without example I would say the easier way would be to have an array with the widget you want in your listview, then on click add an element in your list with setState.
List<Widget> views = [Text("test"), Text("test2")];
then
ListView(children: views)
and final a function to call with your button
void addContainer()
{
setState((){
views.add(Container(color: Colors.red));
});
}
I did not try it so it might contain some typos, but you get the logic.
Solved see the answers
I am using flip card package to make flip cards.
I have many cards in the same page and I want to flip them all when I press a button.
I used the example in the documentation :
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
#override
Widget build(BuildContext context) {
return FlipCard(
key: cardKey,
flipOnTouch: false,
front: Container(
child: RaisedButton(
onPressed: () => cardKey.currentState.toggleCard(),
child: Text('Toggle'),
),
),
back: Container(
child: Text('Back'),
),
);
}
but I get error Duplicate GlobalKey detected in widget tree. or Multiple widgets used the same GlobalKey
So what I can do to solve this problem ?
I solved this problem with making a map of global keys
var cardKeys = Map<int, GlobalKey<FlipCardState>>();
and in the ListView.builder in itemBuilder I added
cardKeys.putIfAbsent(index, () => GlobalKey<FlipCardState>());
GlobalKey<FlipCardState> thisCard = cardKeys[index];
and in the FlipCard I added key: thisCard
Then I make a simple for loop in the button onPressed function
RaisedButton(
onPressed: () {
for (int i = 0; i < names.length; i++) {
cardKeys[i].currentState.toggleCard();
}
},
child: Text('Toggle'),
),
Thanks to this answer here
I am trying to build a sample with reorderable listview that has expansion tile as its child.
Upon expanding the tile, it will present a listview to the user as follow
Expanded tile with listview nested inside
When all expansion tile are collapsed, i have no issue reordering the tiles by long pressing and moving it. But if one of the tiles are expanded, and user try to reorder the tiles, flutter will throw the following error and the expanded tile will not be able to collapse until hot reload
ScrollController attached to multiple scroll views.
'package:flutter/src/widgets/scroll_controller.dart':
Failed assertion: line 111 pos 12: '_positions.length == 1'
Not Collapsible listview
How should I go about fixing it? The issue seems to stem from having a scroll controller nested in another scroll controller. Is there a way of forcing all expansion tile to collapse upon long pressing it?
Thanks in advance
List<int> a = [1, 2, 3];
class _BlankPageState extends State<BlankPage> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Padding(
padding: EdgeInsets.all(10),
child: ReorderableListView(
onReorder: (oldIndex, newIndex) {
print('now');
setState(
() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final int item = a.removeAt(oldIndex);
a.insert(newIndex, item);
},
);
},
children: a.map((index) {
return ExpansionTile(
backgroundColor: Colors.grey,
key: Key('$index'),
title: Text('Tile' + '${index.toString()}'),
children: <Widget>[
Container(
height: 100,
child: ListView(children: <Widget>[
Text('This is a test' + '$index'),
Text('This is a test' + '$index'),
]),
)
],
);
}).toList()),
),
),
);
I was able to solve the above issue with the new release of Flutter 1.17 which introduced the following
Change log for Flutter 1.17.0
49148 Exposed optional scrollController property in ReorderableListView
By adding a scroll controller in my reorderablelistview, I no longer encounter the multiple scroll views error above when a list view is nested inside a reorderablelistview widget
ReorderableListView(
scrollController: ScrollController(initialScrollOffset: 50),
When I want to do "Extract to widget", it raises an error : "reference to an enclosing class method cannot be extracted"
I know there is some variables that must get their data from class constructor but I want Android studio to extract the widget then, I will correct the mistaken codes, like Visual Studio that without any error extract the code to a new widget then it needs to copy the new extracted widget to a new dart file and correct the mistakes.
I want to extract the Card widget part.
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as prefix0;
import 'package:intl/intl.dart';
import '../model/transaction.dart';
class TransactionList extends StatelessWidget {
final List<Transaction> transactions;
final Function deleteTx;
TransactionList(this.transactions, this.deleteTx);
#override
Widget build(BuildContext context) {
return transactions.isEmpty
? LayoutBuilder(
builder: (ctx, constraint) {
return Column(
children: <Widget>[
Text(
'There is no transaction',
style: Theme.of(context).textTheme.title,
textDirection: prefix0.TextDirection.rtl,
),
SizedBox(
height: 10,
),
Container(
height: constraint.maxHeight * 0.6,
child: Image.asset(
'assets/images/yalda.png',
fit: BoxFit.cover,
))
],
);
},
)
: ListView.builder(
itemCount: transactions.length,
itemBuilder: (ctx, index) {
return **Card**(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 5),
elevation: 5,
child: ListTile(
leading: CircleAvatar(
radius: 30,
child: Padding(
padding: const EdgeInsets.all(8),
child: FittedBox(
child: Text('\$${transactions[index].amount}')),
),
),
title: Text(
transactions[index].title,
style: Theme.of(context).textTheme.title,
),
subtitle: Text(DateFormat.yMMMd()
.format(transactions[index].date)
.toString()),
trailing: MediaQuery.of(context).size.width > 360
? FlatButton.icon(
onPressed: () => deleteTx(transactions[index].id),
icon: const Icon(Icons.delete),
label: const Text('Delete'),
textColor: Theme.of(context).errorColor,
)
: IconButton(
icon: const Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () => deleteTx(transactions[index].id),
),
),
);
});
}
}
Simply use "Extract Method" instead of "Extract Widget". VSCode will add all the returns and references.
Edit: If you want to use "Extract Widget" only then simply wrap that widget in a Container and then use "Extract Widget" on that widget.
If that doesn't work, comment out setState() function inside the widget and try again.
Your deleteTx might contain a setState(() {}) method, try to comment that part of your code where you're calling deleteTx it and just put it back after your extraction.
Just remove or comment the setState() {} from your widget and it gonna works.
transform onpressed etc. property to comments and then try again 'Extract Widget' and go on
I had the same issue and in my case it was because of ListView.builder as yours.
So it is easy to fix, Simply make a class and return Card in Widget build and return it in ListView.builder inside the TransactionList class with the desired arguments.
You have to care about a few things:
Whenever you are extracting a widget, that widget should not contain any variable which is used in the current working page.
All the functions or methods should be parametric.
It is because you are referencing a variable(for example, transactions) in your Card widget from the enclosing class i.e. TransactionList. The best way to extract in this case could be to just make stateless/stateful widget outside your class and cut the Card widget and paste it as the return type of the build method of that Widget you created. And you can reference those variables using the constructor of that widget you created.
If you can comment out deleteTx(transactions[index].id) parts of your code and then use onPressed: (){}, you will be able to extract to widget.
After the extraction you can use:
onPressed: (){
setState(() {
deleteTx(transactions[index].id);
});
}
there might be some local referance to the variable in the current class,so if there some referance we cant extract a widget from there.
You can use the "Extract Method". It's a simple way. VSCode will add all the returns and references.
If we extract the widget it will StatelessWidget. StatelessWidget doesn't support changing state so if you use any Onchange property SetState it never extract
so please remove setState(() {});
Just remove or comment the setState() {} from your widget
Or
Just remove or comment the MediaQuery.of(context).size.width from your widget