簡體   English   中英

在 TabBarView Flutter 上從第一個選項卡移動到最后一個選項卡時出現問題

[英]Problem when move from first to last tab on TabBarView Flutter

當我需要從第一個選項卡跳轉到第三個選項卡時,我的 flutter 應用程序遇到問題。 我將在下面更好地解釋:

我正在使用 flutter 構建一個屏幕,當我有一個帶有 3 個選項卡的 TabBar 並在 TabBarView 下方填充此選項卡時。

類似的東西:

Widget _buildReportAppbar() {
    return AppBar(
      backgroundColor: AppColors.colorPrimary,
      elevation: 5,
      leading: ...
      title: ...
      actions: ...
      bottom: PreferredSize(
        preferredSize: Size.fromHeight(58.0),
        child: Padding(
          padding: EdgeInsets.only(bottom: 10),
          child: TabBar(
            labelColor: AppColors.colorPrimary,
            labelPadding: EdgeInsets.only(left: 8, right: 8),
            labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
            unselectedLabelColor: Colors.white,
            unselectedLabelStyle:
                TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
            indicatorSize: TabBarIndicatorSize.label,
            isScrollable: true,
            indicatorWeight: 0,
            indicator: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: Colors.white,
            ),
            controller: _tabController,
            tabs: _tabs,
          ),
        ),
      ),
    );
  }

  Widget _buildReportBody() {
    return TabBarView(
      controller: _tabController,
      children: _provideTabScreenList(),
    );
  }

我的標簽 controller 就像一個魅力,在每個 TabBarView 里面我都有一個 statefulWidget 來構建我的報告屏幕。 每個 StatefulWidget 都有一個 api 調用,它為我帶來報告信息,並使用 SetState(){} 方法將信息放在屏幕上。

List<Widget> _provideTabScreenList() {
_tabScreenList.clear();
_tabScreenList.add(PeriodResumeReport(filterData: currentFilter));
_tabScreenList.add(SaleStatisticReport(filterData: currentFilter));
_tabScreenList.add(SalePerDayReport(filterData: currentFilter));
return _tabScreenList;
}


class PeriodResumeReport extends StatefulWidget {
  final _periodResumeReportState = _PeriodResumeReportState();
  final SelectedFilter filterData;

  PeriodResumeReport({Key key, @required this.filterData}) : super(key: key);

  @override
  _PeriodResumeReportState createState() => _periodResumeReportState;

  //My tabController has a listener that detect the position change and notify the child.
  void isVisible() {
    _periodResumeReportState.isVisible();
  }
}

//I'm using the AutomaticKeepAliveClientMixin to keep the state when i move between the 
childs of the TabBarView
class _PeriodResumeReportState extends State<PeriodResumeReport>
    with AutomaticKeepAliveClientMixin<PeriodResumeReport> {
  var _loadingData = false;
  var _apiErrorMessage = "";
  var _hasResponseFromApi = false;

  var _response = ...api response;

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  //Load first data when the screen visible for the first time.
  @override
  void initState() {
    super.initState();
    _reloadData();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      children: <Widget>[
        _loadingData ? LinearProgressIndicator() : Container(),
        _apiErrorMessage.isNotEmpty
            ? Padding(...)
            : Container(),
        _hasResponseFromApi ? _buildTotalOfSalesContainer() : Container(),
        _hasResponseFromApi ? _buildComparativeChart() : Container(),
      ],
    );
  }

  Widget _buildTotalOfSalesContainer() {
    return ...;
  }

  Widget _buildComparativeChart() {
    return ...;
  }

  //reload data if the user return to the screen
  void isVisible() {
    _reloadData();
  }

  Future<void> _reloadData() async {
    //show ProgressBar and clear apiError
    setState(() {
      _loadingData = true;
      _apiErrorMessage = "";
    });

    try {
      var response = .... api call ....

      ....
      .... handle api response
      .... 

      setState(() {
        _response = response;
        _loadingData = false;
        _hasResponseFromApi = true;
      });
  }

  @override
  bool get wantKeepAlive => true;
}

