Flutter get_it 和 FutureBuilder 无法使用热重载

Posted

技术标签:

【中文标题】Flutter get_it 和 FutureBuilder 无法使用热重载【英文标题】:Flutter get_it and FutureBuilder not working with hot reload 【发布时间】:2020-02-08 03:18:24 【问题描述】:

下面的应用程序按预期工作,但不幸的是它不适用于热重载。我想知道如何使用它进行热重载。

代码所做的只是等待特定的Future<String> 可用,然后在屏幕上显示该字符串。在等待期间,会显示一个进度指示器。

正如我所说,这可以按预期工作,但问题是如果我更改任何内容(我的意思只是简单的装饰性内容)并且热重载启动,应用程序将永远坐在那里显示进度指示器,因为 @987654322 @ 停留在 ConnectionState.waiting 模式。

这是代码更详细的作用:

我在GetIt 中注册了一个String 对象作为单例。由于该字符串包含在 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

【问题讨论】:

在这种情况下,热重载到底会发生什么?我就是这么用的,还没出问题 【参考方案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,);
        
      ),
    );
  

【讨论】:

您好,感谢您告诉我。我很快就会重温信号部分。 作为新的 API Future allReady(); 你在想什么?和 Future isReady(Object instance, String instanceName);而不是直接暴露 Future 或 Stream ?通过这种方式,我们获得了更多的控制权,我可以轻松地纠正上述问题。 这是一个很好的答案,在使用 Firebase.initializeApp() 时对我的 FutureBuilder 效果很好。

以上是关于Flutter get_it 和 FutureBuilder 无法使用热重载的主要内容,如果未能解决你的问题,请参考以下文章

Flutter get_it 和 FutureBuilder 无法使用热重载

Flutter Bloc 状态更改未使用 get_it 更新 UI

Flutter/Dart get_It 单例定位器方法失败

如何将 get_it 用于 Provider.of<ExampleProvider>(context)?

使用 get_it 时颤动 bloc 处理

如何在不使 main() 异步的情况下将异步对象注册到 get_it 包?