Flutter 中使用 bloc 的依赖多个下拉列表问题

问题描述 投票:0回答:1

我的屏幕上有 2 个下拉小部件。当我选择第一个下拉列表并想要显示加载小部件时,我收到错误。

我的问题是:

  1. 当我选择区域时,应用程序显示此错误“当小部件树被锁定时调用 setState() 或 markNeedsBuild()”。
  2. 如果我从 _getCallCardDoctorListByTerritory 方法中删除此行
    emit(currentState.copyWith(isDoctorLoading: true));
    ,则会加载医生列表,但我选择的区域会自动重置。
  3. 如果有任何 UI 或 bloc 的优化提示请告诉我,或者我的 bloc 太长,这种方法是否不正确?

代码太多,请见谅。我认为所有这些都应该放在这里以便更好地理解。

这是我的自定义选项卡小部件

class GlobalCustomTab extends StatelessWidget {
  const GlobalCustomTab({
    super.key,
    required this.title,
    required this.tabs,
    required this.tabViews,
    required this.onTabChangeActions,
  });

  final String title;
  final List<Widget> tabs;
  final List<Widget> tabViews;
  final List<void Function(BuildContext)> onTabChangeActions;

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: tabs.length,
      child: Builder(builder: (context) {
        final TabController tabController = DefaultTabController.of(context);
        tabController.addListener(() {
          if (!tabController.indexIsChanging) {
            _onTabChange(context, tabController.index, onTabChangeActions);
          }
        });
        return Scaffold(
          appBar: CustomAppbar(title: title),
          body: Column(
            children: [
              TabBar(
                unselectedLabelColor: AppColors.secondaryElementText,
                labelStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
                labelPadding: const EdgeInsets.symmetric(vertical: 16),
                indicatorSize: TabBarIndicatorSize.tab,
                dividerColor: Colors.transparent,
                onTap: (index) {
                  _onTabChange(context, index, onTabChangeActions);
                },
                tabs: tabs,
              ),
              Expanded(
                child: TabBarView(children: tabViews),
              ),
            ],
          ),
        );
      }),
    );
  }

  void _onTabChange(BuildContext context, int index, List<void Function(BuildContext)> onTabChangeActions) {
    if (index < onTabChangeActions.length) {
      onTabChangeActions[index](context);
    }
  }
}

这是我使用该小部件的主页

class DailyCallCardPage extends StatelessWidget {
  const DailyCallCardPage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (_) => DoctorDailyCallCardBloc()..add(CallCardDoctorListEvent()),
        ),
        BlocProvider(
          create: (_) => ChemistDailyCallCardBloc()..add(CallCardChemistListEvent()),
        ),
      ],
      child: GlobalCustomTab(
        title: title,
        tabs: const [
          Text('DOCTOR'),
          Text('CHEMIST'),
        ],
        tabViews: [
          const DoctorDailyCallCardPage(),
          const ChemistDailyCallCardPage(),
        ],
        onTabChangeActions: [
          (context) => context
              .read<DoctorDailyCallCardBloc>()
              .add(CallCardDoctorListEvent()),
          (context) => context
              .read<ChemistDailyCallCardBloc>()
              .add(CallCardChemistListEvent()),
        ],
      ),
    );
  }
}

这是医生卡小部件,它是第一个选项卡视图

