草庐IT

dart - Flutter 执行顺序 : build and initState

coder 2023-07-22 原文

我正在尝试创建一个首选项菜单,其中我有三个设置(例如“通知”)与共享首选项一起存储。它们应用于 SwitchListTiles。

每次选择我的设置选项卡时都会出现错误 (I/flutter (22754): Another exception was throwed: 'package:flutter/src/material/switch_list_tile.dart': Failed assertion: line 84 pos 15: 'value != null': 不是真的。)只出现一毫秒。之后会显示正确的设置。当我没有向“ProfileState”中初始化的变量添加默认值时,就会发生这种情况。如果它们有默认值,错误就会消失,但在选项卡选择时开关会“闪烁”,从默认值到共享首选项中的正确值。

我的假设是我的 loadSettings 函数在构建方法之后执行。

我该如何解决?感谢您的帮助。

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class Profile extends StatefulWidget {
  @override
  ProfileState createState() {
    return new ProfileState();
  }
}

class ProfileState extends State<Profile> {
  bool notifications;
  bool trackHistory;
  bool instantOrders;

  @override
  void initState() {
    super.initState();
    loadSettings();
  }

  //load settings
  loadSettings() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      notifications = (prefs.getBool('notifications') ?? true);
      trackHistory = (prefs.getBool('trackHistory') ?? true);
      instantOrders = (prefs.getBool('instantOrders') ?? false);
    });
  }

  //set settings
  setSettings() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setBool('notifications', notifications);
    prefs.setBool('trackHistory', trackHistory);
    prefs.setBool('instantOrders', instantOrders);
  }

  @override
  Widget build(BuildContext context) {
    return new ListView(
      children: <Widget>[
        new Row(
          children: <Widget>[
            new Container(
              padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 8.0),
              child: new Text("General", style: new TextStyle(color: Colors.black54)),
            )
          ],
        ),
        new SwitchListTile(
          title: const Text('Receive Notifications'),
          activeColor: Colors.brown,
          value: notifications,
          onChanged: (bool value) {
            setState(() {
              notifications = value;
              setSettings();
            });
          },
          secondary: const Icon(Icons.notifications, color: Colors.brown),
        ),
        new SwitchListTile(
          title: const Text('Track History of Orders'),
          activeColor: Colors.brown,
          value: trackHistory,
          onChanged: (bool value) {
            setState((){
              trackHistory = value;
              setSettings();
            });
          },
          secondary: const Icon(Icons.history, color: Colors.brown,),
        ),
        new SwitchListTile(
          title: const Text('Force instant Orders'),
          activeColor: Colors.brown,
          value: instantOrders,
          onChanged: (bool value) {
            setState((){
              instantOrders = value;
              setSettings();
            });
          },
          secondary: const Icon(Icons.fast_forward, color: Colors.brown),
        ),
        new Divider(
          height: 10.0,
          ),
        new Container(
          padding: EdgeInsets.all(32.0),
          child: new Center(
            child: new Column(
              children: <Widget>[
                new TextField(
                )
              ],
            ),
          ),
        ),
        new Divider(
          height: 10.0,
        ),
        new Row(
          children: <Widget>[
            new Container(
              padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 20.0),
              child: new Text("License Information", style: new TextStyle(color: Colors.black54)),
            )
          ],
        ),
        new Container(
          padding: new EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0) ,
          child: new RichText(
            text: new TextSpan(
              text: "With confirming our terms and conditions you accept full usage of your personal data. Yikes!",
              style: new TextStyle(color: Colors.black)
            )
          )
        )
      ]
    );
  }
}

编辑

我尝试使用 Darek 解决方案中推荐的 FutureBuilder 来解决它。最初的错误现在已解决,但现在我面临另一个不便。每次轻按开关时,该选项卡都会完全自行构建,这一点非常明显。此外,开关不再平稳运行。在应用程序启动时,您还可以很快看到等待消息,这不是那么漂亮。

这是代码中的新类:

class ProfileState extends State<Profile> {
  bool notifications;
  bool trackHistory;
  bool instantOrders;
  SharedPreferences prefs;

  @override
  void initState() {

    super.initState();
    loadSettings();
  }

  //load settings
  Future<String> loadSettings() async {
    prefs = await SharedPreferences.getInstance();
    notifications= (prefs.getBool('notifications') ?? true);
    trackHistory = (prefs.getBool('trackHistory') ?? true);
    instantOrders= (prefs.getBool('instantOrders') ?? true);
  }

  //set settings
  setSettings() async {
    prefs.setBool('notifications', notifications);
    prefs.setBool('trackHistory', trackHistory);
    prefs.setBool('instantOrders', instantOrders);
  }

