Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Laggy swiping between tabs #5

Closed
aleksandar-radivojevic opened this issue Sep 4, 2019 · 19 comments
Closed

Laggy swiping between tabs #5

aleksandar-radivojevic opened this issue Sep 4, 2019 · 19 comments

Comments

@aleksandar-radivojevic
Copy link

aleksandar-radivojevic commented Sep 4, 2019

I'm building a news app(wordpress as a backend) that has 15 tabs(categories) in ExtendedTabBarView and when I set cache_extent=15, swiping(sliding with finger) between tabs is very smooth but the scrolling is laggy(probably due to high memory usage because it has to preload and cache 15 tabs with 10 post per tab), BUT when I set cache_extent=2, app works fine but swiping between tabs is laggy at the end of animation(transition), while when I TAP on single tab transition between tabs is always smooth, both on cache_extent 2 and 15. What can be the issue with this, I'm not saying thats your plugin a problem, but I need help, I'm struggling with this whole week. :D :D

If you need code sample or anything, just ask I will respond immediately. Keep up the good work. (:

@aleksandar-radivojevic
Copy link
Author

aleksandar-radivojevic commented Sep 4, 2019

And also I forgot to mention another interesting thing, when I set cache_extent=8 (app. half of tabs) very interesting is that swiping between first 8 tabs is laggy while is smooth for the rest of the tabs(7 tabs).

All tests were in release mode. Plugin version: 0.2.2, Flutter version 1.7

@zmtzawqlp
Copy link
Member

cacheExtent full meaning :

/// {@template flutter.rendering.viewport.cacheExtent}
/// The viewport has an area before and after the visible area to cache items
/// that are about to become visible when the user scrolls.
///
/// Items that fall in this cache area are laid out even though they are not
/// (yet) visible on screen. The [cacheExtent] describes how many pixels
/// the cache area extends before the leading edge and after the trailing edge
/// of the viewport.
///
/// The total extent, which the viewport will try to cover with children, is
/// [cacheExtent] before the leading edge + extent of the main axis +
/// [cacheExtent] after the trailing edge.
///
/// The cache area is also used to implement implicit accessibility scrolling
/// on iOS: When the accessibility focus moves from an item in the visible
/// viewport to an invisible item in the cache area, the framework will bring
/// that item into view with an (implicit) scroll action.
/// {@endtemplate}

you cache so many page , so when you tap on one , cache pages will all rebuild

@aleksandar-radivojevic
Copy link
Author

aleksandar-radivojevic commented Sep 5, 2019

I understand concept of cache extent but thats not whats happening here.. Please read my issue one more time. Tap on Tab is not a problem in any case, whether cache_extent is equal to 2 or 15. The problem is that when I set cache_extent to be 1 or 2 and when I do SWIPING(sliding with finger to another Tab in ExtendedTabBarView) it has an annoying frame skip at the end of transition between tabs.

Here is a video link, it's really hard to see in video, but on real device is really noticeable

https://youtu.be/Pkr7r_gCkvU

Below is my code for ExtendedTabBar and also for tabs.

@aleksandar-radivojevic
Copy link
Author

aleksandar-radivojevic commented Sep 5, 2019

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:extended_tabs/extended_tabs.dart';

import 'categories/get_categories.dart';
import 'category_template.dart';

class TabControl extends StatefulWidget {
  static TabBar mainTabBar = TabBar(
    indicatorColor: Colors.indigo,
    indicatorSize: TabBarIndicatorSize.label,
    labelPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 20.0),
    labelStyle: TextStyle(
      fontSize: 17.0,
    ),
    isScrollable: true,
    tabs: buildMenu(),
  );
  static List<Widget> buildMenu() {
    List<Widget> tabs = [];

    for (var i = 0; i < CategoriesJson.getCategories.length; i++) {
      tabs.add(
        Tab(
          text: CategoriesJson.getCategories[i]['name'],
        ),
      );
    }
    return tabs;
  }

  @override
  _TabControlState createState() => _TabControlState();
}

class _TabControlState extends State<TabControl> {
  ConnectivityResult connectivityResult;

  bool isConnected = true;

  Future<bool> checkInternet() async {
    connectivityResult = await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      setState(() {
        isConnected = false;
      });
    } else {
      setState(() {
        isConnected = true;
      });
    }

    return isConnected;
  }

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

  @override
  Widget build(BuildContext context) {
    List<Widget> _categories = [];
    for (var i = 0; i < CategoriesJson.getCategories.length; i++) {
      _categories.add(
        CategoryTemplate(
          CategoriesJson.getCategories[i]['id'],
        ),
      );
    }
    return isConnected
        ? ExtendedTabBarView(
            children: _categories,
            physics: AlwaysScrollableScrollPhysics(),
            cacheExtent: 2,
            linkWithAncestor: false,
            dragStartBehavior: DragStartBehavior.down,
          )
        : Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Icon(
                  Icons.wifi,
                  color: Colors.grey[700],
                  size: 120,
                ),
                Text('Problem sa konekcijom.'),
                RaisedButton(
                    child: Text('Pokusaj ponovo'),
                    onPressed: () {
                      checkInternet();
                    }),
              ],
            ),
          );
  }
}