class DoctorDailyCallCardPage extends StatelessWidget {
  const DoctorDailyCallCardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
      builder: (context, state) {
        if (state is DailyCallCardLoading) {
          return const Center(child: LoadingWidget());
        } else if (state is DailyCallCardError) {
          return Center(child: CustomErrorWidget(message: state.message));
        } else if (state is DailyCallCardLoaded) {
          return SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: kHorizontal, vertical: elementPadding),
            child: Column(
              children: [
                CreateCallCardWidget(),
                _buildDivider(context),
                if (state.isLoadingList!)
                  const Center(child: LoadingWidget())
                else
                  CallCardListWidget(callCardList: state.doctorPlanList),
              ],
            ),
          );
        }
        return const SizedBox.shrink();
      },
    );
  }

  Widget _buildDivider(BuildContext context) {
    return CustomDivider(
      title: 'Call Card',
      widget: Flexible(
        child: Row(
          children: [
            Image.asset(AppIcons.dateIcon),
            Expanded(
              child: CustomDatePicker(
                onChanged: (value) {
                  context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorPlanListEvent(visitDate: value));
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

这是我创建的电话卡小部件

class CreateCallCardWidget extends StatelessWidget {
  CreateCallCardWidget({super.key});

  final callCardKey = GlobalKey<FormState>();

  final DailyCallCardReqParams params = DailyCallCardReqParams(
      visitDate: DateFormat('yyyy-MM-dd', 'en').format(DateTime.now()),
      visitTime: DateFormat('hh:mm a', 'en').format(DateTime.now()));

  @override
  Widget build(BuildContext context) {
    return Container(
      color: AppColors.secondaryElement,
      padding: const EdgeInsets.symmetric(
          horizontal: kHorizontal, vertical: elementPadding),
      margin: const EdgeInsets.symmetric(vertical: 12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          customTitle(title: 'Create Call Card', fontSize: 16.sp),
          const Gap(18),
          Form(
            key: callCardKey,
            child: Column(
              children: [
                CustomDateTimeRow(
                  onDateChanged: (value) {
                    params.visitDate = value;
                  },
                  onTimeChanged: (value) {
                    params.visitTime = value;
                  },
                ),
                BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
                  builder: (context, state) {
                    if (state is DailyCallCardLoaded) {
                      return Column(
                        children: [
                          Visibility(
                            visible: state.showTerritory ?? false,
                            child: CustomDropdownSearch(
                              labelText: 'Select Territory',
                              items: (f, s) => state.territoryList,
                              itemAsString: (Territory data) => '${data.territoryName}\n${data.upperTerritoryCode}',
                              onChanged: (value) {
                                context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorListByTerritoryEvent(territoryCode: value!.upperTerritoryCode));
                              },
                            ),
                          ),
                          state.isDoctorLoading!
                              ? const Center(child: LoadingWidget())
                              : CustomDropdownSearch(
                                  labelText: 'Select Doctor',
                                  items: (f, s) => state.doctorList,
                                  itemAsString: (Doctor data) => data.name!,
                                  onChanged: (value) {
                                    params.doctorID = value!.doctorId;
                                  },
                                ),
                          CustomTextFormField(
                            hintText: 'Write a comment here....',
                            maxLines: 2,
                            onChanged: (value) {
                              params.opinion = value;
                            },
                          ),
                          state.isSettingPlan!
                              ? const ThreeDotLoading()
                              : CustomButton(
                                  title: 'Set Plan',
                                  onPressed: () {
                                    if (callCardKey.currentState!.validate()) {
                                      context
                                          .read<DoctorDailyCallCardBloc>()
                                          .add(SetCallCard(params: params));
                                    }
                                  },),
                        ],
                      );
                    }
                    return const SizedBox.shrink();
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class CustomDateTimeRow extends StatelessWidget {
  const CustomDateTimeRow({
    super.key,
    this.onDateChanged,
    this.onTimeChanged,
  });

  final Function(String?)? onDateChanged;
  final Function(String?)? onTimeChanged;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: CustomContainer(
            height: 45.r,
            child: Row(
              children: [
                Image.asset(AppIcons.dateIcon),
                Expanded(
                  child: CustomDatePicker(
                    onChanged: onDateChanged,
                  ),
                ),
              ],
            ),
          ),
        ),
        const Gap(12),
        Expanded(
          flex: 1,
          child: CustomContainer(
            height: 45.r,
            child: Row(
              children: [
                Image.asset(AppIcons.timeIcon),
                Expanded(
                  child: CustomTimePicker(
                    onChanged: onTimeChanged,
                  ),
                ),
              ],
            ),
          ),
        )
      ],
    );
  }
}

这是我的集体课

class DoctorDailyCallCardBloc extends Bloc<DoctorDailyCallCardEvent, DoctorDailyCallCardState> {
  String visitDate = '';
  final String currentDate = DateTime.now().toString().split(' ')[0];

  DoctorDailyCallCardBloc() : super(DailyCallCardInitial()) {
    on<CallCardDoctorListEvent>(_getCallCardDoctorList);
    on<CallCardDoctorListByTerritoryEvent>(_getCallCardDoctorListByTerritory);
    on<SetCallCard>(_setDailyCallCard);
    on<CallCardDoctorPlanListEvent>(_setDailyCallCardLoaded);
  }

  Future<void> _getCallCardDoctorList(CallCardDoctorListEvent event, Emitter<DoctorDailyCallCardState> emit) async {
    emit(DailyCallCardLoading());

    visitDate = event.visitDate ?? currentDate;
    final String role = await GlobalStorage.getRole() ?? '';
    List<Territory> territoryList = [];
    List<Doctor> doctorList = [];
    List<Doctor> doctorPlanList = [];

    try {
      // Initialize futures based on role
      List<Future<Either>> futures = [
        sl<GetDoctorPlanUseCase>().call(param: {'visitDate': visitDate}),
      ];

      if (role.contains('T')) {
        futures.add(sl<GetDoctorUseCase>().call());
      } else {
        futures.add(sl<AllTerritoryUseCase>().call());
      }

      // Execute all required use cases concurrently
      final results = await Future.wait(futures);

      // Extract results
      final Either doctorPlanResult = results[0];
      final Either doctorResult = results[1];
      final Either? territoryResult = !role.contains('T') ? results[1] : null;

      // Handle doctorResult
      await doctorResult.fold((error) async {
        emit(DailyCallCardError(message: error));
        return;
      }, (doctorResponse) async {
        final DoctorEntity doctorData = DoctorEntity.fromJson(doctorResponse);
        if (doctorData.status!) {
          doctorList = doctorData.dataList ?? [];
        }
      });

      // Handle doctorPlanResult
      await doctorPlanResult.fold((error) async {
        emit(DailyCallCardError(message: error));
        return;
      }, (doctorPlanResponse) async {
        final DoctorEntity doctorPlanData = DoctorEntity.fromJson(doctorPlanResponse);
        if (doctorPlanData.status!) {
          doctorPlanList = doctorPlanData.dataList!;
        }
      });

      // Handle territoryResult if applicable
      if (territoryResult != null) {
        await territoryResult.fold((error) async {
          emit(DailyCallCardError(message: error));
        }, (territoryResponse) async {
          final AllTerritoryEntity territoryData = AllTerritoryEntity.fromJson(territoryResponse);
          if (territoryData.status!) {
            territoryList = territoryData.territoryList ?? [];
          }
        });
      }

      // Emit loaded state
      emit(DailyCallCardLoaded(
        doctorList: doctorList,
        doctorPlanList: doctorPlanList,
        territoryList: territoryList,
        showTerritory: role.contains('T') ? false : true,
        isDoctorLoading: false,
        isSettingPlan: false,
        isLoadingList: false,
      ));
    } catch (e, stacktrace) {
      emit(DailyCallCardError(message: e.toString()));
      debugPrint('Error from $runtimeType: ${e.toString()}');
      debugPrint('Stacktrace from $runtimeType: $stacktrace');
    }
  }

  Future<void> _getCallCardDoctorListByTerritory(CallCardDoctorListByTerritoryEvent event, Emitter<DoctorDailyCallCardState> emit) async {
    final currentState = state;
    if (currentState is DailyCallCardLoaded) {
      emit(currentState.copyWith(isDoctorLoading: true));

      try {
        final Either result = await sl<GetDoctorByTerritoryUseCase>().call(param: {'code': event.territoryCode});

        // Initialize state data
        List<Doctor> doctorList = [];

        await result.fold((error) async {
          emit(DailyCallCardError(message: error));
          return;
        }, (doctorByTerritoryResponse) async {
          final DoctorEntity doctorData = DoctorEntity.fromJson(doctorByTerritoryResponse);
          if (doctorData.status!) {
            doctorList = doctorData.dataList!;
          }
        });

        emit(currentState.copyWith(showTerritory: true, isDoctorLoading: false, doctorList: doctorList));
      } catch (e, stacktrace) {
        emit(DailyCallCardError(message: e.toString()));
        debugPrint('Error from $runtimeType: ${e.toString()}');
        debugPrint('Stacktrace from $runtimeType: $stacktrace');
      }
    }
  }

  Future<void> _setDailyCallCard(SetCallCard event, Emitter<DoctorDailyCallCardState> emit) async {
    final currentState = state;
    if (currentState is DailyCallCardLoaded) {
      emit(currentState.copyWith(isSettingPlan: true));

      Either result = await sl<SetDoctorPlanUseCase>().call(param: event.params);
      await result.fold((error) async {
        emit(DailyCallCardError(message: error));
      }, (response) async {
        final bool status = response['status'];
        final String message = response['message'];
        if (status) {
          Utils.showToast(message);
          emit(currentState.copyWith(isSettingPlan: false));
          add(CallCardDoctorPlanListEvent(visitDate: visitDate));
        }
      });
    }
  }

  Future<void> _setDailyCallCardLoaded(CallCardDoctorPlanListEvent event, Emitter<DoctorDailyCallCardState> emit) async {
    final currentState = state;
    if (currentState is DailyCallCardLoaded) {
      emit(currentState.copyWith(isLoadingList: true));
      visitDate = event.visitDate ?? currentDate;
      try {
        final Either result = await sl<GetDoctorPlanUseCase>().call(param: {'visitDate': visitDate});

        // Initialize state data
        List<Doctor> doctorPlanList = [];

        await result.fold((error) async {
          emit(DailyCallCardError(message: error));
          return;
        }, (doctorPlanResponse) async {
          final DoctorEntity doctorData = DoctorEntity.fromJson(doctorPlanResponse);
          if (doctorData.status!) {
            doctorPlanList = doctorData.dataList!;
          }
        });

        emit(currentState.copyWith(doctorPlanList: doctorPlanList, isLoadingList: false));
      } catch (e, stacktrace) {
        emit(DailyCallCardError(message: e.toString()));
        debugPrint('Error from $runtimeType: ${e.toString()}');
        debugPrint('Stacktrace from $runtimeType: $stacktrace');
      }
    }
  }
}

这是国家级

@immutable
sealed class DoctorDailyCallCardState extends Equatable {
  @override
  List<Object?> get props => [];
}

final class DailyCallCardInitial extends DoctorDailyCallCardState {}

final class DailyCallCardLoading extends DoctorDailyCallCardState {}

final class DailyCallCardByDoctorLoading extends DoctorDailyCallCardState {}

final class DailyCallCardLoaded extends DoctorDailyCallCardState {
  final List<Doctor> doctorList;
  final List<Doctor> doctorPlanList;
  final List<Territory> territoryList;
  final bool? isDoctorLoading;
  final bool? isSettingPlan;
  final bool? isLoadingList;
  final bool? showTerritory;

  DailyCallCardLoaded({
    required this.doctorList,
    required this.doctorPlanList,
    required this.territoryList,
    this.isDoctorLoading,
    this.isSettingPlan = false,
    this.isLoadingList = false,
    this.showTerritory,
  });

  DailyCallCardLoaded copyWith({
    List<Doctor>? doctorList,
    List<Doctor>? doctorPlanList,
    List<Territory>? territoryList,
    bool? isDoctorLoading,
    bool? isSettingPlan,
    bool? isLoadingList,
    bool? showTerritory,
  }) {
    return DailyCallCardLoaded(
      doctorList: doctorList ?? this.doctorList,
      doctorPlanList: doctorPlanList ?? this.doctorPlanList,
      territoryList: territoryList ?? this.territoryList,
      isDoctorLoading: isDoctorLoading ?? this.isDoctorLoading,
      isSettingPlan: isSettingPlan ?? this.isSettingPlan,
      isLoadingList: isLoadingList ?? this.isLoadingList,
      showTerritory: showTerritory ?? this.showTerritory,
    );
  }

  @override
  List<Object?> get props => [
        doctorList,
        doctorPlanList,
        territoryList,
        isDoctorLoading,
        isSettingPlan,
        isLoadingList,
        showTerritory,
      ];
}

final class DailyCallCardError extends DoctorDailyCallCardState {
  final String message;

  DailyCallCardError({
    required this.message,
  });

  @override
  List<Object> get props => [message];
}

这就是活动

@immutable
sealed class DoctorDailyCallCardEvent extends Equatable {
  @override
  List<Object?> get props => [];
}

class CallCardDoctorListEvent extends DoctorDailyCallCardEvent {
  final String? visitDate;

  CallCardDoctorListEvent({
    this.visitDate,
  });

  @override
  List<Object?> get props => [visitDate];
}

class CallCardDoctorListByTerritoryEvent extends DoctorDailyCallCardEvent {
  final String? territoryCode;

  CallCardDoctorListByTerritoryEvent({
    this.territoryCode,
  });

  @override
  List<Object?> get props => [territoryCode];
}

class SetCallCard extends DoctorDailyCallCardEvent {
  final DailyCallCardReqParams params;

  SetCallCard({
    required this.params,
  });

  @override
  List<Object> get props => [params];
}

class CallCardDoctorPlanListEvent extends DoctorDailyCallCardEvent {
  final String? visitDate;

  CallCardDoctorPlanListEvent({
    this.visitDate,
  });

  @override
  List<Object?> get props => [visitDate];
}
flutter state bloc
1个回答
0
投票

将我的

CreateCallCardWidget
StatelessWidget
更改为
StatefulWidget
并更新
onChanged
CustomDropDownWidget
功能后,我的问题就解决了。

class CreateCallCardWidget extends StatefulWidget {
  const CreateCallCardWidget({super.key});

  @override
  State<CreateCallCardWidget> createState() => _CreateCallCardWidgetState();
}

class _CreateCallCardWidgetState extends State<CreateCallCardWidget> {
  final callCardKey = GlobalKey<FormState>();
  late DailyCallCardReqParams params;
  Territory? selectedTerritory;

  @override
  void initState() {
    super.initState();
    params = DailyCallCardReqParams(
      visitDate: DateFormat('yyyy-MM-dd', 'en').format(DateTime.now()),
      visitTime: DateFormat('hh:mm a', 'en').format(DateTime.now()),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: AppColors.secondaryElement,
      padding: const EdgeInsets.symmetric(
          horizontal: kHorizontal, vertical: elementPadding),
      margin: const EdgeInsets.symmetric(vertical: 12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          customTitle(title: 'Create Call Card', fontSize: 16.sp),
          const Gap(18),
          Form(
            key: callCardKey,
            child: Column(
              children: [
                CustomDateTimeRow(
                  onDateChanged: (value) {
                    params.visitDate = value;
                  },
                  onTimeChanged: (value) {
                    params.visitTime = value;
                  },
                ),
                BlocBuilder<DoctorDailyCallCardBloc, DoctorDailyCallCardState>(
                  builder: (context, state) {
                    if (state is DailyCallCardLoaded) {
                      return Column(
                        children: [
                          Visibility(
                            visible: state.showTerritory ?? false,
                            child: CustomDropdownSearch(
                              labelText: 'Select Territory',
                              items: (f, s) => state.territoryList,
                              itemAsString: (Territory data) => '${data.territoryName}\n${data.upperTerritoryCode}',
                              onChanged: (value) {
                                setState(() {
                                  selectedTerritory = value;
                                });
                                context.read<DoctorDailyCallCardBloc>().add(CallCardDoctorListByTerritoryEvent(territoryCode: value!.upperTerritoryCode));
                              },
                            ),
                          ),
                          state.isDoctorLoading!
                              ? const Center(child: ThreeDotLoading())
                              : CustomDropdownSearch(
                                  labelText: 'Select Doctor',
                                  items: (f, s) => state.doctorList,
                                  itemAsString: (Doctor data) => data.name!,
                                  onChanged: (value) {
                                    params.doctorID = value!.doctorId;
                                  },
                                ),
                          CustomTextFormField(
                            hintText: 'Write a comment here....',
                            maxLines: 2,
                            onChanged: (value) {
                              params.opinion = value;
                            },
                          ),
                          state.isSettingPlan!
                              ? const ThreeDotLoading()
                              : CustomButton(
                                  title: 'Set Plan',
                                  onPressed: () {
                                    if (callCardKey.currentState!.validate()) {
                                      context
                                          .read<DoctorDailyCallCardBloc>()
                                          .add(SetCallCard(params: params));
                                    }
                                  },
                                ),
                        ],
                      );
                    }
                    return const SizedBox.shrink();
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.