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 代码是什么:即
- 用户在 UI 中执行了触发事件并将其传递给 SettingsBloc
的操作
- 然后 SettingsBloc 可能会确定“房间”因此发生变化,change/emit 新的“currentRoom”
- 然后 SettingsBloc 将触发对 ImagesBlock 的异步请求(以某种方式)获取此“房间”的 ui.Images 的新列表,使用下面的异步代码
- 然后 UI/Widget 需要为“String room”和 ui.Image 的
列表获取更改
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
关键是如果图片还在加载中你想显示什么?
答案可能是这样的:
- 更改background/room直到图片加载完毕
- 显示加载指示器
- 先显示同步数据,后更改背景
最后两个可以用 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 以获得更结构化的代码。
如何加载一组本地图像以在小部件中显示(注意这是异步的 - 请参阅下面的函数)由(基于)“设置”的 flutter_bloc 状态变化触发?注意到这个集团也通过 hydrated_bloc 持续存在。所以我要问的用例是:
用例 - 在同一个 Widget 上渲染不同的图像集,以动态显示 2D 点击冒险类型游戏的房间,基于用户去的“房间”。房间事件的变化被传递给 SettingsBloc,然后它将确定要移动到的新“房间”。这个新的“房间”状态可以通过 Bloc 概念获得,但是我如何以及在哪里异步加载我需要的这个房间的特定图像集(它们在编译时没有设置)? (使用我拥有的 CustomPainter 将其提供给动态房间渲染小部件 - 即绘制到 canvas 上的图像)。
例如,建议使用哪种方法来执行此操作? (然后理想情况下代码看起来像什么来实现这个)
a) 监听小部件内“房间”的变化,然后触发(在小部件内)调用异步函数动态加载我需要的 ui.Image?但如果是,您如何在 Widget 内的代码中执行此操作? (或者这不是最佳实践)。请参考我下面的代码,它确实 work/run 但似乎发生了无限循环 OR
b) 我是否应该将图像设置为一个单独的集团(例如 images_bloc)。但在这种情况下,执行此操作所需的 flutter 代码是什么:即
- 用户在 UI 中执行了触发事件并将其传递给 SettingsBloc 的操作
- 然后 SettingsBloc 可能会确定“房间”因此发生变化,change/emit 新的“currentRoom”
- 然后 SettingsBloc 将触发对 ImagesBlock 的异步请求(以某种方式)获取此“房间”的 ui.Images 的新列表,使用下面的异步代码
- 然后 UI/Widget 需要为“String room”和 ui.Image 的 列表获取更改
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
关键是如果图片还在加载中你想显示什么?
答案可能是这样的:
- 更改background/room直到图片加载完毕
- 显示加载指示器
- 先显示同步数据,后更改背景
最后两个可以用 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 以获得更结构化的代码。