Flutter:Image.memory() 失败,字节为 != null': is not true -- 当有数据时

时间:2021-03-30 15:18:26

标签: flutter dart

我正在尝试根据来自后端的 (base64) 数据显示图像,但我不断收到错误 bytes != null': is not true.

这是我的代码:

class _FuncState extends State<Func> {
  Uint8List userIconData;
  
  void initState() {
    super.initState();
    updateUI();
  }

  void updateUI() async {
    await getUserIconData(1, 2, 3).then((value) => userIconData = value);
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        Container(
          child: CircleAvatar(
            backgroundImage: Image.memory(userIconData).image, // <--- problem here
            maxRadius: 20,
          ),
        ),
      ),
    );
  }
}

帮助代码:

Future<Uint8List> getUserIconData(
  role,
  id,
  session,
) async {
  var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
  var response = await http.post(url, body: {
    'method': 'getUserProfilePic',
    'a': id.toString(),
    'b': role.toString(),
    'c': session.toString(),
  });
  if (response.statusCode == 200) {
    Map data = jsonDecode(response.body);
    return base64Decode(data['img']);
  }
  return null;
}

我已使用调试器逐步完成代码,并确认辅助函数正在为图像返回正确的字节序列。

感谢您的指点。

进一步说明。错误还说:

<块引用>

要么断言表明框架本身存在错误,要么我们 应在此错误消息中提供更多信息 帮助您确定并解决根本原因。在任一情况下, 请通过在 GitHub 上提交错误报告此断言

2 个答案:

答案 0 :(得分:2)

这很简单;如果您看一下您的代码,您应该能够完成此操作序列。

  1. 小部件已创建。 无操作。 此时 userIconData 为空。
  2. initState 被调用。 异步 http 调用已启动。 userIconData == null
  3. build 被调用。 发生构建,抛出错误。 userIconData == null
  4. http 调用返回。 userIconData 已设置。 userIconData == 您的图片

由于没有调用 setState,您的构建函数将不会再次运行。如果你这样做了,就会发生这种情况(但你之前仍然会有例外)。

  1. build 被调用。 userIconData 已设置。 userIconData == 您的图片

这里的关键是理解异步调用(任何返回未来并可选择使用 asyncawait 的东西)不会立即返回,而是在稍后的某个时间返回,并且您不能依靠他们在此期间设置了您需要的东西。如果您之前尝试过使用从磁盘加载的图像来执行此操作并且成功,那只是因为 Flutter 执行了一些技巧,这些技巧只有在从磁盘加载是同步的情况下才有可能。

这里有两个选项可以替代您编写代码的方式。

class _FuncState extends State<Func> {
  Uint8List? userIconData;
  
  // if you're using any data from the `func` widget, use this instead
  // of initState in case the widget changes.
  // You could also check the old vs new and if there has been no change
  // that would need a reload, not do the reload.
  @override
  void didUpdateWidget(Func oldWidget) {
    super.didUpdateWidget(oldWidget);
    updateUI();
  }
  
  void updateUI() async {
    await getUserIconData(widget.role, widget.id, widget.session).then((value){
      // this ensures that a rebuild happens
      setState(() =>  userIconData = value);
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Container(
          // this only uses your circle avatar if the image is loaded, otherwise
          // show a loading indicator.
          child: userIconData != null ? CircleAvatar(
            backgroundImage: Image.memory(userIconData!).image,
            maxRadius: 20,
          ) : CircularProgressIndicator(),
        ),
      ),
    );
  }
}

做同样事情的另一种方法是使用 FutureBuilder。

class _FuncState extends State<Func> {
  // using late isn't entirely safe, but we can trust
  // flutter to always call didUpdateWidget before
  // build so this will work.
  late Future<Uint8List> userIconDataFuture;

  @override
  void didUpdateWidget(Func oldWidget) {
    super.didUpdateWidget(oldWidget);
    userIconDataFuture =
        getUserIconData(widget.role, widget.id, widget.session);
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Container(
          child: FutureBuilder(
            future: userIconDataFuture,
            builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
              if (snapshot.hasData) {
                return CircleAvatar(
                    backgroundImage: Image.memory(snapshot.data!).image,
                    maxRadius: 20);
              } else {
                return CircularProgressIndicator();
              }
            },
          ),
        ),
      ),
    );
  }
}

请注意,加载指示器只是一种选择;我实际上建议为您的头像(即灰色的“用户”图像)设置一个硬编码的默认值,在加载图像时将其关闭。

请注意,我在这里使用了空安全代码,因为这将使此答案具有更长的使用寿命,但是要切换回非空安全代码,您只需删除无关的 ?、{{1 }} 和 ! 在代码中。

答案 1 :(得分:1)

错误信息对我来说很清楚。当您将 userIconData 传递给 null 构造函数时,它是 Image.memory

在渲染图像之前使用 FutureBuilder 或条件来检查 userIconData 是否为空,如果是,则手动显示加载指示器,或类似的内容。此外,您还需要实际设置状态以触发重新渲染。不过我会选择前者。