@aleksandar-radivojevic
Copy link
Author

import 'package:flutter_pagewise/flutter_pagewise.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';

import 'card_elements/card_template.dart';
import 'models/backend_service.dart';
import 'models/post.dart';

class CategoryTemplate extends StatelessWidget {
  final int categoryId;
  CategoryTemplate(this.categoryId);

  final int pageSize = 10;

  @override
  Widget build(BuildContext context) {
    return PagewiseListView(
      pageFuture: (indeks) =>
          BackendService.getPosts(indeks * pageSize, categoryId),
      pageSize: pageSize,
      controller: ScrollController(),
      showRetry: true,
      loadingBuilder: (context) {
        return SpinKitThreeBounce(
          size: 28,
          color: Theme.of(context).primaryColor,
        );
      },
      itemBuilder: (context, PostModel post, index) {
        final parentKey = GlobalKey();
        return CardTemplate(post: post, parentKey: parentKey);
      },
    );
  }
}

@aleksandar-radivojevic
Copy link
Author

import 'package:morpheus/page_routes/morpheus_page_route.dart';
import 'package:provider/provider.dart';

import '.././models/post.dart';
import '../post_comments.dart';
import 'date.dart';
import './../models/toast.dart';
import 'featured_media.dart';
import 'share.dart';
import 'title.dart';
import '../offline_article.dart';

class CardTemplate extends StatelessWidget {
  final PostModel post;
  final GlobalKey parentKey;
  CardTemplate({this.post, this.parentKey});

  void _handleTap(BuildContext context) {
    Navigator.of(context).push(MorpheusPageRoute(
      builder: (context) => PostAndComment(post),
      parentKey: parentKey,
      transitionToChild: true,
    ));
  }

