Flutter: инициализация переменных при запуске - PullRequest
0 голосов
/ 20 мая 2018

Я пытаюсь использовать значения, сохраненные в SharedPreferences, для инициализации нескольких переменных в моем приложении.Во Flutter SharedPreferences являются асинхронными, поэтому в результате код, инициализируемый позже в коде, создает проблемы с моим приложением, поскольку некоторые переменные равны нулю при вызове метода сборки.

Вот небольшойтестовое приложение 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;

  @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

Вы можете видеть это, когда я пытался получить переменную из SharedPreferences назапуска, поскольку SharedPreference является асинхронным, itemNo имеет значение null.Затем приложение запускает метод сборки и запускает метод сборки для itemNo = null, что приводит к сбоям в приложении.

Как только оно получает значение из SharedPreferences, я вызываю setState, который затем вызывает метод сборки сновас правильным значением.Однако первоначальный вызов для сборки с itemNo = null не должен был произойти.

Я хотел бы, чтобы был метод синхронизации для SharedPreferences, но он, кажется, не существует.Как запустить приложение, чтобы переменные правильно инициализировались во Flutter при запуске?

Я попытался решить эту проблему, используя синхронный метод для инициализации моих переменных, записав в файл 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");
  }
}

У меня все еще есть результаты, когда вызывается метод сборкидо полной инициализации переменных, что вызывает сбои приложения.

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

Как инициализировать переменные в Flutter при запуске приложения?

1 Ответ

0 голосов
/ 20 мая 2018

Как правильно указал Гюнтер Цохбахер , 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;

Это следующее использование оператора с нулевым знанием :

itemNo ??= 0;

Заключение

Как вы можете видеть в моем коде, я окружил ваш Text виджет FutureBuilder.В Flutter вы решаете большинство проблем, используя Widget.Я также представил «Загрузка ...» Text, который можно сместить вместо «Hello» Text, пока itemNo все еще загружается.

Нет "хака", который уберет время загрузки и даст вам доступ к вашему itemNo при запуске.Вы либо делаете это, идиоматическим образом, либо задерживаете время запуска.

Вам нужно будет использовать заполнители для загрузки каждый раз, когда вы загружаете что-то, потому что оно просто недоступно мгновенно.

Дополнительно

Кстати, , вы также можете просто удалить "Загрузка ..." Text и всегда возвращать текст "Hello" потому что вы никогда не увидите «Загрузка ...» Text в вашем случае, это просто происходит слишком быстро.

Другой вариант - ускользнуть ConnectionState и просто вернуть Container, если нет данных:

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

В случае, если ваш пользовательский интерфейс не защищен

Вы можете просто выполнить свою логику API в initState с помощью моего *Функция 1062 * делает initState асинхронным следующим образом:

@override
void initState() {
    super.initState();
    fetchSavedItemNo(); // continue your work in the `fetchSavedItemNo` function
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...