从一个标签页滑动到另一个标签页时,TabView不会保留状态

时间:2020-08-02 16:43:47

标签: flutter state-management flutter-bloc

上下文:这是一个带有TabView的页面,可在各选项卡之间导航,所有这些选项卡均使用flutter_bloc(版本6.0.1)。

问题:当滑动到任何选项卡时,状态不会保留,并且整个小部件树都将被重建,如下图gif所示。

PageView State not being preserved when swiping

这是build()方法:

 @override
  Widget build(BuildContext context) {
    super.build(context);
    return DefaultTabController(
      initialIndex: 0,
      length: 3,
      child: Scaffold(
        backgroundColor: Colors.white,
        appBar: _buildAppBarWithTabs(),
        body: TabBarView(
          children: <Widget>[
            defaultViewforCategory(1), //Women
            defaultViewforCategory(3), //Men
            defaultViewforCategory(2), //Kids
          ],
        ),
      ),
    );
  }

这是功能defaultViewforCategory()

的实现
Widget defaultViewforCategory(int mainCategoryId) {
    return PageStorage(
      bucket: bucket,
      key: PageStorageKey(mainCategoryId),
      child: ConstrainedBox(
        constraints: BoxConstraints(maxWidth: 1200),
        child: ListView(
          scrollDirection: Axis.vertical,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 150),
              child: Container(
                height: 800,
                child: RefreshIndicator(
                  onRefresh: () => refreshTimeline(),
                  child: CustomScrollView(
                    scrollDirection: Axis.vertical,
                    slivers: <Widget>[
                      SliverToBoxAdapter(
                        child: MasonryGrid(
                          column: getResponsiveColumnNumber(context, 1, 2, 6),
                          children: <Widget>[
                            // First Bloc
                            BlocProvider(
                              create: (context) {
                                BrandBloc(repository: _brandRepository);
                              },
                              child: Container(
                                width: 200,
                                alignment: Alignment.center,
                                height: 90,
                                child: BrandScreen(
                                  brandBloc: context.bloc(),
                                ),
                              ),
                            ),
                            CategoryScreen(
                              // Second Bloc
                              categoryBloc: CategoryBloc(
                                  mainCategoryId: mainCategoryId,
                                  repository: _categoryRepository),
                            ),

                            // -------------- Featured Items--------------------------
                            Container(
                              width: 200,
                              alignment: Alignment.center,
                              height: 350,
                              child: _buildFeaturedItemsList(mainCategoryId),
                            ),
                            Placeholder(strokeWidth: 0, color: Colors.white)
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

尝试的解决方案: 1-我尝试过AutomaticKeepAliveClientMixin,但事实证明,mixin保留了使用BottomNavigationBar切换到另一个页面时页面的状态。

2-{{​​1}}不能解决问题。

问题:如何在用户每次滑动到另一个选项卡时阻止重新构建PageStorage

1 个答案:

答案 0 :(得分:0)

如您所述,问题之一是每次显示选项卡时都会重建TabBarView。对于该问题,有一个开放主题here。因此,每次屏幕更改时都会创建一个新的Bloc实例。

注意,因为CategoryBloc未通过BlocProvider传递,因此您应该手动处置该集团。

一种简单的解决方案是将BlocProviderTabBarView之外的层次结构中向上移动-例如,在build方法中是第一个组件。

注意就性能而言,这是可以的,因为在请求块时会延迟初始化BLOC。

现在,更为棘手的问题是创建CategoryBloc的方式(因为具有动态构造函数)。在这里,您可以有两种解决方案:

  1. 要么将CategoryBloc修改为可被所有类别屏幕共享-在这里,我没有太多帮助,因为我没有代码。这个想法是通过mainCategoryIdeventsemits发送带有结果的新状态。在这种情况下,您应该将mainCategoryId转发到state,并在BlocBuilder上使用buildWhen参数仅在mainCategoryId与CategoryScreen id匹配时进行构建(创建屏幕时可以通过)。并且不要忘记还使用BlocProvider子级之外的TabBarView提供CategoryBloc。

  2. 将CategoryBloc创建移动到TabBarView之外,并将其缓存以供进一步访问。我在下面创建了一个示例来强调这一点。

    // ...
    
    ///
    /// Categories blocs cache.
    ///
    Map<int, CategoryBloc> _categoriesBlocs;
    
    ///
    /// Creates UNIQUE instances of CategoryBloc by id.
    ///
    CategoryBloc getCategoryBlocById(int id) {
        // If you don't already have a bloc for that particular id, create a new one
        // and cache it (by saving it in the Map)
        this._categoriesBlocs.putIfAbsent(
            id,
            () => CategoryBloc(
            mainCategoryId: id,
                      repository: _categoryRepository,
            ));
    
        // Return the cached category bloc
        return this._categoriesBlocs[id];
      }
    
    ///
    /// This is very important. Because we manually create the BLOCs we have 
    /// to manually dispose them 
    ///
    @override
    void dispose() {
        for (var bloc in this._categoriesBlocs) {
         bloc.displose();
        }
        super.dispose();
    }
    
    @override
    Widget build(BuildContext context) {
        super.build(context);
    
        return MultiBlocProvider(
            providers: [
                BlocProvider(
                    create: (context) => BrandBloc(repository: _brandRepository),
                )
            ],
            child: DefaultTabController(
                initialIndex: 0,
                length: 3,
                child: Scaffold(
                    backgroundColor: Colors.white,
                    appBar: _buildAppBarWithTabs(),
                    body: TabBarView(
                        children: <Widget>[
                            defaultViewforCategory(1), //Women
                            defaultViewforCategory(3), //Men
                            defaultViewforCategory(2), //Kids
                        ],
                    ),
                ),
            ),
        );
    }
    
      // ...
    
      CategoryScreen(
      // Second Bloc
      // Now, here you will get the same BLOC instance every time
          categoryBloc: getCategoryBlocById(mainCategoryId),
      ),
    
      // ...