Flutter - 当Provider变量更改时触发导航

7
我正在尝试在应用程序首次启动时显示一个闪屏,直到我正确检索到所有数据。检索是通过名为“ProductData”的类完成的。一旦准备好,我想从闪屏页面导航到应用程序的主屏幕。
不幸的是,我找不到一个好方法来触发运行这种导航并监听提供程序的方法。
以下是我用来测试这个想法的代码。具体来说,当变量shouldProceed变为true时,我想运行命令Navigator.pushNamed(context,'home')。不幸的是,下面的代码给我带来了错误:“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(),
        ),
      );
    }
  }
}

基于侦听提供者结果导航到页面是否有更好的方法?

4个回答

4

不要尝试导航到新的视图,如果仍在等待数据,则应该显示加载闪屏界面,一旦数据变化,再显示主页视图,就像这样:

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();
    }
  }
}

1
我已经看到过这种方法几次了。让我感到困扰的是,在这种情况下没有Navigator参与,屏幕转换没有任何视觉效果。新屏幕突然弹出来了。这是必须接受的吗?还是可以改进一下? - Paco1
你可以使用 Navigator,但这会强制你从 Navigator 栈中移除 RouteSplash,因为你不希望用户能够返回到闪屏界面。 - J. S.
我和上面描述的情况一样:当尝试通过BLoC的状态更改触发新屏幕时,我会收到 "setState或markNeedsBuild在state期间调用" 的错误。我找到的唯一方法是使用GlobalKey访问Navigator状态而不需要通常必须的BuildContext,并直接从BLoC中推送。对我来说,这也不是非常干净。在我看来,导航应该留在应用程序的UI部分。还有别的方法吗? - Paco1
今天偶然发现了这个:https://github.com/felangel/bloc/issues/201... 不幸的是,大量关于BLoC的计数器教程使得找到真正重要的东西变得非常困难。感谢让我继续寻找! - Paco1

2

其他人面对这个问题可以使用这段代码

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

这将在没有构建错误的情况下导航到另一个屏幕


黑客。爱它。 - benjamin deworsop

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


0

我认为我有一个解决方案,可以实现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,
              ),
            ),
          ],
        ),
      ),
    ));
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接