颤振有状态的小部件在热重载和 pushNamed 之后丢失数据

Posted

技术标签:

【中文标题】颤振有状态的小部件在热重载和 pushNamed 之后丢失数据【英文标题】:flutter stateful widget lose data on hot reload and after pushNamed 【发布时间】:2019-12-17 00:51:58 【问题描述】:

我不确定要查找什么,因此请随时给我指点,以了解在哪里查找。我正在学习,所以这绝对是值得赞赏的。

我刚刚发现我可以在颤振中使用命名路由,并且我正在尝试转移到事物以使我的应用程序的行为更可预测。但是,在某些时候,我的 UserDataContainer Stateful Widget 会丢失他的状态值。所以我有一些用户数据,它消失了。

这里是一些代码。

这是我的用户数据容器:

// app_state_container.dart
//import 'package:advanced_app/models/app_state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:myapp/FoodLib/user.dart';

class UserDataContainer extends StatefulWidget 
// Your apps state is managed by the container
  final UserDataType userdata = UserDataType();
// This widget is simply the root of the tree,
// so it has to have a child!
  final Widget child;

  UserDataContainer(@required this.child
//    @required this.userdata,
      ) 
    print("CREATED USERDATA CONTAINER");
    print("The name: " + this.userdata.name);
  

// This creates a method on the AppState that's just like 'of'
// On MediaQueries, Theme, etc
// This is the secret to accessing your AppState all over your app
  static _UserDataContainerState of(BuildContext context) 
    return (context.inheritFromWidgetOfExactType(_InheritedUserDataContainer)
            as _InheritedUserDataContainer)
        .data;
  

  @override
  _UserDataContainerState createState() => new _UserDataContainerState();


class _UserDataContainerState extends State<UserDataContainer> 
// Just padding the state through so we don't have to
// manipulate it with widget.state.
//  UserDataType userdata  = UserDataType();

  String name = "test";

  @override
  void initState() 
// You'll almost certainly want to do some logic
// in InitState of your AppStateContainer. In this example, we'll eventually
// write the methods to check the local state
// for existing users and all that.
    super.initState();
  

// So the WidgetTree is actually
// AppStateContainer --> InheritedStateContainer --> The rest of your app.
  @override
  Widget build(BuildContext context) 
    return new _InheritedUserDataContainer(
      data: this,
      child: widget.child,
    );
  


// This is likely all your InheritedWidget will ever need.
class _InheritedUserDataContainer extends InheritedWidget 
// The data is whatever this widget is passing down.
  final _UserDataContainerState data;

// InheritedWidgets are always just wrappers.
// So there has to be a child,
// Although Flutter just knows to build the Widget thats passed to it
// So you don't have have a build method or anything.
  _InheritedUserDataContainer(
    Key key,
    @required this.data,
    @required Widget child,
  ) : super(key: key, child: child);

// This is a better way to do this, which you'll see later.
// But basically, Flutter automatically calls this method when any data
// in this widget is changed.
// You can use this method to make sure that flutter actually should
// repaint the tree, or do nothing.
// It helps with performance.
  @override
  bool updateShouldNotify(_InheritedUserDataContainer old)
    if(
    data.widget.userdata.fooditems.length !=
        old.data.widget.userdata.fooditems.length)
      return true;

    
//    print("SAME");
    return false;
  

其中UserDataType 是包含消失数据的类。

这是我使用 Material 类的主要应用程序:

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new DynamicTheme(
        defaultBrightness: Brightness.light,
        data: (brightness) => new ThemeData(
              primarySwatch: Colors.teal,
              brightness: brightness,
            ),
        themedWidgetBuilder: (context, theme) 
          return UserDataContainer(
            child: MaterialApp(
              title: 'Title',
              home: UserLoader(),
              theme: theme,

              routes: 
                // When navigating to the "/" route, build the FirstScreen widget.
                '/userpage': (context) => User(),
                '/settings': (context) => SettingsPage(),
                '/saved_stuff' : (context) => SavedRecipesPage(),
                '/database' : (context) => Database(),
                '/new_list' : (context) => ShoppingKartPage(),
                '/stats' : (context) => StatPage(),
                // When navigating to the "/second" route, build the SecondScreen widget.
//                '/second': (context) => SecondScreen(),
              ,

            ),
          );
        );
  