使用此代碼,一切正常。 但是有一個問題,如果我將第一個標簽滑動到第二個標簽,然后再滑動到第三個標簽,一切都可以。 如果我在第一個選項卡中並單擊以移動到第三個選項卡而不是傳遞到之前的第二個選項卡,則會出現問題。 這樣做,第二個選項卡會因以下錯誤而崩潰:

Exception has occurred.
FlutterError (setState() called after dispose(): _SaleStatisticReportState#8c846(lifecycle 
state: defunct)
This error happens if you call setState() on a State object for a widget that no longer 
appears in the widget tree (e.g., whose parent widget no longer includes the widget in its. 
build). This error can occur when code calls setState() from a timer or an animation 
callback.
The preferred solution is to cancel the timer or stop listening to the animation in the 
dispose() callback. Another solution is to check the "mounted" property of this object 
before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object 
is retaining a reference to this State object after it has been removed from the tree. To 
avoid memory leaks, consider breaking the reference to this object during dispose().)

在這一行:

@override
void setState(fn) {
    if(mounted){
      super.setState(fn);
    }
}

我不知道有什么問題,也許從第一個選項卡直接滑動到第三個選項卡的動作運行 isVisible 方法並在第二個選項卡上啟動 api 調用但是,因為我在第三個選項卡上,setState(){}在第二次崩潰。 我如何解決這個問題?

問題出在 _reloadData 方法中,因為您在 initstate 中調用此方法,因此此方法調用 setState,即使屏幕未掛載。 要解決此問題,請嘗試以下解決方法。

在以下解決方案中,我確保已安裝屏幕,如果已安裝,則僅調用 setState。

Future<void> _reloadData() async {
    //show ProgressBar and clear apiError
    if(mounted){
    setState(() {
      _loadingData = true;
      _apiErrorMessage = "";
    });
    }

    try {
      var response = .... api call ....

      ....
      .... handle api response
      .... 
      if(mounted)
      setState(() {
        _response = response;
        _loadingData = false;
        _hasResponseFromApi = true;
      });
      }
  }

更新:

class DeleteWidget extends StatefulWidget {
  @override
  _DeleteWidgetState createState() => _DeleteWidgetState();
}

class _DeleteWidgetState extends State<DeleteWidget> {
  int clockHours = 10;
  int clockMinutes = 10;
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        backgroundColor: Colors.teal,
        appBar: AppBar(
          title: const Text('Tabbed AppBar'),
          bottom: TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: "1"),
              Tab(text: "2"),
              Tab(text: "3"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Home1(),
            Home2(),
            Home3(),
          ],
        ),
      ),
    );
  }
}

class Home1 extends StatefulWidget {
  @override
  _Home1State createState() => _Home1State();
}

class _Home1State extends State<Home1> {
  @override
  void initState() {
    super.initState();
    callme();
  }

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class Home2 extends StatefulWidget {
  @override
  _Home2State createState() => _Home2State();
}

class _Home2State extends State<Home2>
    with AutomaticKeepAliveClientMixin<Home2> {
  @override
  void initState() {
    super.initState();

    callme();
  }

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));
    // if (mounted) {
    setState(() {});
    //}
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}

class Home3 extends StatefulWidget {
  @override
  _Home3State createState() => _Home3State();
}

class _Home3State extends State<Home3>
    with AutomaticKeepAliveClientMixin<Home3> {
  @override
  void initState() {
    super.initState();
    callme();
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}

更新:

如何從父小部件調用子方法。

class DeleteWidget extends StatefulWidget {
  @override
  _DeleteWidgetState createState() => _DeleteWidgetState();
}

class _DeleteWidgetState extends State<DeleteWidget> {
  GlobalKey<Home1State> _keyChild1;

  @override
  void initState() {
    super.initState();
    _keyChild1 = GlobalKey();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          RaisedButton(
            child: Text("press"),
            onPressed: () {
              _keyChild1.currentState.callme();
            },
          ),
          Home1(
            key: _keyChild1,
          )
        ],
      ),
    );
  }
}

class Home1 extends StatefulWidget {
  Home1({Key key}) : super(key: key);

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

class Home1State extends State<Home1> {
  callme() {
    print("method call from parent");
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("Home1"),
    );
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM