如何在不强制转换的情况下使用冻结表示共享状态

How to represent shared state with freezed without casting

我正在使用 freezed package to generate state objects which are consumed by the bloc library

我喜欢为小部件的状态定义联合 类 的功能,这样我就可以表达小部件具有的不同且通常不相交的状态。例如:

@freezed
class ResultsReportState with _$ResultsReportState {
  const factory ResultsReportState.loading() = ResultsReportLoading;

  const factory ResultsReportState.success({
    required ReportViewViewModel report,
  }) = ResultsReportSuccess;

  const factory ResultsReportState.refreshing({
    required ReportViewViewModel report,
  }) = ResultsReportRefreshing;

  const factory ResultsReportState.error() = ResultsReportError;
}

在上面的代码片段中,我的意图是在出现错误或加载小部件时不显示任何数据,但我仍然希望在成功加载或用户刷新小部件时显示数据。所以 ResultsReportSuccessResultsReportRefreshing 状态有一个共享状态 ReportViewViewModel。但是,即使在执行类型检查后我也无法访问这些共享属性 as suggested here.

例如,如果没有明确的类型转换,这将不起作用:

class ResultsReport extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ResultsReportBloc, ResultsReportState>(
      builder: (context, state) {
        if (state is ResultsReportSuccess || state is ResultsReportRefreshing) {
          return SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                var serviceCategory = state.report.serviceCategories[index];
                return ServiceCategoryBlock(
                  viewModel: serviceCategory,
                );
              },
              childCount: state.report.serviceCategories.length,
            ),
          );
        } else if (state is ResultsReportLoading) {
          return ResultsScreenLoadingSkeleton();
        } else {
          return SliverFillRemaining(
            child: ErrorStateContent(
              onErrorRetry: () {
                context
                    .read<ResultsReportBloc>()
                    .add(ResultsReportEvent.retryButtonTapped());
              },
            ),
          );
        }
      },
    );
  }
}

但是我没有什么可以显式类型转换的,因为它可能是任何一种类型。所以,我尝试了这种方法,它引入了一个我可以参考的接口:

part of 'results_report_bloc.dart';

abstract class ReportPopulated {
  ReportViewViewModel get report;
}

@freezed
class ResultsReportState with _$ResultsReportState {
  const factory ResultsReportState.loading() = ResultsReportLoading;

  @Implements<ReportPopulated>()
  const factory ResultsReportState.success({
    required ReportViewViewModel report,
  }) = ResultsReportSuccess;

  @Implements<ReportPopulated>()
  const factory ResultsReportState.refreshing({
    required ReportViewViewModel report,
  }) = ResultsReportRefreshing;

  const factory ResultsReportState.error() = ResultsReportError;
}
class ResultsReport extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ResultsReportBloc, ResultsReportState>(
      builder: (context, state) {
        if (state is ReportPopulated) {
          return SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                var serviceCategory = state.report.serviceCategories[index];
                return ServiceCategoryBlock(
                  viewModel: serviceCategory,
                );
              },
              childCount: state.report.serviceCategories.length,
            ),
          );
        } else if (state is ResultsReportLoading) {
          return ResultsScreenLoadingSkeleton();
        } else {
          return SliverFillRemaining(
            child: ErrorStateContent(
              onErrorRetry: () {
                context
                    .read<ResultsReportBloc>()
                    .add(ResultsReportEvent.retryButtonTapped());
              },
            ),
          );
        }
      },
    );
  }
}

但这也需要类型转换。所以,我可以这样做:

class ResultsReport extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ResultsReportBloc, ResultsReportState>(
      builder: (context, state) {
        if (state is ReportPopulated) {
          ReportPopulated currentState = state as ReportPopulated;
          return SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                var serviceCategory = currentState.report.serviceCategories[index];
                return ServiceCategoryBlock(
                  viewModel: serviceCategory,
                );
              },
              childCount: currentState.report.serviceCategories.length,
            ),
          );
        } else if (state is ResultsReportLoading) {
          return ResultsScreenLoadingSkeleton();
        } else {
          return SliverFillRemaining(
            child: ErrorStateContent(
              onErrorRetry: () {
                context
                    .read<ResultsReportBloc>()
                    .add(ResultsReportEvent.retryButtonTapped());
              },
            ),
          );
        }
      },
    );
  }
}

但我想知道为什么必须进行类型转换,因为它感觉很麻烦。欢迎任何人就如何以不同方式实现我的共享状态目标提供任何见解。

我认为您面临的问题可能与 Dart 类型的提升有关,它并不总是像您预期的那样有效。解释的很透彻here.

但是,我如何使用 freezed 处理此问题是使用生成的联合方法。渲染 UI 时,您可以像这样使用它们:

class ResultsReport extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ResultsReportBloc, ResultsReportState>(
      builder: (context, state) => state.maybeWhen(
        loading: () => ResultsScreenLoadingSkeleton(),
        success: (report) => SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              var serviceCategory = report.serviceCategories[index];
              return ServiceCategoryBlock(
                viewModel: serviceCategory,
              );
            },
            childCount: report.serviceCategories.length,
          ),
        ),
        refreshing: (report) => SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              var serviceCategory = report.serviceCategories[index];
              return ServiceCategoryBlock(
                viewModel: serviceCategory,
              );
            },
            childCount: report.serviceCategories.length,
          ),
        ),
        error: () => SliverFillRemaining(
          child: ErrorStateContent(
            onErrorRetry: () {
              context
                  .read<ResultsReportBloc>()
                  .add(ResultsReportEvent.retryButtonTapped());
            },
          ),
        ),
      ),
    );
  }
}

请注意 successrefreshing 状态的代码是重复的,因此您可能应该将其提取到单独的小部件中。