Flip all cards when clicking a button using flip_card flutter - android

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

Related

How do I make a button that adds a container with specific info inside in flutter

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.

Reference to an enclosing class method cannot be extracted on refactoring

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

ListView displays items twice

I am trying to build a listview in flutter with the following :
The expected functionality is the listview should display 1 item at a time.
class SimpleContentScreen extends StatefulWidget {
#override
_SimpleContentScreenState createState() => _SimpleContentScreenState();
}
class _SimpleContentScreenState extends BaseState<SimpleContentScreen> {
List<SimpleContent> simpleContentList;
List<SimpleContent> displayList = List();
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
simpleContentList = getOOFirstContent();
displayList.add(simpleContentList[_currentIndex]);
return Scaffold(
appBar: buildAppBar("Introduction"),
body: _buildListView(),
floatingActionButton: _buildFab(),
);
}
FloatingActionButton _buildFab() => FloatingActionButton(
onPressed: () {
if( _currentIndex < simpleContentList.length - 1 ) {
setState(() {
_currentIndex = _currentIndex + 1;
displayList.add(simpleContentList[_currentIndex]);
});
}
},
child: Icon(Icons.navigate_next),
foregroundColor: Colors.white,
backgroundColor: Colors.blueGrey,
);
ListView _buildListView() => ListView.builder(
key: Key("_simple_content_list"),
itemCount: displayList.length,
itemBuilder: (context, position) {
return _buildItemView( displayList[position] );
}
);
_buildItemView(SimpleContent displayList) => Container(
padding: const EdgeInsets.all(12),
margin: EdgeInsets.fromLTRB(0, 8, 32, 8),
decoration: BoxDecoration(color: Colors.blueAccent),
child : new Text(
displayList.contentString,
style: buildTextSimpleContent(20))
);
}
Upon press of FAB - it's adding the items twice. Why is this? I have solved it by clearing the displayList and adding all items from 0 to the current index.
I tried setting key to the listview, but that didn't solve it.
Any help or insight appreciated.
setState calls the build method of the Widget to build
So this is what's happening
onPressed method is called when FAB is clicked
_currentIndex = _currentIndex + 1;
displayList.add(simpleContentList[_currentIndex]);
This adds a new item
But then build method is again called
So you again add the element in the list in build method displayList.add(simpleContentList[_currentIndex]);
Solution 1
Remove
simpleContentList = getOOFirstContent();
displayList.add(simpleContentList[_currentIndex]);
from build and add it to initState
Solution 2
delete
displayList.add(simpleContentList[_currentIndex]);
from setState so that the element is added only once
For more details on StateFul Widget Lifecycle method refer here

How to implement a swipe to delete listview to remove data from firestore

Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore

Disabling Flutter Hero reverse animation

Given 2 routes, e.g. parent and a child and a Hero(..) widget with the same tag.
When the user is on the "parent" screen and opens a "child" - the Hero widget is animated. When it goes back (via Navigator.pop) it's also animated.
I'm looking for a way to disable that animation when going back (from child to parent via Navigator.pop).
Is there a kind of handler which will be called on a widget before it's going to be "animated away" ? Then I probably could change Hero tag and problem solved.
Or, when creating a "builder" for a route in parent widget, I could probably remember a reference to a target widget and before calling Navigator.pop notify it about "you are gonna be animated out". That would also require making that widget stateful (I haven't found a way to force rebuild a stateless widget).
Is there an easier way of implementing this?
While there currently isn’t a built-in way to disable Hero animations in any particular direction, though CLucera’s use of FadeTransition with HeroFlightDirection is one creative way, the most direct approach is to break the tag association between the two Hero’s:
When you go from the 2nd Hero back to the 1st Hero, just temporarily change the 1st Hero’s tag to something else, then the Hero won’t animate back. A simplified example:
class _MyHomePageState extends State<MyHomePage> {
String tag1, tag2;
String sharedTag = 'test';
String breakTag = 'notTest';
#override
void initState() {
super.initState();
tag1 = sharedTag;
tag2 = sharedTag;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Hero(
tag: tag1,
child: RaisedButton(
child: Text("hi"),
onPressed: () {
// restore the tag
if (tag1 != sharedTag) {
setState(() {
tag1 = sharedTag;
});
}
// second route
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
alignment: Alignment.topLeft,
child: Hero(
tag: tag2,
child: RaisedButton(
child: Text('hello'),
onPressed: () {
// change the tag to disable the reverse anim
setState(() {
tag1 = breakTag;
});
Navigator.of(context).pop();
},
),
)
),
);
}
)
);
},
)
),
),
);
}
}
But if you want to directly modify the animation, then playing around inside the flightShuttleBuilder is the way to do it like CLucera did. You can also check out medium/mastering-hero-animations-in-flutter to further explore that area.
The only approach that I can come up at the moment is to "Animate" the popping Hero in a way that seems not animated, let's check this code:
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Hero(
flightShuttleBuilder: (context, anim, direction, fromContext, toContext) {
final Hero toHero = toContext.widget;
if (direction == HeroFlightDirection.pop) {
return FadeTransition(
opacity: AlwaysStoppedAnimation(0),
child: toHero.child,
);
} else {
return toHero.child;
}
},
child: FlatButton(
child: Text("prev 1"),
onPressed: () {
Navigator.of(context).pop();
},
),
tag: "test",
));
}
}
in your SecondRoute (the one that should pop) you have to supply a flightShuttleBuilder parameter to your Hero then you can check the direction and if it is popping, just hide the Widget with an AlwaysStoppedAnimation fade transition
the result is something like this:
I hope that this is something like the expected result, of course, you can completely change the transition inside the flightShuttleBuilder to change the effect! it's up to you :)

Categories

Resources