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

Flutter - trigger navigation when Provider variable changes

我试图在应用程序初始启动时显示启动画面,直到我正确检索到所有数据。检索由名为 "ProductData" 的 class 完成。准备就绪后,我想从启动页面导航到应用程序的主屏幕。

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

这是我用来测试这个想法的代码。具体来说,我想 运行 命令 Navigator.pushNamed(context, 'home');当变量 shouldProceed 变为真时。不幸的是,下面的代码给了我错误,"setState() or markNeedsBuild() called during build."

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

是否有更好的方法来根据提供商的结果导航到页面?

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

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

在这个例子中使用 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

我想我有一个解决方案可以满足 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,
              ),
            ),
          ],
        ),
      ),
    ));
  }
}

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

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

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