颤振 + 块 6.0.6。BlocBuilder 的“builder”回调未提供与“buildWhen”回调相同的状态

use*_*900 4 dart flutter bloc flutter-bloc

我正在构建一个 tic-tak-toe 应用程序,并决定学习 Flutter 的 BLoC。我的小部件有问题BlocBuilder

正当我这么想的时候。每次块构建器小部件监听的 Cubit/Bloc 发出新状态时,块构建器都会执行以下例程:

  1. 调用buildWhen回调,传递先前的状态作为previous参数,并将新发出的状态作为current参数。

  2. 如果buildWhen回调返回 true 则重建。

  3. 在重建过程中,调用builder回调函数,传递给定的上下文作为context参数,并将新发出的状态作为state参数。此回调返回我们返回的小部件。

所以结论是current调用的参数buildWhen总是等于调用state的参数builder。但实际上却有所不同:

BlocBuilder<GameCubit, GameState>(
      buildWhen: (previous, current) => current is SetSlotSignGameState && (current as SetSlotSignGameState).slotPosition == widget.pos,
      builder: (context, state) {
        var sign = (state as SetSlotSignGameState).sign;
        // Widget creation goes here...
      },
    );
Run Code Online (Sandbox Code Playgroud)

builder回调中,它抛出:

构建 BlocBuilder<GameCubit, GameState>(dirty, state: _BlocBuilderBaseState<GameCubit, GameState>#dc100): type 'GameState' is not a subtype of type 'SetSlotSignGameState' in typecast 引发以下 _CastError 相关导致错误的小部件是: BlocBuilder<GameCubit, GameState>

我发出类中状态的方法GameCubit

// [pos] is the position of the slot clicked
void setSlotSign(Vec2<int> pos) {

    // Some code

    emit(SetSlotSignGameState(/* Parameter representing the sign that is being placed in the slot*/, pos));

    // Some code

    emit(TurnChangeGameState());
}
Run Code Online (Sandbox Code Playgroud)

简要介绍一下状态类型SetSlotSignGameState当用户点击 tic-tac-toe 网格中的插槽并且该插槽为空时发出。所以这个状态意味着我们需要改变某个槽位的符号。TurnChangeGameState当我们需要轮到下一个玩家时发出。

临时解决办法。现在,我通过将回调的状态保存在小部件状态buildWhen的私有字段中,然后从构建器中使用它来修复它。也有这个问题,但我可以将检查从回调移至回调。该解决方案的缺点是非常不优雅且不方便。BlocListenerlistenWhenlisten

jor*_*iel 6

buildWhen在初始状态或 Flutter 请求重建时绕过(甚至不被调用)。

我创建了一个小“测试”来强调:

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(BlocTestApp());
}

class BlocTestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider<TestCubit>(
        // Create the TestCubit and call test() event right away
        create: (context) => TestCubit()..test(),
        child: BlocBuilder<TestCubit, String>(
          buildWhen: (previous, current) {
            print("Call buildWhen(previous: $previous, current: $current)");
            return false;
          },
          builder: (context, state) {
            print("Build $state");
            return Text(state);
          },
        ),
      ),
    );
  }
}

class TestCubit extends Cubit<String> {
  TestCubit() : super("Initial State");

  void test() {
    Future.delayed(Duration(seconds: 2), () {
      emit("Test State");
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

输出:

I/flutter (13854): Build Initial State
I/flutter (13854): Call buildWhen(previous: Initial State, current: Test State)
Run Code Online (Sandbox Code Playgroud)

从输出中可以看出,初始状态是立即构建的,无需调用buildWhen. 仅当状态发生变化时才buildWhen进行检查。


其他参考资料

Flutter Bloc 库的创建者 (@felangel)也在这里概述了这种行为:

这是预期的行为,因为 BlocBuilder 重建有两个原因:

  1. 集团状态发生变化
  2. Flutter 将小部件标记为需要重建。

buildWhen 将阻止由 1 触发的构建,但不会阻止由 2 触发的构建。在这种情况下,当语言更改时,整个小部件树可能会被重建,这就是为什么尽管 buildWhen 仍会重建 BlocBuilder。


可能的解决方案

在您的情况下,根据您透露的少量代码,最好将整个 Tic-Tac-Toe 配置存储在状态中并使用 BLOC 事件来更改它。这样就不需要那个buildWhen条件了。

或者在函数内部进行检查,builder如果逻辑允许您这样做(这是 BLOC 最常用的解决方案)。

回答你的问题(如果到目前为止还不清楚:D):遗憾的是,你不能依赖于过滤发送到function 的buildWhen状态类型builder