如何等待 Class 初始化?

How to wait for a Class to initialize?

我有一个 class、calendar,它异步读取文件。

// Copyright 2017 <Abhi Agarwal>
// Refer to LICENSE

// Dart Imports
import 'dart:async';
import 'dart:convert';

// Flutter Imports
import 'package:flutter/services.dart';

// Local Imports
import 'event.dart';

/// Class deals with the school-wide calendar Calendar.
class Calendar {
  /// The days off in the year.
  List<Event> daysOff = new List<Event>();

  /// The half-days, not necessarily days off.
  List<Event> halfDays = new List<Event>();

  /// Vacations, extended days off.
  List<Event> vactations = new List<Event>();

  DateTime schoolStart;
  DateTime schoolEnd;
  DateTime schoolMaxEnd;

  Calendar() {
    _addDaysOff();
    //  _addHalfDays();
    //  _addVacations();
    //  _addTimes();
  }

  static const String filePath = "assets/calendar/";

  /// Reads a JSON File specified by the Arguments and returns a Decoded Object.
  Future<List<Map<String, String>>> _readJson(String fileName) async =>
      await JSON.decode(await rootBundle.loadString(filePath + fileName));

  Future _addDaysOff() async {
    const String fileName = "days_off.json";
    final List<Map<String, String>> parsed = await _readJson(fileName);

    for (final Map<String, String> item in parsed) {
      Event event = new Event(item["name"], DateTime.parse(item["date"]));
      daysOff.add(event);
    }
  }
}

一切正常,但是当我想 运行 一个简单的测试时,问题就出现了。

// Copyright 2017 <Abhi Agarwal>
// Refer to LICENSE

import 'package:flutter/material.dart';

import 'definitions/calendar/calendar.dart';

void main() {
  Calendar myCalendar = new Calendar();
  runApp(new Center(child: new Text(myCalendar.daysOff[0].name)));
}

使用 flutter run,我得到 RangeError (index): Invalid value: Valid value range is empty: 0。这是有道理的,List只有在初始化的时候才会有元素。问题是,在数据加载完成之前,我该如何放置占位符?

您可以重构您的 Calendar class 以公开 Future 以在 Calendar 准备好使用时完成:

// Private constructor, use create() to get an instance
Calendar._();

// Future that completes when the new Calendar is ready to use
static Future<Calendar> create() async {
  Calendar calendar = Calendar._();
  await calendar._addDaysOff();
  //    await calendar._addHalfDays();
  //    await calendar._addVacations();
  //    await calendar._addTimes();
  return calendar;
}

然后你可以await Future,例如:

main() async {
  Calendar myCalendar = await Calendar.create();
  runApp(new Center(child: new Text(myCalendar.daysOff[0].name)));
}

如果您希望 Calendar 在您的 Flutter 小部件树中更深的某个地方被 StatefulWidget 实例化而不是让它实例化,您可以等待 Calendar 使用 FutureBuilder 进行初始化活在根本上。

来自Collin Jackson helped me immensely, but it took some time for me to understand the context enough to implement it for my needs. Below is a more generic example of this which uses FutureBuilder, as Collin suggested, and can be run directly in DartPad的答案。

import 'package:flutter/material.dart';

class Delay {
  Delay.init();

  static Future<Delay> create() async {
    Delay delay = Delay.init();
    await delay.delay4();
    return delay;
  }

  Future delay4() {
    return Future.delayed(Duration(seconds: 4), () => null);
  }

  Future<String> delay5() {
    return Future.delayed(Duration(seconds: 5), () => "end of 5");
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Future<Delay> d = initDelay();

  static Future<Delay> initDelay() async {
    return await Delay.create();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Delay>(
      future: d,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return FutureBuilder<String>(
            future: snapshot.data!.delay5(),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text("result: ${snapshot.data}");
              } else if (snapshot.hasError) {
                return Text("result: error5");
              } else {
                return Text("result: loading5 ...");
             }
          });
        } else if (snapshot.hasError) {
          return Text("result: error4");
        } else {
          return Text("result: loading4 ...");
        }
      }
    );
  }
}