flutter bloc 以及 bloc 状态更改如何在更新 Widget/UI 之前触发第二次异步调用? (附代码)

flutter bloc and how a bloc state change can then trigger a 2nd async call before updating the Widget/UI with both? (code attached)

如何加载一组本地图像以在小部件中显示(注意这是异步的 - 请参阅下面的函数)由(基于)“设置”的 flutter_bloc 状态变化触发?注意到这个集团也通过 hydrated_bloc 持续存在。所以我要问的用例是:

用例 - 在同一个 Widget 上渲染不同的图像集,以动态显示 2D 点击冒险类型游戏的房间,基于用户去的“​​房间”。房间事件的变化被传递给 SettingsBloc,然后它将确定要移动到的新“房间”。这个新的“房间”状态可以通过 Bloc 概念获得,但是我如何以及在哪里异步加载我需要的这个房间的特定图像集(它们在编译时没有设置)? (使用我拥有的 CustomPainter 将其提供给动态房间渲染小部件 - 即绘制到 canvas 上的图像)。

例如,建议使用哪种方法来执行此操作? (然后理想情况下代码看起来像什么来实现这个)

a) 监听小部件内“房间”的变化,然后触发(在小部件内)调用异步函数动态加载我需要的 ui.Image?但如果是,您如何在 Widget 内的代码中执行此操作? (或者这不是最佳实践)。请参考我下面的代码,它确实 work/run 但似乎发生了无限循环 OR

b) 我是否应该将图像设置为一个单独的集团(例如 images_bloc)。但在这种情况下,执行此操作所需的 flutter 代码是什么:即

c) 另一种方法?


这是我迄今为止对上述方法 (a) 的最佳尝试。在 UI 重新更改背景时有效,但似乎存在无限循环:

记录:

Launching lib/main.dart on iPhone 12 Pro Max in debug mode...
lib/main.dart:1
Xcode build done.                                           29.5s
Connecting to VM Service at ws://127.0.0.1:49364/lyUIDblZgmY=/ws
flutter: game_main.build ----------------------------
flutter: trying to get room data for current room: room2
flutter: About to load imagename:plane.png
flutter: game_main.build ----------------------------
flutter: trying to get room data for current room: room2
flutter: About to load imagename:plane.png
flutter: game_main.build ----------------------------
flutter: trying to get room data for current room: room2
flutter: About to load imagename:plane.png
flutter: game_main.build ----------------------------
ETC ETC

代码:

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:adventure/ui/widgets/game_painter.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:adventure/bloc/settings_cubit.dart';
import 'package:adventure/game_design/room.dart';
import 'package:flutter/material.dart';
import 'package:touchable/touchable.dart';

class GameMain extends StatefulWidget {
  GameMain({Key key}) : super(key: key);
  @override
  _GameMainState createState() => _GameMainState();
}

class _GameMainState extends State<GameMain> {
  final RoomsData roomsData = RoomsData.getPopulated();
  ui.Image _backgroundImage;

  Future<ui.Image> _loadImageAsync(imageString) async {
    ByteData bd = await rootBundle.load(imageString);
    final Uint8List bytes = Uint8List.view(bd.buffer);
    final ui.Codec codec = await ui.instantiateImageCodec(bytes);
    final ui.Image image = (await codec.getNextFrame()).image;
    return image;
  }

  Future<void> _updateBackgroundImageState(String imagename) async {
    print('About to load imagename:$imagename');
    final image = await _loadImageAsync('assets/images/$imagename');
    setState(() {
      _backgroundImage = image;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('game_main.build ----------------------------');

    // Get Persistant Data Settings 
    SettingsCubit settingsCubit = context.watch<SettingsCubit>();

    // Get Room Configuration (for room we're now in)
    print('Trying to get room data for current room: ${settingsCubit.state.currentRoom}');
    Room roomData = roomsData.getRoom(settingsCubit.state.currentRoom);

    // Update Background Image
    _updateBackgroundImageState(roomData.backgroundImage);
    
    // Create and return Canvas
    return Container(
      child: BlocBuilder<SettingsCubit, SettingsState>(
        builder: (context, state) {
          return Stack(children: <Widget>[

            CanvasTouchDetector(
              builder: (context) => CustomPaint(
                painter: GamePainter(context, settingsCubit, roomData, _backgroundImage)
              )
            ),

            FlatButton(
              // For testing changes to background image
              color: Colors.blue,
              textColor: Colors.white,
              onPressed: () {
                var newRoomString = settingsCubit.state.currentRoom == 'room1' ? 'room2' : 'room1';
                settingsCubit.setRoom(newRoomString);
              },
              child: Text(
                "Flat Button",
                style: TextStyle(fontSize: 20.0),
              ),
            ),

          ]);
        }
      )
    );

  }
}

你的代码的问题是你把 setStatus 放在 build 里面,所以它调用 build -> _updateBackgroundImage -> setStatus -> rebuild -> _updateBackgroundImage ...(无限)

我认为日期更新流程是这样的:

change SettingsCubit -> get RoomData ->  (wait for async image data) -> _updateBackgroundImage

关键是如果图片还在加载中你想显示什么?

答案可能是这样的:

  1. 更改background/room直到图片加载完毕
  2. 显示加载指示器
  3. 先显示同步数据,后更改背景

最后两个可以用 FutureBuilder 轻松完成。但对于 bloc 概念,你可以在 SettingsCubit 中添加新的状态并在加载时控制图像状态

class SettingsCubit extends Cubit{
  ...
  Future setRoom(String newRoom) async {
    // emit state (imageIsLoading = true)
    // load image async func
    // emit state (imageIsLoading = false, image data update in state)
  }
}
...

@override
Widget build(BuildContext context) {
  SettingsCubit settingsCubit = context.watch<SettingsCubit>();

  if(settingsCubit.state.imageIsLoading){
    // show what do you want to show when image is not ready
  }else{
    // show room with image (ex. from settingsCubit.state.imageData)
  }

当然你可以添加新的 bloc,比如 ImagesBloc 以获得更结构化的代码。