最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Flutter MVVM with Riverpod - Stack Overflow

programmeradmin2浏览0评论

I have an app where on a view I allow users to perform a booking.

The page first load and I use a Riverpod provider to get the future booking dates:

@riverpod
Future<BookDatesState> futureBookingDates(Ref ref) async ...

Then I defined a set of states:

sealed class BookDatesState {
  const BookDatesState();
}

class BookDatesLoadingState extends BookDatesState {
  const BookDatesLoadingState();
}

class BookDatesErrorState extends BookDatesState {
  final String message;
  const BookDatesErrorState({required this.message});
}

class BookDatesNoInternetState extends BookDatesState {
  final String message;
  const BookDatesNoInternetState({required this.message});
}

class BookDatesLoadedState extends BookDatesState {
  final List<DateTime> dates;
  const BookDatesLoadedState({required this.dates});
}

Which I then use in my view to observe and display views:

   final bookDatesUi = switch (bookDatesState) {
      BookDatesLoadingState() => const Center(
          child: Padding(
            padding: EdgeInsets.all(21.0),
            child: LoadingView(),
          ),
        ),
      BookDatesErrorState() => ErrorView(
          message: bookDatesState.message,
          showErrorImage: true,
        ),
      BookDatesNoInternetState() => ErrorView(
          message: bookDatesState.message,
          showNoInternetImage: true,
        ),
      BookDatesLoadedState() => BookingDatesView(
          bookDates: bookDatesState.dates,
          selectedDate: chosenDate,
          onDateSelected: (date) {
            // Reset the time when date is selected
            ref.read(chosenTimeProvider.notifier).set(null);

            // Set the date selected
            ref.read(chosenDateProvider.notifier).set(date);

            // Load the dates
            ref.read(availableTimeSlotsProvider.notifier).load(
                  service.merchantId,
                  date,
                );
          },
        ),
    };

final bookDatesState = ref.watch(futureBookingDatesProvider).when(
          data: (state) => state,
          error: (error, stack) =>
              BookDatesErrorState(message: error.toString()),
          loading: () => const BookDatesLoadingState(),
        );

Now a list of dates is shown on screen. When the user selects a date, I then use a Notifier riverpod class to get the available list of time slots:

@riverpod
class AvailableTimeSlots extends _$AvailableTimeSlots ...

I then make use of another set of states for the slots:

sealed class SlotsState {
  const SlotsState();
}

class SlotsInitialState extends SlotsState {
  const SlotsInitialState();
}

class SlotsLoadingState extends SlotsState {
  const SlotsLoadingState();
}

class SlotsErrorState extends SlotsState {
  final String message;
  const SlotsErrorState({required this.message});
}

class SlotsEmptyState extends SlotsState {
  const SlotsEmptyState();
}

class SlotsNoInternetState extends SlotsState {
  final String message;
  const SlotsNoInternetState({required this.message});
}

class SlotsLoadedState extends SlotsState {
  final DateTime date;
  final List<TimeOfDay> slots;
  const SlotsLoadedState({required this.slots, required this.date});
}

And display the view on my screen:

final slotsState = ref.watch(availableTimeSlotsProvider).when(
      data: (state) => state,
      error: (error, stack) => SlotsErrorState(message: error.toString()),
      loading: () => const SlotsLoadingState(),
    );

// Get the slots ui
final slotsUi = switch (slotsState) {
  SlotsInitialState() => const SlotsViewInitial(),
  SlotsLoadingState() => const Center(
      child: Padding(
        padding: EdgeInsets.all(21.0),
        child: LoadingView(),
      ),
    ),
  SlotsEmptyState() => const SlotsViewEmpty(),
  SlotsErrorState() => ErrorView(
      message: slotsState.message,
      showErrorImage: true,
    ),
  SlotsNoInternetState() => ErrorView(
      message: slotsState.message,
      showNoInternetImage: true,
    ),
  SlotsLoadedState() => SlotsViewLoaded(
      slots: slotsState.slots,
      chosenTime: chosenTime,
      onTimeSelected: (TimeOfDay time) {
        ref.read(chosenTimeProvider.notifier).set(time);
      },
    ),
};

I make use of different views because I don't want the whole screen to reload when the user selects a date, I want just the time slots view to reload.

Now I have other Riverpod providers just for this specific page which is based on the user input:

@riverpod
class ChosenDate extends _$ChosenDate {
  @override
  DateTime? build() => null;

  void set(DateTime? date) {
    state = date;
  }
}

@riverpod
class ChosenTime extends _$ChosenTime {
  @override
  TimeOfDay? build() => null;

  void set(TimeOfDay? time) {
    state = time;
  }
}

@riverpod
class ChosenFromHome extends _$ChosenFromHome {
  @override
  bool build() => false;

  void update(bool selected) {
    state = selected;
  }
}

Instead of having different Riverpod notifiers and providers, I want to have a single main Notifier class and then have different methods in it which follows more the MVVM design structure while still keeping the same flow of my app (when a user selects a date, only the time part should reload and so on).

Does anyone have any idea on how I can achieve this ?

I have an app where on a view I allow users to perform a booking.

The page first load and I use a Riverpod provider to get the future booking dates:

@riverpod
Future<BookDatesState> futureBookingDates(Ref ref) async ...

Then I defined a set of states:

sealed class BookDatesState {
  const BookDatesState();
}

class BookDatesLoadingState extends BookDatesState {
  const BookDatesLoadingState();
}

class BookDatesErrorState extends BookDatesState {
  final String message;
  const BookDatesErrorState({required this.message});
}

class BookDatesNoInternetState extends BookDatesState {
  final String message;
  const BookDatesNoInternetState({required this.message});
}

class BookDatesLoadedState extends BookDatesState {
  final List<DateTime> dates;
  const BookDatesLoadedState({required this.dates});
}

Which I then use in my view to observe and display views:

   final bookDatesUi = switch (bookDatesState) {
      BookDatesLoadingState() => const Center(
          child: Padding(
            padding: EdgeInsets.all(21.0),
            child: LoadingView(),
          ),
        ),
      BookDatesErrorState() => ErrorView(
          message: bookDatesState.message,
          showErrorImage: true,
        ),
      BookDatesNoInternetState() => ErrorView(
          message: bookDatesState.message,
          showNoInternetImage: true,
        ),
      BookDatesLoadedState() => BookingDatesView(
          bookDates: bookDatesState.dates,
          selectedDate: chosenDate,
          onDateSelected: (date) {
            // Reset the time when date is selected
            ref.read(chosenTimeProvider.notifier).set(null);

            // Set the date selected
            ref.read(chosenDateProvider.notifier).set(date);

            // Load the dates
            ref.read(availableTimeSlotsProvider.notifier).load(
                  service.merchantId,
                  date,
                );
          },
        ),
    };

final bookDatesState = ref.watch(futureBookingDatesProvider).when(
          data: (state) => state,
          error: (error, stack) =>
              BookDatesErrorState(message: error.toString()),
          loading: () => const BookDatesLoadingState(),
        );

Now a list of dates is shown on screen. When the user selects a date, I then use a Notifier riverpod class to get the available list of time slots:

@riverpod
class AvailableTimeSlots extends _$AvailableTimeSlots ...

I then make use of another set of states for the slots:

sealed class SlotsState {
  const SlotsState();
}

class SlotsInitialState extends SlotsState {
  const SlotsInitialState();
}

class SlotsLoadingState extends SlotsState {
  const SlotsLoadingState();
}

class SlotsErrorState extends SlotsState {
  final String message;
  const SlotsErrorState({required this.message});
}

class SlotsEmptyState extends SlotsState {
  const SlotsEmptyState();
}

class SlotsNoInternetState extends SlotsState {
  final String message;
  const SlotsNoInternetState({required this.message});
}

class SlotsLoadedState extends SlotsState {
  final DateTime date;
  final List<TimeOfDay> slots;
  const SlotsLoadedState({required this.slots, required this.date});
}

And display the view on my screen:

final slotsState = ref.watch(availableTimeSlotsProvider).when(
      data: (state) => state,
      error: (error, stack) => SlotsErrorState(message: error.toString()),
      loading: () => const SlotsLoadingState(),
    );

// Get the slots ui
final slotsUi = switch (slotsState) {
  SlotsInitialState() => const SlotsViewInitial(),
  SlotsLoadingState() => const Center(
      child: Padding(
        padding: EdgeInsets.all(21.0),
        child: LoadingView(),
      ),
    ),
  SlotsEmptyState() => const SlotsViewEmpty(),
  SlotsErrorState() => ErrorView(
      message: slotsState.message,
      showErrorImage: true,
    ),
  SlotsNoInternetState() => ErrorView(
      message: slotsState.message,
      showNoInternetImage: true,
    ),
  SlotsLoadedState() => SlotsViewLoaded(
      slots: slotsState.slots,
      chosenTime: chosenTime,
      onTimeSelected: (TimeOfDay time) {
        ref.read(chosenTimeProvider.notifier).set(time);
      },
    ),
};

I make use of different views because I don't want the whole screen to reload when the user selects a date, I want just the time slots view to reload.

Now I have other Riverpod providers just for this specific page which is based on the user input:

@riverpod
class ChosenDate extends _$ChosenDate {
  @override
  DateTime? build() => null;

  void set(DateTime? date) {
    state = date;
  }
}

@riverpod
class ChosenTime extends _$ChosenTime {
  @override
  TimeOfDay? build() => null;

  void set(TimeOfDay? time) {
    state = time;
  }
}

@riverpod
class ChosenFromHome extends _$ChosenFromHome {
  @override
  bool build() => false;

  void update(bool selected) {
    state = selected;
  }
}

Instead of having different Riverpod notifiers and providers, I want to have a single main Notifier class and then have different methods in it which follows more the MVVM design structure while still keeping the same flow of my app (when a user selects a date, only the time part should reload and so on).

Does anyone have any idea on how I can achieve this ?

Share Improve this question asked yesterday Mervin HemarajuMervin Hemaraju 2,1273 gold badges34 silver badges87 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Can you define one common ChosenDate model with DateTime? date, TimeOfDay? time and bool fromHomeselected fields, in which be sure to make the correct hashCode/==. Place this state in the ChosenDateNotifier.

Then, in each particular Consumer widget, use select to listen for only the desired change:

TimeOfDay? time = ref.watch(chosenDateNotifierProvider.select((model) => model.time));

Read more:

  • Optimizing performance | Riverpod
发布评论

评论列表(0)

  1. 暂无评论