Flutter get_it и FutureBuilder не работают с горячей перезагрузкой

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

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

Как я уже сказал, это работает, как ожидалось, но проблема в том, что если я что-то изменю (я имею в виду просто косметические вещи) и сработает горячая перезагрузка, приложение просто будет постоянно показывать индикатор прогресса, потому что connectionState остается в режиме ConnectionState.waiting. .

Вот что делает код более подробно:

Я регистрирую объект String как синглтон в GetIt. Поскольку эта строка заключена в Future, код должен дождаться ее готовности, прежде чем ее можно будет зарегистрировать.

(В реальном коде я загружаю строку JSON из актива. Вот почему она находится в Future.)

FutureBuilder ожидает доступности строкового синглтона, ожидая, пока GetIt сигнализирует о свойстве с именем readyFuture.

Когда строка становится доступной, FutureBuilder отображает ее на экране. А пока отображается CircularProgressIndicator.

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

GetIt locator = GetIt.instance;

void setupLocator() async {
  final str = await Future<String>.delayed(Duration(seconds: 3), () => "hello");
  locator.registerSingleton(str, signalsReady: true);
  locator.signalReady(str);
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<void>(
        future: locator.readyFuture,
        builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
          Widget result;

          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.active:
            case ConnectionState.waiting:
              result = Center(child: CircularProgressIndicator());
              break;
            case ConnectionState.done:
              if (snapshot.hasError)
                result = Center(child: Text('Error: ${snapshot.error}'));
              else
                result = Center(child: Text('${locator.get<String>()}'));
              break;
          }

          return result;
        }
      ),
    );
  }
}

В моем pubspec.yaml есть следующие зависимости:

  get_it: ^3.0.1
  provider: ^3.1.0

person dan-gph    schedule 10.10.2019    source источник
comment
Что именно в этом случае происходит при горячей перезагрузке? Я его очень часто использую и проблем пока не было   -  person Thomas    schedule 21.11.2019


Ответы (1)


Проблема в том, что GetIt использует широковещательный поток GetIt.ready, чтобы сигнализировать о своей готовности. Проблема с широковещательным потоком заключается в том, что сигнал теряется при горячей перезагрузке.

Другая, отдельная проблема состоит в том, что если нет слушателей GetIt.ready широковещательного потока, то сигнал готовности будет потерян. Таким образом, существует состояние гонки: вы должны убедиться, что FutureBuilder слушает GetIt.ready до того, как будет отправлен сигнал готовности, а это может быть невозможно в целом.

Решение, которое я придумал, заключалось в использовании объекта Completer в качестве сигнала готовности. (См. locatorReady в приведенном ниже коде.) Я добавил слушателя к GetIt.ready, который сигнализирует об объекте Completer.

FutureBuilder ждет свойства Completer.future.

(Я новичок во Flutter, но мне кажется, что использование широковещательного потока в GetIt было не лучшим выбором. Я думаю, что Completer было бы лучше.)

Это обновленный код:

import 'dart:async';

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

GetIt locator = GetIt.instance;
Completer<void> locatorReady = Completer<void>();

void setupLocator() async {
  final str = await Future<String>.delayed(Duration(seconds: 8), () => "hello");
  locator.registerSingleton(str, signalsReady: true);
  locator.ready.listen((_){locatorReady.complete();});
  locator.signalReady(str);
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<void>(
        future: locatorReady.future,
        builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
          Widget result;

          switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.active:
          case ConnectionState.waiting:
            result = Text('Awaiting result ...');
            break;
          case ConnectionState.done:
            if (snapshot.hasError)
              result = Text('Error: ${snapshot.error}');
            else
              result = Text('Result: ${locator.get<String>()}');
            break;
          }

          return Center(child: result,);
        }
      ),
    );
  }
}
person dan-gph    schedule 11.10.2019
comment
Привет, спасибо, что сообщили мне. Я скоро вернусь к сигнальной части. - person Thomas; 21.11.2019
comment
Что вы думаете об этом как о новом API Future ‹void› allReady (); и будущее ‹void› isReady ‹T› ({экземпляр объекта, String instanceName}); вместо того, чтобы открывать будущее или поток напрямую? Таким образом мы получаем больший контроль, и я могу легко исправить указанную выше проблему. - person Thomas; 03.12.2019
comment
Это отличный ответ, хорошо сработавший для моего FutureBuilder при использовании Firebase.initializeApp(). - person Jake; 13.04.2021