UI doesn't fully update when receive bloc informations - flutter

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.

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(),
))

pointless Api requests occuring in future builder flutter

I have a Future Builder in my flutter app and it displays --
Error : if there's an error in json parsing
Data : if everything goes smooth
Loader : if its taking time
Everything works. the Future is calling a 'future' function thats doing a get request of some student data and the 'builder' is displaying it. I have an edit dialog box on the same page. I can edit the student information through the put request. The problem is that when I click on the form fields in the edit dialog box, I notice that get request is automatically happening approx 10 times. When I save the edits, a confirmation dialog box appears that data is updated. While this happens again get requests happens upto 10 times. And then it pops. So there are round about 20 useless requests happening on the server.
I think it happens because when I click the form fields the keyboard appears and the underlying displaying widget rebuilds, calling the api. When data is edited keyboards goes back into its place again widget rebuilds, calling the api. How can I resolve this issue ?
this is the code if it helps :
child: FutureBuilder(
future: APIs().getStudentDetails(),
builder: (context, data) {
if (data.hasError) {
return Padding(
padding: const EdgeInsets.all(8),
child: Center(child: Text("${data.error}")));
} else if (data.hasData) {
var studentData = data.data as List<StudentDetails>;
return Padding(
padding: const EdgeInsets.fromLTRB(0, 15, 0, 0),
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.9,
child: ListView.builder(
itemCount: studentData.length,
itemBuilder: ((context, index) {
final student = studentData[index];
final id = student.studentId;
final father = student.fatherName;
final mother = student.motherName;
final cg = student.cg;
final cityName = student.city;
final studentName = student.studentName;
return SizedBox(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: SingleChildScrollView(
child: GestureDetector(
onDoubleTap: () {
edit(context, id!, studentName!, father,
mother, cg, cityName!);
},
child: Column(children: [
CustomReadOnlyField(
hintText: id.toString()),
CustomReadOnlyField(hintText: studentName),
CustomReadOnlyField(hintText: father),
CustomReadOnlyField(hintText: mother),
CustomReadOnlyField(
hintText: cg.toString()),
CustomReadOnlyField(hintText: cityName),
]),
),
),
),
);
}),
scrollDirection: Axis.vertical,
),
),
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
I followed this answer and it worke. Flutter FutureBuilder gets constantly called
Apparantly I had to 'Lazily initializing my Future' and 'Initializing my Future in initState:'
Create a state variable for future like
late final future = APIs().getStudentDetails();
and use
FutureBuilder(
future: future ,
You can check Fixing a common FutureBuilder and StreamBuilder problem
class _YourWidgetState extends State<YourWidget> with AutomaticKeepAliveClientMixin<YourWidget> {
#override
bool get wantKeepAlive => true;
So extend your Widget with AutomaticKeepAliveClientMixin so items inside Listview will not be reproduced

Flutter Chat Reply to Message

I'm trying to build a Chat UI with ListView. My first version was a ListView.build that works fine.
I want to upgrade my chat view for "replyTo message" option (similar to whatsapp). This requires the user to be able to click the "replyTo" message and scroll backwards to the original message. The ListView.build does not allow to scroll up to a message that is outside the screen view (as it is not even built).
body: Column(
children: [
Expanded(
child: EasyRefresh.custom(
...
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
ChatMessage _currMsg = vm.chat[index]!;
...
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
...
SwipeTo(
onRightSwipe: () {
setState(() {
isReplying = true;
replyMessage = _currMsg;
});
},
child: ShowMessage(
key: Key(_currMsg.id!),
msg: _currMsg,
myMessage: _currMsg.postById == vm.user!.uid,
onTapReply: (id) async {
final ctx = _itemKey.currentContext;
if (ctx != null)
await Scrollable.ensureVisible(ctx);
},
),
),
],
);
},
childCount: vm.chat.length,
),
),
],
),
),
It seems like the only way is to build all the list items at first build itself.
My question is, is there a way I can retain the already built items, and only add new incoming items rather than rebuilding all items every time.
Thanks.

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.

Flutter. Multiple heroes share same tag. ListView with buttons

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!