  Widget buildSecondCategory() {
    if (post.secondCategory != null) {
      return Text(post.secondCategory);
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    final myDatabase = Provider.of<MyDatabase>(context);

    void _addFavorite() {
      myDatabase.addFavorite(
        Article(
            link: post.link,
            featuredMediaFull: post.featuredMediaFull,
            featuredMediaMedium: post.featuredMediaMedium,
            secondCategory: post.secondCategory,
            content: post.content,
            id: post.id,
            firstCategory: post.firstCategory,
            title: post.title,
            commentCount: post.commentCount),
      );
      Toast.displayToast('Vest je uspešno dodata u arhivu.');
    }

    void _deleteFavorite() {
      myDatabase.deleteFavorite(post.id);
      Toast.displayToast('Vest je uspešno izrisana iz arhive.');
    }

    return GestureDetector(
      key: parentKey,
      onTap: () => _handleTap(context),
      child: Card(
        color: Theme.of(context).cardColor,
        child: Container(
          padding: const EdgeInsets.fromLTRB(10, 25, 10, 5),
          child: Column(
            children: <Widget>[
              Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Expanded(
                      flex: 1,
                      child: FeaturedMedia(
                        featuredMedia: post.featuredMediaMedium,
                        commentCount: post.commentCount.toString(),
                      ),
                    ),
                    Expanded(
                      flex: 1,
                      child: Container(
                        padding: const EdgeInsets.only(left: 15),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Wrap(
                              direction: Axis.horizontal,
                              alignment: WrapAlignment.start,
                              spacing: 10,
                              runSpacing: 7,
                              crossAxisAlignment: WrapCrossAlignment.start,
                              children: <Widget>[
                                Container(
                                  padding: const EdgeInsets.symmetric(
                                      horizontal: 7.0, vertical: 3.0),
                                  decoration: BoxDecoration(
                                    borderRadius: BorderRadius.circular(4.0),
                                    color: Theme.of(context).accentColor,
                                  ),
                                  child: Text(post.firstCategory ?? ''),
                                ),
                                Container(
                                  padding: EdgeInsets.symmetric(
                                      vertical:
                                          post.secondCategory != null ? 3.0 : 0,
                                      horizontal: 7.0),
                                  decoration: BoxDecoration(
                                    borderRadius: BorderRadius.circular(4.0),
                                    color: Theme.of(context).accentColor,
                                  ),
                                  child: buildSecondCategory(),
                                )
                              ],
                            ),
                            CardTitle(post.title),
                          ],
                        ),
                      ),
                    ),
                  ]),
              Row(
                children: <Widget>[
                  Expanded(child: CardDate(post.date)),
                  Container(
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: <Widget>[
                        StreamBuilder<bool>(
                          stream: myDatabase.isFavorite(post.id),
                          builder:
                              (BuildContext context, AsyncSnapshot snapshot) {
                            if (snapshot.hasData && snapshot.data) {
                              return IconButton(
                                icon: Icon(Icons.bookmark),
                                onPressed: _deleteFavorite,
                              );
                            }
                            return IconButton(
                              onPressed: _addFavorite,
                              icon: Icon(Icons.bookmark_border),
                            );
                          },
                        ),
                        CardShare(post.title, post.link)
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

@AlexV525
Copy link
Member

AlexV525 commented Sep 7, 2019

Hi there. You can try add logs when initState or dispose or build, try to figure out the reason why it'll cause the lagging action. Also please provider a smaller demo, which means the smallest demo that can reproduce this issue. Thanks.

@aleksandar-radivojevic
Copy link
Author

aleksandar-radivojevic commented Sep 14, 2019

Hey guys, I've found out what is the problem.

Let's say for example that we have a news app with 3 tabs and we set cacheExtent = 1. As soon as we start swiping(sliding) from first to second tab, third tab is (pre)loading and thats causing the lag. Transition between second and third tab is going to be smooth(also from second to first), because it has nothing in front to preload.

If we set cacheExtent=3 everything will run smooth, because it has nothing to preload, but this is expensive when we have lot of tabs.

Maybe if you can change the speed of transition between tabs on swiping or force TabBar to start preloading next Tab after the swiping animation has finished, thats my ideas on this.
I'm hoping that you guys can find a workaround this, because I really can't.

Here is the small demo that will demonstrate the problem.
https://github.com/aleksandar-radivojevic/extended_tabs_issue

@AlexV525
Copy link
Member

AlexV525 commented Sep 14, 2019

Thank you for your further investigation.
First thing I want to say is as many flutter'er will done, which is mixed those code for business with the UI thread, may cause app looks not so smooth in some cases. I'm not saying that your code is bad or something, but you may try to solve this issue in this way.
Second is cacheExtent might not doing what we expected, we're currently working on it and try to find another method to implement 'page cache' properly. Before that, I personally recommend you leave this value without setting it.
We'll try to run your example when we have time for coding, cause now in China is the Mid-Autumn festival. Will be back soon :)

@aleksandar-radivojevic
Copy link
Author

Thank you very much Alex, I'm aware of my code structure and will fix that and come back with results.

Just one more question, is there a way to increase speed of swiping through tabs? I think that that will solve the problem, because when I tap on Tab, animation is both fast and smooth, I am just wondering is there a way to make swiping speed equal like is on tapping? And to be always consistent, no matter how fast or slow user slides with finger.

Have a great time on festival.

@AlexV525
Copy link
Member

@aleksandar-radivojevic That's kinda easy XD, with the constant value kTabScrollDuration in material constants. It's 300 milliseconds by default, overwritten the _warpToCurrentIndex which is implement at here /lib/src/tabs.dart#L177 might work.

@aleksandar-radivojevic
Copy link
Author

Tried like this, but no success..

 void changeTab() {
    TabControl.tabController.animateTo(
      TabControl.tabController.index,
      duration: Duration(milliseconds: 100),
      curve: Curves.bounceInOut,
    );
  }

  @override
  void initState() {
    TabControl.tabController =
        TabController(length: CategoriesJson.getCategories.length, vsync: this);

    Home.scrollController = ScrollController(initialScrollOffset: 0);

    TabControl.tabController.addListener(changeTab);
    super.initState();
  }

@aleksandar-radivojevic
Copy link
Author

Can we use this plugin with PageView?

@AlexV525
Copy link
Member

@aleksandar-radivojevic Sorry about our busy in our work. PageView is the default implement.

@AlexV525
Copy link
Member

@aleksandar-radivojevic I thought that maybe you'll get better performance if you using a PageView rather than TabBarView for your code, cause PageView's pages will not keep alive when they are not selected, and when TabBarView animate to some index, all pages between them will run initState again.

@aleksandar-radivojevic
Copy link
Author

aleksandar-radivojevic commented Oct 11, 2019

I've tried it but no success. Its the same. One thing that gain some kind of success is using Future.delayed() with duration of 300ms(which is duration of switching between tabs when swiping/sliding) for my network call inside Tab. But I use same call for fetching more posts bottom(Pagewise ListView package), so thats the problem..

@AlexV525
Copy link
Member

@aleksandar-radivojevic And that's definitely what I was told you before, the request stuck UI thread. So I think this issue can be closed, and you can investigate about how to avoid this situation.

@ItsPKC
Copy link

ItsPKC commented Dec 28, 2020

Main Reason for this lag is highly discussed here, if you want to know more then visit here ....
flutter/flutter#27680

@ItsPKC
Copy link

ItsPKC commented Dec 28, 2020

Reasonable alternative can be their ... [https://medium.com/fluttervn/making-bottom-tabbar-with-flutter-5ff82e8defe0](visit here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants