You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Bloc状态仅首次更新 加载指示器重复触发失效问题

问题根因

加载指示器仅首次点击生效,是三处代码逻辑错误共同导致的:

  • 违反flutter_bloc的状态设计原则:flutter_bloc要求状态为不可变对象,原有代码直接修改现有state实例的loadingState属性,属于可变状态操作;且连续emit同一引用的状态时,bloc内部会拦截重复的状态更新,不会触发UI重建。
  • 业务状态类实现缺陷:HomeState构造函数没有向父类BaseState传递loadingState参数,所有新创建的HomeState实例的loadingState永远是默认值initial;同时copyWith方法没有覆盖loadingState字段,业务逻辑更新count时生成的新状态会丢失加载状态。
  • 视图层逻辑错误:BaseView中存在多余的错误BlocProvider,上层已经通过MultiBlocProvider注入了HomeCubit,BaseView的build方法重复创建了类型不匹配的BaseCubit实例,造成上下文cubit引用混乱。
修复方案
  1. 重构状态基类与业务状态类:所有状态属性声明为final保证不可变,子类构造函数正确传递父类字段,copyWith方法覆盖全量字段(包括父类的加载状态字段)。
  2. 改造BaseCubit的通用请求方法:禁止直接修改现有状态属性,所有状态更新都通过copyWith生成新实例后再调用emit。
  3. 清理视图层冗余代码:删除BaseView中多余的BlocProvider,直接使用上层注入的cubit实例。
修复后完整代码
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(create: (_) => HomeCubit()),
      ],
      child: const MaterialApp(
        title: 'Flutter Bloc Demo',
        home: HomeView(),
      ),
    );
  }
}

class HomeView extends BaseView<HomeCubit, HomeState> {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget builder(HomeCubit cubit, HomeState state) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(state.count.toString()),
          ElevatedButton(
            onPressed: cubit.increment,
            child: const Text('Increase'),
          ),
        ],
      ),
    );
  }
}

// 状态基类
enum LoadingState { initial, loading, loaded }

abstract class BaseState {
  const BaseState({
    this.loadingState = LoadingState.initial,
  });
  final LoadingState loadingState;

  BaseState copyWith({LoadingState? loadingState});
}

// 业务状态类
class HomeState extends BaseState {
  final int count;

  const HomeState({
    required this.count,
    super.loadingState,
  });

  @override
  HomeState copyWith({
    int? count,
    LoadingState? loadingState,
  }) {
    return HomeState(
      count: count ?? this.count,
      loadingState: loadingState ?? this.loadingState,
    );
  }
}

// 业务Cubit基类
class BaseCubit<S extends BaseState> extends Cubit<S> {
  BaseCubit(S state) : super(state);

  Future<void> flow(Future<void> Function() function) async {
    // 生成新的加载状态实例,禁止修改原状态
    emit(state.copyWith(loadingState: LoadingState.loading) as S);

    await function();

    // 生成新的加载完成状态实例
    emit(state.copyWith(loadingState: LoadingState.loaded) as S);
  }
}

class HomeCubit extends BaseCubit<HomeState> {
  HomeCubit() : super(const HomeState(count: 0));

  void increment() {
    flow(() async {
      await Future.delayed(const Duration(seconds: 1));
      emit(state.copyWith(count: state.count + 1));
    });
  }
}

// 视图基类
@immutable
abstract class BaseView<C extends StateStreamable<S>, S extends BaseState>
    extends StatelessWidget {
  const BaseView({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocBuilder<C, S>(
        builder: (context, state) {
          final cubit = context.read<C>();

          if (state.loadingState == LoadingState.loading) {
            return loadingWidget;
          }

          return builder.call(cubit, state);
        },
      ),
    );
  }

  Widget builder(C cubit, S state);

  Widget get loadingWidget => const Center(
        child: CircularProgressIndicator(),
      );
}

内容的提问来源于stack exchange,提问作者Lacrimalis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 09:57:19