class UserLoader extends StatelessWidget 
  @override




  Widget build(BuildContext context) 
    print("Version 1");

    final ThemeData theme = Theme.of(context);

    return FutureBuilder<void>(
        future: UserDataContainer.of(context).widget.userdata.init(),
        builder: (BuildContext context, AsyncSnapshot<void> snapshot) 
          if (snapshot.connectionState == ConnectionState.done) 
            print("Build user");




            return User();
           else 
            return SplashScreen();
          
        );
  


class SplashScreen extends StatefulWidget 
  @override
  _SplashScreenState createState() => new _SplashScreenState();


class _SplashScreenState extends State<SplashScreen> 

  @override
  void initState() 
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      body: new Center(
        child: new Image.asset('images/icon.png'),
      ),
    );
  

材料应用程序有我的路线,并且 UserLoader 在那里,所以我可以在启动时加载保存在手机上的一些用户数据。它会放置一个启动画面并等待数据加载完毕。

在我的抽屉里,我已经用指定的版本替换了移动到特定页面的代码:

Navigator.push(
      context,
      MaterialPageRoute(
          builder: (context) => SettingsPage()));

成为

Navigator.of(context).pop();

例如。

但是,现在当我使用该应用程序时,UserDataContainer 有时会包含一个全新的 User() 实例(它是一个 dart 类,而不是小部件),我不知道为什么该应用程序会突然出现这样的行为。

编辑:

我尝试通过使用它而不是我的 futureloader 来删除 FutureBuilder,但 UserContainer 的状态每次都会重新生成(虽然不会再次初始化):

class LoadingScreen extends StatefulWidget 
  @override
  _LoadingScreenState createState() => new _LoadingScreenState();



class _LoadingScreenState extends State<LoadingScreen>

  @override
  void initState() 
    super.initState();

    SchedulerBinding.instance.addPostFrameCallback((_)
      runInitTasks();
    );


  

  @protected
  Future runInitTasks() async 
    await UserDataContainer.of(context).widget.userdata.init();

    Navigator.of(context).pushReplacementNamed('/fridge');
  

  @override
  Widget build(BuildContext context)
    return Scaffold(
      body: new Center(
        child: new Image.asset('images/icon.png'),
      ),
    );
  

【问题讨论】:

我想,但我不确定。问题出在FutureBuilder&lt;void&gt; 及其构建器中,因为在第一次显示User() 页面后,您在其他页面中创建的任何setState 以重建小部件,此UserLoader 页面也将被重建为树.所以我的建议是找到新的方法,不要一次又一次地重建FutureBuilder&lt;void&gt; 有什么建议吗?计时器似乎很随意,但我只发现了这个 您可以使用Navigator.pushReplacementNavigator.pushReplacementNamed 代替return User();。以防止不再重建此页面。代码将是if (snapshot.connectionState == ConnectionState.done) print("Build user"); // Navigator.pushReplacement method return SplashScreen(); 这会触发此错误:在构建期间调用了 setState() 或 markNeedsBuild()。 不是相关问题。数据丢失的第一个问题解决了吗? 【参考方案1】:

Navigation 上数据丢失的原因是因为一旦导航到屏幕正在重建。使用shared_preferences 可以轻松跟踪简单的数据,但如果您想获得更大的灵活性,可以使用状态管理实现,例如使用provider 插件。这是文档中提供的关于如何使用提供程序的sample。

【讨论】:

以上是关于颤振有状态的小部件在热重载和 pushNamed 之后丢失数据的主要内容,如果未能解决你的问题,请参考以下文章

在有状态的小部件中,颤振字段不能是最终的

在调试模式下在颤振上显示特定的小部件

Flutter Provider 仅在热重载后显示结果

如何创建自定义颤振 sdk 小部件,重建颤振和使用新的小部件

提取的小部件内的颤振滑块

如何在颤动的另一个有状态小部件中访问在一个有状态小部件中创建的对象