Flutter Riverpod StateNotifier load data on page build

Viewed 2183

I am developing a new application and testing Riverpod with state notifier and have a question about where I can load my initial data when I'm building a page.

I have following state class:

abstract class SalesOrderListState extends Equatable {
  const SalesOrderListState();

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

class SalesOrderListInitial extends SalesOrderListState {}

class SalesOrderListLoading extends SalesOrderListState {}

class SalesOrderListSuccess extends SalesOrderListState {
  final List<SalesOrderListItem> salesOrderListItems;

  SalesOrderListSuccess({
    @required this.salesOrderListItems,
  });

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

class SalesOrderListError extends SalesOrderListState {
  final String error;

  const SalesOrderListError({@required this.error});

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

and this state_notifier class:

final salesOrderListStateNotifierProvider =
    StateNotifierProvider<SalesOrderListStateNotifier>(
        (ref) => SalesOrderListStateNotifier(ref.read));

class SalesOrderListStateNotifier extends StateNotifier<SalesOrderListState> {
  final Reader read;

  SalesOrderListStateNotifier(this.read) : super(SalesOrderListInitial());

  Future<void> getSalesOrders() async {
    final SalesOrderListUseCase _salesOrderListUseCase =
        read(salesOrderListUseCaseProvider);

    state = SalesOrderListLoading();
    final result = await _salesOrderListUseCase.getSalesOrders();
    result.fold(
      (failure) =>
          state = SalesOrderListError(error: mapFailureToMessage(failure)),
      (salesOrderListItems) async {
          state = SalesOrderListSuccess(salesOrderListItems: salesOrderListItems);
      },
    );
  }
}

Now my widged class (page) use the state notifier like this:

 ...

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(),
      ),
      body: Consumer(
        builder: (context, watch, child) {
          final state = watch(salesOrderListStateNotifier.state);
          switch (state.runtimeType) {
            case SalesOrderListSuccess:
              final _salesOrders = (state as SalesOrderListSuccess).salesOrderListItems;
              ...
                 print sales orders
              ...
              );
              break;
            case SalesOrderListError:
              return MessageDisplayWidget(
                message: (state as SalesOrderListError).error,
              );
              break;
            default:
              return LoadingWidget();
          }
        },
      ),
    );
  }

but before the page loads, I have to call

context
.read(salesOrderListStateNotifierProvider)
.getSalesOrders()

What is the best approach to do it?

I have already tried to use a statefull widget and override initstate like this

void initState() {
    super.initState();
    Future.delayed(Duration.zero).then(
      (_) => context
          .read(salesOrderListStateNotifierProvider)
          .getSalesOrders(),
    );
    // WidgetsBinding.instance.addPostFrameCallback(
    //   (_) => context
    //       .read(salesOrderListStateNotifierProvider)
    //       .getSalesOrders(),
    // );
  }

This works but I'm not sure if this is the best approach.

I have a lot of pages where I have to load data before painting it and I'm not sure if converting all this pages to statefull widgets to override initstate is the best solution.

2 Answers

On github @iamarnas gave me the solution, call the fetch method inside the statenotifier constructor

class UserNotifier extends StateNotifier<User>{
  UserNotifier() : super(User()){
    fetchData(); // It's same as initState();
  } 
}

I had the same issue calling api on init..Use WidgetBinding in initState and it will fix the issue.

WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
 context.read(salesOrderListStateNotifierProvider).getSalesOrders()
});
Related