Mohamed Mohamed
Mohamed Mohamed

Reputation: 4295

Flutter animations gets very laggy

I was designing the UI in flutter and with the animations and stuff but when I started to add two StreamBuilders with listviews, using Firestore, to the UI replace the dummy data the animations turned from buttery smooth to sooo laggy I'd rather not have them there.

I'm using two AnimationControllers two control two types of animations, I used this tutorial to learn how to do it without setStates and it worked smoothly until I added the StreamBuilders.

One AnimationController animates a fade transition, and the other a vertical translation to hide the lists widget off the screen.

Code:

AnimationController _fadeAnimationController;
AnimationController _margAnimationController;

@override
void initState() {
  super.initState();

  this._fadeAnimationController = AnimationController(
    value: 1.0,
    duration: Duration(milliseconds: 300),
    reverseDuration: Duration(milliseconds: 300),
    vsync: this,
  );

  this._margAnimationController = AnimationController(
    value: 0.0,
    upperBound: widget.deviceHeight,
    duration: Duration(milliseconds: 300),
    reverseDuration: Duration(milliseconds: 300),
    vsync: this,
  );
}

@override
void dispose() {
  super.dispose();

  this._fadeAnimationController.dispose();
  this._margAnimationController.dispose();
}

@override
Widget build(BuildContext context) {
  return Material(
    color: Colors.black,
    child: Stack(
      children: <Widget>[
        FadeTransition( // FIRST ANIMATED WIDGET
          opacity: this._fadeAnimationController,
          child: Container(color: Config.pColor),
        ),
        // Other unrelated widgets…
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[
              Platform.isIOS
                  ? CupertinoNavigationBar(
                      automaticallyImplyLeading: false,
                      backgroundColor: Colors.transparent,
                      border: Border.all(color: Colors.transparent, width: 0.0, style: BorderStyle.none),
                      middle: Text('Dost', style: TextStyle(color: Config.bgColor)),
                      trailing: this._cameraIconButton(),
                      transitionBetweenRoutes: false,
                      heroTag: 'CameraAppBar')
                  : AppBar(
                      automaticallyImplyLeading: false,
                      backgroundColor: Colors.transparent,
                      elevation: 0.0,
                      title: Text('Dost', style: TextStyle(color: Config.bgColor)),
                      actions: <Widget>[this._cameraIconButton()],
                    )
            ]),
            Expanded(
              child: AnimatedBuilder( // SECOND ANIMATED WIDGET
                animation: this._margAnimationController,
                builder: (_, child) => Transform.translate(
                  offset: Offset(0, this._margAnimationController.value),
                  child: child,
                ),
                child: HomePage( // STREAMBUILDERS ARE INSIDE THIS WIDGET
                    cUser: InheritedUser.of(context).user,
                    showCamera: () {
                      this._openCloseCamera();
                    },
                    showPosts: () {
                      Funcs.popup(context: context, w: CUPostsPage(cUser: InheritedUser.of(context).user));
                    },
                    showSettings: () {
                      Funcs.navigateTo(context: context, w: SettingsPage(), fullscreenDialog: false);
                    }),
              ),
            )
          ],
        )
      ],
    ),
  );
}

The HomePage() widget basically has a list of the two StreamBuilders each having a ListView, one horizontal and the other vertical. Both look very similar.

The StreamBuilder widgets:

class ChatsList extends StatelessWidget {
  @override
  Widget build(BuildContext context) => StreamBuilder<List<Chat>>(
      initialData: InheritedUser.of(context).user.chats,
      stream: APIs().chats.chatsStream(cUserID: InheritedUser.of(context).user.userID),
      builder: (context, snap) {
        User cUser = InheritedUser.of(context).user;

        cUser.chats.clear();
        cUser.chats = snap.data;

        return ListView.builder(
            physics: BouncingScrollPhysics(),
            shrinkWrap: true,
            padding: EdgeInsets.only(top: 0.0),
            itemBuilder: (context, index) => ChatItem(chat: cUser.chats[index]),
            itemCount: cUser.chats.length);
      });
}

AND

class AUPostsList extends StatelessWidget {
  final ScrollController scrollController;

  AUPostsList({this.scrollController});

  @override
  Widget build(BuildContext context) => StreamBuilder<List<Post>>(
      initialData: [],
      stream: APIs().posts.auPostsStream(cUserID: InheritedUser.of(context).user.userID),
      builder: (context, snap) {
        Map<String, List<Post>> _posts = {};
        List<String> _postsUserIDs = [];

        snap.data.forEach((post) {
          if (_posts[post.user.documentID] == null) {
            _posts[post.user.documentID] = [post];
          } else {
            _posts[post.user.documentID].add(post);
          }

          if (!_postsUserIDs.contains(post.user.documentID)) {
            _postsUserIDs.add(post.user.documentID);
            _posts[post.user.documentID].sort((a, b) => b.createdAt.compareTo(a.createdAt));
          }
        });

        return Container(
            height: ((MediaQuery.of(context).size.width - 80.0) / 3) * 1.5,
            child: ListView.separated(
              scrollDirection: Axis.horizontal,
              controller: this.scrollController,
              physics: AlwaysScrollableScrollPhysics(),
              shrinkWrap: true,
              padding: EdgeInsets.only(top: 0.0, left: 10.0, right: 10.0),
              itemBuilder: (context, index) => AUPostItem(posts: _posts[_postsUserIDs[index]]),
              separatorBuilder: (context, index) => Container(),
              itemCount: _postsUserIDs.length,
            ));
      });
}

When I comment on one of the StreamBuilders out then it's just laggy, however, the second, AUPostsList, is much laggier than ChatsList. But when both are being shown then the animation is really really really laggy on release mode, nonexistent on debug mode. And both are commented out then it's soooooooo smooth both on debug and release mode.

Yes, both on iOS and Android have the same effects.

Upvotes: 0

Views: 2469

Answers (1)

Zhangir Siranov
Zhangir Siranov

Reputation: 369

Instead of using the stream builders, you can use listeners that will listen for the updates of the Firestore database, and setState when you have the update. It worked better for me for some reason. In your case that would be something like this:

List<DocumentSnapshot> posts;
var query = Firestore.instance
    .collection('posts')
    .where('userId', isEqualTo: userId);

listener() async {
  query.snapshots().listen((querySnapshot) {
    querySnapshot.documentChanges.forEach((changes) {
      if (posts.every((f) => f != changes.document)) {
        setState(() {
          posts.add(changes.document);
        });
      } else {
        setState(() {
          posts[posts.indexWhere((f) =>
                  f.data['postId'] == changes.document.data['postId'])] =
              changes.document;
        });
      }
    });
  });
}

Of Course, you will have to adjust all of the data for your needs.

Upvotes: 1

Related Questions