【问题标题】:Flutter - trigger navigation when Provider variable changesFlutter - Provider 变量更改时触发导航
【发布时间】:2020-04-13 17:58:47
【问题描述】:

我正在尝试在初始应用启动时显示启动画面,直到我正确检索到所有数据。检索由名为“ProductData”的类完成。一旦准备好,我想从启动页面导航到应用程序的主屏幕。

不幸的是,我找不到触发运行这种导航并侦听提供程序的方法的好方法。

这是我用来测试这个想法的代码。具体来说,我想运行命令 Navigator.pushNamed(context, 'home');当变量 shouldProceed 变为 true 时。不幸的是,下面的代码给了我错误,“在构建期间调用了 setState() 或 markNeedsBuild()。”

import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';=
import 'package:provider/provider.dart';

class RouteSplash extends StatefulWidget {
  @override
  _RouteSplashState createState() => _RouteSplashState();
}

class _RouteSplashState extends State<RouteSplash> {
  bool shouldProceed = false;

  @override
  Widget build(BuildContext context) {
    shouldProceed =
        Provider.of<ProductData>(context, listen: true).shouldProceed; 
    if (shouldProceed) {
      Navigator.pushNamed(context, 'home'); <-- The error occurs when this line is hit.
    } else {
      return Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }
  }
}

有没有更好的方法来根据提供者的结果导航到页面?

【问题讨论】:

    标签: flutter triggers navigation splash-screen provider


    【解决方案1】:

    如果您仍在等待数据,您应该显示加载初始屏幕,而不是尝试导航到新视图,并且一旦更改显示您的主主视图,如下所示:

    import 'package:catalogo/firebase/ProductData.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    class Main extends StatefulWidget {
      @override
      _MainState createState() => _MainState();
    }
    
    class _MainState extends State<Main> {
      bool shouldProceed = Provider.of<ProductData>(context, listen: true).shouldProceed;
    
      @override
      Widget build(BuildContext context) {
        if(shouldProceed){
          return Home();
        }else{
          return RouteSplash();
        }
      }
    }
    

    【讨论】:

    • 我已经见过这种方法几次了。令我烦恼的是,由于此场景中没有导航器,因此屏幕转换没有视觉效果。新屏幕突然弹出。这是接受还是放弃,还是可以改进?
    • 好吧,你可以使用Navigator,但这会迫使你从Navigator堆栈中删除RouteSplash,因为你不希望你的用户能够导航回来到启动画面。
    • 我处于与上述相同的情况:当我尝试推送由我的 BLoC 的状态更改触发的新屏幕时,我收到“在状态期间调用 setState 或 markNeedsBuild”。我发现的唯一方法是使用GlobalKey 来访问Navigator 状态,而不需要通常需要的BuildContext,并直接从BLoC 内部推送。对我来说,这也不是很干净。在我看来,导航应该留在应用程序的 UI 部分。还有其他方法吗?
    • 今天偶然发现:github.com/felangel/bloc/issues/201... 不幸的是,大量的 BLoC 计数器教程使得很难找到真正重要的东西。感谢您让我继续寻找!
    【解决方案2】:

    在本例中使用 BlocListener:

    BlocListener(
        bloc: BlocProvider.of<DataBloc>(context),
        listener: (BuildContext context, DataState state) {
            if (state is Success) {              
                Navigator.of(context).pushNamed('/details');
            }              
        },
        child: BlocBuilder(
            bloc: BlocProvider.of<DataBloc>(context),
            builder: (BuildContext context, DataState state) {        
                if (state is Initial) {
                    return Text('Press the Button');
                }
                if (state is Loading) {
                    return CircularProgressIndicator();
                }  
                if (state is Success) {
                    return Text('Success');
                }  
                if (state is Failure) {
                    return Text('Failure');
                }
            },
        }
    )
    

    来源:https://github.com/felangel/bloc/issues/201

    【讨论】:

      【解决方案3】:

      我想我有一个可以满足 OP 要求的解决方案。如果您将初始屏幕设为Stateful,则可以添加PostFrameCallback。这避免了在 build 运行时调用 Navigator 的任何问题。然后,您的回调可以调用Provider 需要读取数据的任何例程。这个读取数据例程可以传递一个包含Navigator 命令的进一步回调。

      在我的解决方案中,我添加了进一步的回调,以便启动屏幕至少可见一秒钟(您可以在此处选择您认为合理的持续时间)。不幸的是,这会产生竞争条件,因此我需要导入 synchronized 包以避免出现问题。

      import 'package:flutter/material.dart';
      import 'package:reflect/utils/constants.dart';
      import 'category_screen.dart';
      import 'package:provider/provider.dart';
      import 'package:reflect/data_models/app_prefs.dart';
      import 'dart:async';
      import 'dart:core';
      import 'package:synchronized/synchronized.dart';
      
      class LoadingScreen extends StatefulWidget {
        static const id = 'LoadingScreen';
      
        @override
        _LoadingScreenState createState() => _LoadingScreenState();
      }
      
      class _LoadingScreenState extends State<LoadingScreen> {
        bool readPrefsDone = false;
        bool timeFinished = false;
        Lock _lock = Lock();
      
        void initState() {
          super.initState();
          WidgetsBinding.instance.addPostFrameCallback((_) {
            Provider.of<AppPrefs>(context, listen: false).readPrefs(readDone);
            Timer(Duration(seconds: 1), () {
              timerDone();
            });
          });
        }
      
        void timerDone() async {
          _lock.synchronized(() {
            if (readPrefsDone) {
              pushMainScreen();
            }
            timeFinished = true;
          });
        }
      
        void readDone() {
          _lock.synchronized(() {
            if (timeFinished) {
              pushMainScreen();
            }
            readPrefsDone = true;
          });
        }
      
        void pushMainScreen() {
          Navigator.pushReplacement(
            context,
            PageRouteBuilder(
              pageBuilder: (context, animation, animation2) => CategoryScreen(),
              transitionDuration: Duration(seconds: 1),
            ),
          );
        }
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
              body: Container(
            color: Colors.white,
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Hero(
                    tag: kFishLogoTag,
                    child: Image(
                      image: AssetImage('assets/fish_logo.png'),
                    ),
                  ),
                  SizedBox(
                    height: 30,
                  ),
                  Text(
                    'Reflect',
                    style: TextStyle(
                      fontSize: 30,
                      color: Color(0xFF0000cc),
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          ));
        }
      }
      

      【讨论】:

        【解决方案4】:

        遇到此问题的任何其他人都可以使用此代码

        Future.delayed(Duration.zero, () => Navigate.toView(context));
        

        这会导航到另一个屏幕而不会出现构建错误

        【讨论】:

          猜你喜欢
          • 2020-04-21
          • 2021-02-07
          • 2021-11-12
          • 1970-01-01
          • 2021-10-16
          • 2016-03-07
          • 2020-09-08
          • 2021-07-21
          • 2020-07-19
          相关资源
          最近更新 更多