Flutter. Multiple heroes share same tag. ListView with buttons - flutter

Can't figure it out how to solve this problem. So, here is the simplest code which represents my problem:
Scaffold(
body: Stack(
children: <Widget>[
...
Scrollbar(
child: ListView.builder(
primary: false,
shrinkWrap: true,
itemCount: _mapBloc?.mapData?.companies?.count ?? 0,
itemBuilder: (context, index) {
final company = _mapBloc?.mapData?.companies?.data[index];
return InkWell(
child: Hero(
tag: company.id,
child: Card(
child: Container(
height: 50,
width: double.infinity,
),
),
),
onTap: () {
Navigator.of(context)
.pushNamed('/company', arguments: company)
.then(
(results) {
if (results is PopWithResults) {
PopWithResults popResult = results;
}
},
);
},
);
},
),
)
],
),
);
And stack trace:
The following assertion was thrown during a scheduler callback:
There are multiple heroes that share the same tag within a subtree.
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must
have a unique non-null tag.
...
The items count in ListView changes with every database request. If the size is up to 5 widgets in ListView, then clicking works correctly, as soon as the list expands to, for example, 8 elements, I get the error written above. With what it can be connected? I tried to use unique Hero tags, but this does not solve the problem.
I need some advice and hope you can help me. If you need more information, please, write the comment.
Thanks for your attention!

Related

Flutter: Is it possible to use single JSON file in bodies of more expansion panels?

In my app, I have one JSON file with some static data which I use to produce List of a widgets. Then, I have one screen with ExpansionPanelRadio showing few items and each of them, when expanded, (in their bodies) are containing that list of a widgets made using JSON file.
I am using provider and I am able to display that list of widgets inside body of expansionpanel but the lists are somehow repeating.
For example, I expand one panel and in its body list is displayed few times, like in a loop. I guess, that the problem is in provider but I don't understand it quite well.
I am pretty new to flutter and would appreciate if someone could explain me why is this happening and what approach should I use to solve it.
here is part of a code where i make those expansion panels with provided JSON in a body:
SingleChildScrollView(
child: ExpansionPanelList.radio(
elevation: 0,
children: MyList.map<ExpansionPanelRadio>((Item item) {
return ExpansionPanelRadio(
value: MyList.indexOf(item),
headerBuilder: (BuildContext context, bool isExpanded) {
return Row(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: SvgPicture.asset(
"assets/images/some_image.svg"
),
),
Text('some label'),
],
);
},
body: ChangeNotifierProvider(
create: (context) => FeaturesProvider(),
builder: (context, child) {
Provider.of<FeaturesProvider>(context, listen: false)
.readFeatures();
return SingleChildScrollView(
child: Container(
child: Features(item.objectId.toString()),
),
);
}));
}).toList(),
))

Use of Listview.builder makes the screen go away

I want to show Listview underneath my two widgets but when i hot reload, nothing happens and if i run again, UI shows blank screen. If i remove Listview.builder it works fine.
Below is my code.
import 'package:flutter/material.dart';
import 'package:plant_clone/constants.dart';
import 'package:plant_clone/model/model.dart';
import 'package:plant_clone/screens/home/components/header_with_searchbox.dart';
import 'package:plant_clone/screens/home/components/title_with_more_btn.dart';
import 'package:plant_clone/viewmodel/recommended_plants_viewmodel.dart';
class Body extends StatelessWidget {
RecommendedPlantViewModel recommendedPlantViewModel =
new RecommendedPlantViewModel();
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
recommendedPlantViewModel.setWidgetsData();
return SingleChildScrollView(
child: Column(
children: [
HeaderWithSearchBox(size: size),
TitleWithMoreButton(
title: "Recommended",
press: () {},
),
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 3,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: (){},
title: Text('Hello'),
),
);
})
],
),
);
}
}
It doesn't look like there's any other way than setting height constraint using a SizedBox that's wrapping a ListView.
Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: size.height,
child: ListView.builder(
...
),
),
],
)
https://flutter.dev/docs/cookbook/lists/horizontal-list
i think it happened because the list view should have a height,,
the esiest way is to test that put it inside a container and give a height to it..
and the second way is wrap the listview inside a Expanded widget and it will fix ..
if not then post the error from debug log
In order to make this to work, you must wrap your ListView with a Container and define the height property as it is part of a Column. You also need to wrap the widget returned by the itemBuilder with a Container and define the width property as the scrollDirection is set to Axis.horizontal.
Container(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 3,
itemBuilder: (context, index) {
return Container(
width: 100,
child: Card(
child: ListTile(
onTap: () {},
title: Text('Hello'),
),
),
);
},
),
)

A dismissed Dismissible widget is still part of the tree

