代码之家  ›  专栏  ›  技术社区  ›  Simon Sudhin

颤振:启动时初始化变量

  •  8
  • Simon Sudhin  · 技术社区  · 7 年前

    我正在尝试使用sharedPreferences中保存的值来初始化应用程序中的几个变量。在flutter中,sharedreferences是异步的,因此会导致稍后在代码中初始化变量,这会给我的应用程序带来问题,因为调用build方法时,某些变量为空。

    下面是我为演示这个问题而编写的一个小型测试颤振应用程序:

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:path_provider/path_provider.dart';
    import 'dart:io';
    import 'dart:convert';
    import 'package:shared_preferences/shared_preferences.dart';
    
    class TestingApp extends StatefulWidget {
    
      TestingApp() {}
    
      @override
      State<StatefulWidget> createState() {
    // TODO: implement createState
        return new _CupertinoNavigationState();
      }
    }
    
    class _CupertinoNavigationState extends State<TestingApp> {
    
      int itemNo;
    
      @override
      void initState() {
        super.initState();
    //    SharedPreferences.getInstance().then((sp) {
    //      sp.setInt("itemNo", 3);
    //    });
        SharedPreferences.getInstance().then((sp)  {
          print("sp " + sp.getInt("itemNo").toString());
          setState(() {
            itemNo = sp.getInt("itemNo");
          });
        });
        print("This is the item number " + itemNo.toString());
      }
    
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        print("item number on build " + itemNo.toString());
        return new Text("Hello");
      }
    }
    

    这是控制台中的结果:

    Performing full restart...
    flutter: This is the item number null
    flutter: item number on build null // build method being called and variable is null
    Restarted app in 1 993ms.
    flutter: sp 3
    flutter: item number on build 3
    

    您可以看到,当我在启动时尝试从sharedreferences中获取变量时,由于sharedreference是异步的,所以itemno为空。然后,应用程序运行build方法并在itemno=null上运行build方法,这会导致应用程序崩溃。

    一旦它从sharedPreferences获取值,我就调用setState,然后用正确的值再次调用build方法。但是,不应该发生itemno=null的初始生成调用。

    我希望有一个共享引用的同步方法,但它似乎不存在。如何运行应用程序,以便在启动时在颤振中正确初始化变量?

    我试图通过使用同步方法来解决这个问题,通过写入一个JSON文件,然后使用下面的短flutter测试应用程序从中读取变量来初始化我的变量-对我来说,保存一个变量来初始化似乎是太过杀伤力了,但我还是尝试了一下:

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:path_provider/path_provider.dart';
    import 'dart:io';
    import 'dart:convert';
    import 'package:shared_preferences/shared_preferences.dart';
    
    class TestingApp extends StatefulWidget {
    
      TestingApp() {}
    
      @override
      State<StatefulWidget> createState() {
    // TODO: implement createState
        return new _CupertinoNavigationState();
      }
    }
    
    class _CupertinoNavigationState extends State<TestingApp> {
    
      int itemNo;
      File  jsonFile;
      String fileName = "items.json";
      Directory dir;
      bool fileExists = false;
    
    void createFile(Map content, Directory dir, String fileName) {
    //  print("Creating file for category " + dir.path);
      File file = new File(dir.path + "/" + fileName);
      file.createSync();
      fileExists = true;
      file.writeAsStringSync(json.encode(content));
    }
    
    void writeToFile(int itemNo) {
    //  print("Writing to category file");
      Map itemMap = new Map();
      itemMap['item'] = itemNo;
      if (fileExists) {
        print("category file exists");
        Map jsonFileContent = json.decode(jsonFile.readAsStringSync());
        jsonFileContent.addAll(itemMap);
        jsonFile.writeAsStringSync(json.encode(itemMap));
      } else {
        print("category File does not exists");
        getApplicationDocumentsDirectory().then((Directory directory) {
          dir = directory;
          createFile(itemMap, dir, fileName);
        });
    
      }
    }
    
    fetchSavedItemNo() {
      //load the currency from the saved json file.
      getApplicationDocumentsDirectory().then((Directory directory) {
        dir = directory;
        jsonFile = new File(dir.path+ "/" + fileName);
        fileExists = jsonFile.existsSync();
        setState(() {
          if (fileExists)
            itemNo = json.decode(jsonFile.readAsStringSync())['item'];
          print("fetching saved itemNo " +itemNo.toString());
          if (itemNo == null) {
            itemNo = 0;
          }
        });
    
    
        return itemNo;
        //else the itemNo will just be 0
      });
    }
    
      @override
      void initState() {
        super.initState();
        writeToFile(3);
        setState(() {
          itemNo = fetchSavedItemNo();
        });
    
      }
    
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        print("item number on build " + itemNo.toString());
        return new Text("Hello");
      }
    }
    

    在变量完全初始化之前调用build方法的结果仍然存在,这会导致应用程序崩溃。

    Performing full restart...
    flutter: category File does not exists
    flutter: item number on build null   // the build method is called here
    Restarted app in 1 894ms.
    flutter: fetching saved itemNo 3
    flutter: item number on build 3
    

    如何在应用程序启动时初始化颤振中的变量?

    1 回复  |  直到 7 年前
        1
  •  10
  •   creativecreatorormaybenot    6 年前

    AS Günter Zöchbacher 正确指出, FutureBuilder 是前进的道路。在您的案例中,它看起来是这样的:

    import 'dart:async'; // you will need to add this import in order to use Future's
    
    Future<int> fetchSavedItemNo() async { // you need to return a Future to the FutureBuilder
        dir = wait getApplicationDocumentsDirectory();
        jsonFile = new File(dir.path+ "/" + fileName);
        fileExists = jsonFile.existsSync();
    
        // you should also not set state because the FutureBuilder will take care of that
        if (fileExists)
            itemNo = json.decode(jsonFile.readAsStringSync())['item'];
    
        itemNo ??= 0; // this is a great null-aware operator, which assigns 0 if itemNo is null
    
        return itemNo;
    }
    
    @override
    Widget build(BuildContext context) {
        return FutureBuilder<int>(
            future: fetchSavedItemNo(),
            builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                 if (snapshot.connectionState == ConnectionState.done) {
                     print('itemNo in FutureBuilder: ${snapshot.data}';
                     return Text('Hello');
                 } else
                     return Text('Loading...');
            },
        );
    }
    

    我也改变了你 fetchSavedItemNo 函数相应地返回 Future .

    较短的写作方式:

    if (itemNo != null)
        itemNo = 0;
    

    以下是否使用 null-aware operator :

    itemNo ??= 0;
    

    结论

    正如您在我的代码中看到的,我包围了 Text 小部件 未来建筑 . 在颤振中,使用 Widget 我还介绍了 “正在加载…” 文本 可替换为 “你好” 文本 itemNo 仍在加载。

    没有“黑客”可以删除加载时间并让您访问 伊藤诺 启动时。你要么这样做,惯用的,方式,或者你延迟你的启动时间。

    每次加载时都需要使用占位符进行加载,因为它不是即时可用的。

    附加

    顺便说一句 ,您也可以移除 “正在加载…” 文本 总是把你的 “你好” 文本,因为你会 从未见过 这个 “正在加载…” 文本 在你的情况下,事情发生得太快了。

    另一个选择是逃避 ConnectionState 只需返回 Container 如果没有数据:

    FutureBuilder<int>(
        future: fetchSavedItemNo,
        builder: (BuildContext context, AsyncSnapshot<int> snapshot) => snapshot.hasData 
            ? Text(
                'Hello, itemNo: ${snapshot.data}',
              )
            : Container(),
    )
    

    以防你的用户界面没有受到影响

    您可以在 initState 与我的 胎儿保存期 通过 初始状态 异步如下:

    @override
    void initState() {
        super.initState();
        fetchSavedItemNo(); // continue your work in the `fetchSavedItemNo` function
    }