  @override
  Widget build(BuildContext context) {
    var profileBuilder = new FutureBuilder(
      future: loadSettings(), // a Future<String> or null
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return new Text('No preferences');
          case ConnectionState.waiting:
            return new Text('Loading preferences');
          case ConnectionState.done:
            if (snapshot.hasError)
              return new Text('Error: ');
            else
              return new Column(
                  children: <Widget>[
                    new Row(
                      children: <Widget>[
                        new Container(
                          padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 8.0),
                          child: new Text("General", style: new TextStyle(color: Colors.black54)),
                        )
                      ],
                    ),
                    new SwitchListTile(
                      title: const Text('Receive Notifications'),
                      activeColor: Colors.brown,
                      value: notifications,
                      onChanged: (bool value) {
                        setState(() {
                          notifications = value;
                          setSettings();
                        });
                      },
                      secondary: const Icon(Icons.notifications, color: Colors.brown),
                    ),
                    new SwitchListTile(
                      title: const Text('Track History of Orders'),
                      activeColor: Colors.brown,
                      value: trackHistory,
                      onChanged: (bool value) {
                        setState((){
                          trackHistory = value;
                          setSettings();
                        });
                      },
                      secondary: const Icon(Icons.history, color: Colors.brown,),
                    ),
                    new SwitchListTile(
                      title: const Text('Force instant Orders'),
                      activeColor: Colors.brown,
                      value: instantOrders,
                      onChanged: (bool value) {
                        setState((){
                          instantOrders = value;
                          setSettings();
                        });
                      },
                      secondary: const Icon(Icons.fast_forward, color: Colors.brown),
                    ),
                    new Divider(
                      height: 10.0,
                    ),
                    new Row(
                      children: <Widget>[
                        new Container(
                          padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 20.0),
                          child: new Text("License Information", style: new TextStyle(color: Colors.black54)),
                        )
                      ],
                    ),
                    new Container(
                        padding: new EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0) ,
                        child: new RichText(
                            text: new TextSpan(
                                text: "With confirming our terms and conditions you accept full usage of your personal data. Yikes!",
                                style: new TextStyle(color: Colors.black)
                            )
                        )
                    )
                  ]
              );
        }
      },
    );
    return new Scaffold(
      body: profileBuilder,
    );


  }
}

最佳答案

State 的生命周期对象去 createState -> initState -> didChangeDependencies -> build (有关详细信息,请参阅链接文档)。因此,就您而言,这不是订购问题。实际发生的是 loadSettings正在被调用,但是一旦它到达 await一个Future返回并继续执行调用者(请参阅 Dart docs 中的 async/await)。因此,正在构建您的小部件树并使用您的初始空值,然后执行异步部分并初始化您的变量和 setState被称为触发重建,它工作正常。

您需要使用的是 FutureBuilder这样您就可以在 Future 完成时相应地构建 UI:

new FutureBuilder(
  future: _calculation, // a Future<String> or null
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none: return new Text('Press button to start');
      case ConnectionState.waiting: return new Text('Awaiting result...');
      default:
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        else
          return new Text('Result: ${snapshot.data}');
    }
  },
)

在上面的示例中,您将替换 _calculationloadSettings并返回 none 中的相关 UI和 waiting状态(后者将是你的 SwitchListTile s)。

关于dart - Flutter 执行顺序 : build and initState,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51963497/

有关dart - Flutter 执行顺序 : build and initState的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  3. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  4. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  5. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  6. ruby-on-rails - rbenv:从 RVM 移动到 rbenv 后,在 Jenkins 执行 shell 中找不到命令 - 2

    我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions

  7. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  8. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

  9. ruby-on-rails - 在 RSpec 中,如何以任意顺序期望具有不同参数的多条消息? - 2

    RSpec似乎按顺序匹配方法接收的消息。我不确定如何使以下代码工作:allow(a).toreceive(:f)expect(a).toreceive(:f).with(2)a.f(1)a.f(2)a.f(3)我问的原因是a.f的一些调用是由我的代码的上层控制的,所以我不能对这些方法调用添加期望。 最佳答案 RSpecspy是测试这种情况的一种方式。要监视一个方法,用allowstub,除了方法名称之外没有任何约束,调用该方法,然后expect确切的方法调用。例如:allow(a).toreceive(:f)a.f(2)a.f(1)

  10. ruby-on-rails - 只有当不是 nil 时才执行映射? - 2

    如果names为nil,则以下中断。我怎样才能让这个map只有在它不是nil时才执行?self.topics=names.split(",").mapdo|n|Topic.where(name:n.strip).first_or_create!end 最佳答案 其他几个选项:选项1(在其上执行map时检查split的结果):names_list=names.try(:split,",")self.topics=names_list.mapdo|n|Topic.where(name:n.strip).first_or_create!e

随机推荐