I'm trying to build a system just like tinder where there is a stack of cards a user can swipe and the next few cards are always preloaded.
To do that, I have a Stack
widget that build a series of cards child widgets and give them the id of the content to load:
class PostCardStack extends StatefulWidget {
const PostCardStack({Key key, this.postIds, this.onCardDismissed})
: super(key: key);
final List<String> postIds;
final Function onCardDismissed;
@override
_PostCardStackState createState() => _PostCardStackState();
}
class _PostCardStackState extends State<PostCardStack> {
ValueNotifier<double> _notifier = ValueNotifier<double>(0.0);
_buildCardStack() {
List<Widget> cards = [];
for (String postId in widget.postIds) {
int idx = widget.postIds.indexOf(postId);
if (postId == widget.postIds.first) {
cards.add(CustomDismissible(
resizeDuration: null,
dismissThresholds: {CustomDismissDirection.horizontal: 0.2},
notifier: _notifier,
key: Key(postId),
onDismissed: (direction) {
_notifier.value = 0.0;
widget.onCardDismissed(postId);
},
child: SlidablePanel(
panel: AnimatedBuilder(
animation: _notifier,
child: PostCard(
postId: postId,
),
builder: (context, _) {
return Opacity(
opacity: 1 - _notifier.value,
child: PostCard(
postId: postId,
));
}),
)));
} else {
cards.add(AnimatedBuilder(
animation: _notifier,
child: PostCard(
postId: postId,
),
builder: (context, _) {
return Opacity(
opacity: lerpDouble(1 - (0.1 * idx), 1 - ((0.1 * idx) - 0.1),
_notifier.value),
child: Transform(
origin: null,
alignment: Alignment.bottomCenter,
transform: Matrix4.translationValues(
0.0,
lerpDouble(
-idx * 35, (-idx * 35 + 35), _notifier.value),
0.0)
..scale(
lerpDouble(1 - (0.1 * idx), 1 - ((0.1 * idx) - 0.1),
_notifier.value),
lerpDouble(1 - (0.1 * idx), 1 - ((0.1 * idx) - 0.1),
_notifier.value),
1),
child: PostCard(
postId: postId,
)));
}));
}
}
return cards.reversed.toList();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter, children: _buildCardStack());
}
}
In the PostCard
widget, I use the postId
param to fetch the card information and build it when it is ready.
class PostCard extends StatefulWidget {
const PostCard({Key key, this.postId}) : super(key: key);
final String postId;
@override
_PostCardState createState() => _PostCardState();
}
class _PostCardState extends State<PostCard> {
PostModel post;
@override
void initState() {
super.initState();
print("${widget.postId} mounted");
_fetchPost(widget.postId);
}
@override
void dispose() {
print("${widget.postId} disposed");
super.dispose();
}
_fetchPost(String postId) async {
PostModel fullPost = await blablaFirestore(postId);
setState(() {
post = fullPost;
});
}
@override
Widget build(BuildContext context) {
return Container(
height: 300,
child: Align(
alignment: Alignment.topCenter,
child: Text(post == null ? "Loading..." : post.text),
),
);
}
}
Everything works fine except when any element of the list of id in the Stack component changes, all the child cards gets rebuilt, therefore loosing their previously loaded state and have to fetch the data again.
If only one element of the list of id change, why are every cards rebuilding? Am I missing something here? :)
Edit: In the question marked as duplicate, the problem is the build method having side effects. I don't believe that this is the case here. In my case, the problem is that state of the PostCards widget is not kept even tho they rebuild with the exact same param (postId)
Cheers!