3

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.

Alex Bibiano
  • 633
  • 1
  • 6
  • 19

2 Answers2

8

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();
  } 
}
Alex Bibiano
  • 633
  • 1
  • 6
  • 19
  • Hi Alex, I'm having the same question and wondering the solution. In your case, did you call the method inside the constructor like this: SalesOrderListStateNotifier(this.read) : super(SalesOrderListInitial() { getSalesOrder() }); Thanks – Jimmy Apr 10 '21 at 03:37
  • Yes, I just call the method inside the constructor – Alex Bibiano May 14 '21 at 13:33
  • Is this still the right way to do this? I get a syntax error: `Expected a class member. Try placing this code inside a class member.`. – Clifton Labrum Aug 26 '22 at 00:25
  • This has one issue. If in some scenario your provider is being used somewhere else then it won't be disposed and the constructor of your StateNotifier will not be called again. In this case if you pop and push your view it won't refresh the states and data as the constructor is not called. – Gagan Yadav Feb 23 '23 at 10:52
2

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()
});