There seem to be many questions regarding this error but I'm yet to find an answer that will work in my situation.
The behaviour I'm seeing is that the Dismissible works, it fires and deletes the item, but for a moment it shows an error in the ListView. I'm guessing it's waiting for the tree to update based on the Stream<List>, which in turn is removing the record from Firebase.
My StreamBuilder...
return StreamBuilder<List<Person>>(
stream: personBloc.personsByUserId(userId),
builder: (context, snapshot) {
...
}
My ListView.builder()
ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var person = snapshot.data[index];
return GestureDetector(
onTap: () {
Navigator.of(context)
.pushNamed('/reading/${person.personId}');
},
child: Dismissible(
key: Key(person.personId),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
personBloc.deletePerson(person.personId);
},
background: Container(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
FontAwesomeIcons.trash,
color: Colors.white,
),
Text(
'Delete',
style: TextStyle(color: Colors.white),
textAlign: TextAlign.right,
),
],
),
),
color: Colors.red,
),
child: AppCard(
//Bunch of properties get set here
),
),
);
},
My deletePerson
deletePerson(String personId) async {
fetchPersonId(personId).then((value) {
if (value.imageUrl.isNotEmpty) {
removeImage();
}
db.deletePerson(personId);
});
}
I've tried changing the onDismissed to a confirmDismiss with no luck.
Any suggestions?
This happens when you dismiss with a Dismissible widget but haven't removed the item from the list being used by the ListView.builder. If your list was being stored locally, with latency not being an issue, you might never see this issue, but because you are using Firestore (I assume, based on your mention ofFirebase) then there is going to be some latency between asking the item to be removed from the DB and the list getting updated on the app. To avoid this issue, you can manage the local list separately from the list coming from the Stream. Updating the state as the stream changes, but allowing you to delete items locally from the local list and avoiding these kind of view errors.
I ended up making a couple of changes to my code to address this.
I added a BehaviourSubject in my bloc to monitor whether the delete was taking place or not. At the beginning of the firestore delete I set this to true and then added a .then to the delete to set it back to false.
I then added a Streambuilder around the ListView on the screen to monitor the value of this and show a CircularProgressIndicator when true.
It now looks like this:
Thanks for your help.

GestureDetector is not working with Dismissible Widget

I am trying to get the drag offsets while using Dismissible widget. So tried to wrap it with GestureDetector but its onHorizontalDragStart is not working. Tried the either way ie by putting GestureDetector as child of Dismissible but then Dismissible stopped working.
How to solve this? Thnx...
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
var item = items[index];
return GestureDetector(
onHorizontalDragStart: (e) {
print(e);
},
child: Dismissible(
key: ValueKey(item),
background: Container(
color: Colors.teal,
),
child: ListTile(
title: Text("item $item"),
),
onDismissed: (d) {
items.remove(item);
},
),
);
},
);
Nested Gesture Widgets
The reason you are having this issue is because both of those widgets receive touch input and when you have two widgets that receive touch input, long story short the child wins that battle. Here is the long story. So both of your inputs from your Dismissible and GestureDetector are sent to what is called a GestureArena. There the arena takes into account multiple different factors but the end of the story is the child always wins. You can fix this issue by defining your own RawGestureDetector with its own GestureFactory which will change the way the arena performs.
ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
var item = items[index];
return Listener(
child: Dismissible(
key: ValueKey(item),
child: ListTile(
title: Text('This is some text'),
),
onDismissed: (DismissDirection direction) {
items.remove(index);
},
),
onPointerMove: (PointerMoveEvent move) {
if (move.localDelta.dx > 1) {//Ideally this number whould be
//(move.localDelta.dx != 0) but that is too sensitive so you can play with
//this number till you find something you like.
print(move.position);
}
},
);
},
);
I want to give all credit to Nash the author of Flutter Deep Dive: Gestures this is a great article I highly recommend you check it out.

UI doesn't fully update when receive bloc informations

I have this weird problem: I want to update a grid of items when I click on it. I use a BLoC pattern to manage the changement so the view just receive a list and have to display it. My problem is that the view doesn't fully update.
Before I go further in the explanation, here my code
body: BlocEventStateBuilder<ShopEvent, ShopState>(
bloc: bloc,
builder: (BuildContext context, ShopState state) {
staggeredTile.clear();
cards.clear();
staggeredTile.add(StaggeredTile.count(4, 0.1));
cards.add(Container());
if (state.products != null) {
state.products.forEach((item) {
staggeredTile.add(StaggeredTile.count(2, 2));
cards.add(
Card(
child: InkWell(
child: Column(
children: <Widget>[
Image.network(
item.picture,
height: 140,
),
Container(margin: EdgeInsets.only(top: 8.0)),
Text(item.title)
],
),
onTap: () {
bloc.emitEvent(ClickShopEvent(item.id));
},
),
),
);
});
}
return StaggeredGridView.count(
crossAxisCount: 4,
staggeredTiles: staggeredTile,
children: cards,
);
}),
So, I have two items. When I click on the first one, I'm suppose to have one item with a different name and picture. But when I click, I have one item as expected, but with the same text and image. When I print thoses values, it's correctly updated but the view doesn't show it.
Do you have any clues of my problem?
For a reason that I can't explain, when I replaced
staggeredTile.clear();
cards.clear();
by
staggeredTile = new List<StaggeredTile>();
cards = new List<Widget>();
It works fine.
If someone can explain me the reason, I'd be gratefull.