基于Flutter MVVM+Riverpod:合并多Notifier为单一类的实现问询
实现方案:合并Riverpod Notifier为单一主类(贴合MVVM)
1. 定义统一的主状态类
将分散的日期加载状态、时段加载状态、用户选择信息全部封装到一个主状态类中,保留原有子状态的逻辑结构:
// 原日期状态类(保留原有逻辑) enum BookDatesStatus { initial, loading, loaded, error } class BookDatesState { final BookDatesStatus status; final List<DateTime> availableDates; final String? errorMessage; BookDatesState({ required this.status, required this.availableDates, this.errorMessage, }); BookDatesState copyWith({ BookDatesStatus? status, List<DateTime>? availableDates, String? errorMessage, }) { return BookDatesState( status: status ?? this.status, availableDates: availableDates ?? this.availableDates, errorMessage: errorMessage ?? this.errorMessage, ); } } // 原时段状态类(保留原有逻辑) enum SlotsStatus { initial, loading, loaded, error } class SlotsState { final SlotsStatus status; final List<String> availableSlots; final String? errorMessage; SlotsState({ required this.status, required this.availableSlots, this.errorMessage, }); SlotsState copyWith({ SlotsStatus? status, List<String>? availableSlots, String? errorMessage, }) { return SlotsState( status: status ?? this.status, availableSlots: availableSlots ?? this.availableSlots, errorMessage: errorMessage ?? this.errorMessage, ); } } // 统一主状态类 class AppointmentState { final BookDatesState datesState; final SlotsState slotsState; final DateTime? chosenDate; final String? chosenTime; AppointmentState({ required this.datesState, required this.slotsState, this.chosenDate, this.chosenTime, }); AppointmentState copyWith({ BookDatesState? datesState, SlotsState? slotsState, DateTime? chosenDate, String? chosenTime, }) { return AppointmentState( datesState: datesState ?? this.datesState, slotsState: slotsState ?? this.slotsState, chosenDate: chosenDate ?? this.chosenDate, chosenTime: chosenTime ?? this.chosenTime, ); } // 初始状态 static AppointmentState initial() { return AppointmentState( datesState: BookDatesState( status: BookDatesStatus.initial, availableDates: [], ), slotsState: SlotsState( status: SlotsStatus.initial, availableSlots: [], ), chosenDate: null, chosenTime: null, ); } }
2. 实现单一主Notifier类
将原多个Provider/Notifier的逻辑整合到主Notifier中,统一处理数据加载、用户交互逻辑:
final appointmentNotifierProvider = NotifierProvider<AppointmentNotifier, AppointmentState>( AppointmentNotifier.new, ); class AppointmentNotifier extends Notifier<AppointmentState> { // 替换为你的实际数据源(API/本地存储) final _dateDataSource = DateDataSource(); final _slotDataSource = SlotDataSource(); @override AppointmentState build() { // 初始化时自动加载可预约日期 _loadAvailableDates(); return AppointmentState.initial(); } // 加载可预约日期(对应原futureBookingDates逻辑) Future<void> _loadAvailableDates() async { state = state.copyWith( datesState: state.datesState.copyWith(status: BookDatesStatus.loading), ); try { final dates = await _dateDataSource.fetchAvailableDates(); state = state.copyWith( datesState: state.datesState.copyWith( status: BookDatesStatus.loaded, availableDates: dates, ), ); } catch (e) { state = state.copyWith( datesState: state.datesState.copyWith( status: BookDatesStatus.error, errorMessage: e.toString(), ), ); } } // 用户选择日期,同时加载对应时段(整合原ChosenDate + AvailableTimeSlots逻辑) Future<void> selectDate(DateTime date) async { state = state.copyWith( chosenDate: date, chosenTime: null, // 切换日期清空已选时段 slotsState: state.slotsState.copyWith(status: SlotsStatus.loading), ); try { final slots = await _slotDataSource.fetchSlotsForDate(date); state = state.copyWith( slotsState: state.slotsState.copyWith( status: SlotsStatus.loaded, availableSlots: slots, ), ); } catch (e) { state = state.copyWith( slotsState: state.slotsState.copyWith( status: SlotsStatus.error, errorMessage: e.toString(), ), ); } } // 用户选择时段(对应原ChosenTime逻辑) void selectTime(String time) { state = state.copyWith(chosenTime: time); } // 重置状态(可选) void resetAppointment() { state = AppointmentState.initial(); _loadAvailableDates(); } } // 模拟数据源,替换为你的业务实现 class DateDataSource { Future<List<DateTime>> fetchAvailableDates() async { await Future.delayed(const Duration(seconds: 1)); return [ DateTime.now(), DateTime.now().add(const Duration(days: 1)), DateTime.now().add(const Duration(days: 2)), ]; } } class SlotDataSource { Future<List<String>> fetchSlotsForDate(DateTime date) async { await Future.delayed(const Duration(seconds: 1)); return date.day == DateTime.now().day ? ["09:00", "10:00", "14:00"] : ["11:00", "13:00", "15:00"]; } }
3. UI层精准刷新组件
通过select方法监听状态的局部变化,实现“选择日期仅刷新时段视图”的需求,避免不必要的重绘:
class AppointmentPage extends ConsumerWidget { const AppointmentPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar(title: const Text("预约服务")), body: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 日期选择区:仅监听datesState和chosenDate _DateSelector( state: ref.watch(appointmentNotifierProvider.select((s) => s.datesState)), chosenDate: ref.watch(appointmentNotifierProvider.select((s) => s.chosenDate)), onSelect: (date) => ref.read(appointmentNotifierProvider.notifier).selectDate(date), ), const SizedBox(height: 20), // 时段选择区:仅监听slotsState和chosenTime _TimeSlotSelector( state: ref.watch(appointmentNotifierProvider.select((s) => s.slotsState)), chosenTime: ref.watch(appointmentNotifierProvider.select((s) => s.chosenTime)), onSelect: (time) => ref.read(appointmentNotifierProvider.notifier).selectTime(time), ), ], ), ), ); } } class _DateSelector extends StatelessWidget { final BookDatesState state; final DateTime? chosenDate; final Function(DateTime) onSelect; const _DateSelector({ required this.state, required this.chosenDate, required this.onSelect, }); @override Widget build(BuildContext context) { switch (state.status) { case BookDatesStatus.loading: return const CircularProgressIndicator(); case BookDatesStatus.error: return Text("加载日期失败:${state.errorMessage}"); case BookDatesStatus.loaded: return Wrap( spacing: 8, children: state.availableDates.map((date) { final isSelected = chosenDate?.isAtSameMomentAs(date) ?? false; return ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isSelected ? Colors.blue : null, ), onPressed: () => onSelect(date), child: Text("${date.month}/${date.day}"), ); }).toList(), ); default: return const SizedBox(); } } } class _TimeSlotSelector extends StatelessWidget { final SlotsState state; final String? chosenTime; final Function(String) onSelect; const _TimeSlotSelector({ required this.state, required this.chosenTime, required this.onSelect, }); @override Widget build(BuildContext context) { switch (state.status) { case SlotsStatus.loading: return const CircularProgressIndicator(); case SlotsStatus.error: return Text("加载时段失败:${state.errorMessage}"); case SlotsStatus.loaded: return Wrap( spacing: 8, children: state.availableSlots.map((slot) { final isSelected = chosenTime == slot; return ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isSelected ? Colors.green : null, ), onPressed: () => onSelect(slot), child: Text(slot), ); }).toList(), ); default: return const Text("请先选择预约日期"); } } }
核心优势
- 贴合MVVM:主Notifier作为ViewModel,统一处理业务逻辑与状态管理,UI层仅负责展示和触发事件
- 保留原有流程:通过
select监听局部状态,选择日期时仅刷新时段组件,避免整页重绘 - 状态集中管理:所有预约相关状态统一维护,避免分散状态导致的逻辑混乱
- 扩展性强:后续新增预约字段(如备注、用户信息),只需在主状态类中添加字段即可
内容的提问来源于stack exchange,提问作者Mervin Hemaraju
相关产品推荐
相关产品推荐

