maheshgupta024
maheshgupta024

Reputation: 7877

Using provider in fetching data onLoad

I'm from Android development and very new to Flutter development, I have come across ScopedModel and Provider for State Management and I picked Provider for my use case. And my use case is after logging in, dashboard should be rendered with data. I have created a ViewModel that is extending ChangeNotifier, and wanted to use so that I can reuse this data across different widgets. Im not completely sure how to use my ViewModel to load data onload of the widget. Can some help me with a proper design pattern.

abstract class ViewModel extends ChangeNotifier {
}

class EventViewModel extends ViewModel {
  EventDataState _eventDataState = EventDataUnInitialized();

  EventDataState get eventDataState => _eventDataState;

  set eventDataState(EventDataState value) {
    _eventDataState = value;
    notifyListeners();
  }

  gatherEvents() {
    eventDataState = EventDataLoading();
    EventService.create().getEvents().then((data){
      var responseData = json.decode(data.bodyString);
      eventDataState = EventDataLoaded(
          responseData.map((each) => new EventData.fromJson(each)).toList());
    }).catchError((err){
      eventDataState = EventDataLoadFailed("Failed to do something");
    });
  }
}


class DashboardPage extends StatefulWidget {
  static const routename = "/dashboard";

  @override
  _DashboardPageState createState() => _DashboardPageState();
}

    class _DashboardPageState extends State<DashboardPage> {

      @override
      Widget build(BuildContext context) {
        return Consumer<EventViewModel>(
          builder: (context, vm, child) {
            if (vm.eventDataState is EventDataUnInitialized) {
              vm.gatherEvents();
              return Scaffold(body: Container(),);
            }else if (vm.eventDataState is EventDataLoading) {
              return Scaffold(body: Container(child: Center(child: CircularProgressIndicator(),),),);
            }else if (vm.eventDataState is EventDataLoadFailed) {
              return Scaffold(body: Container(child: Center(child: Text("Error Loading Data."),),),);
            }else{
              return Scaffold(body: Container(child: Center(child: Text("Data done loading..."),),),);
            }
          },
        );
      }
    }

Error Im getting is :

I/flutter (30492): ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
I/flutter (30492): The following assertion was thrown while dispatching notifications for EventViewModel:
I/flutter (30492): setState() or markNeedsBuild() called during build.
I/flutter (30492): This ListenableProvider<EventViewModel> widget cannot be marked as needing to build because the
I/flutter (30492): framework is already in the process of building widgets.  A widget can be marked as needing to be
I/flutter (30492): built during the build phase only if one of its ancestors is currently building. This exception is
I/flutter (30492): allowed because the framework builds parent widgets before children, which means a dirty descendant
I/flutter (30492): will always be built. Otherwise, the framework might not visit this widget during this build phase.
I/flutter (30492): The widget on which setState() or markNeedsBuild() was called was:
I/flutter (30492):   ListenableProvider<EventViewModel>
I/flutter (30492): The widget which was currently being built when the offending call was made was:
I/flutter (30492):   Consumer<EventViewModel>

Upvotes: 1

Views: 4787

Answers (3)

isacjunior
isacjunior

Reputation: 492

You can use SchedulerBinding to prevent this exception:

Alternative 1:

initState() {
  super.initState();
  final gatherEvents =
      Provider.of<EventViewModel>(context, listen: false).gatherEvents;
  SchedulerBinding.instance.addPostFrameCallback((_) => gatherEvents());
}

Alternative 2:

initState() {
  super.initState();
  Future.microtask(() =>
    Provider.of<EventViewModel>(context, listen: false).gatherEvents();
  );
}

Upvotes: 8

halface_sun
halface_sun

Reputation: 429

I'm using get_it which provides a service locator so that you can access your view model everywhere after registration.

And try FutureBuilder to build widgets with APIs.

I'm not sure whether the error will be solved or not, maybe you should return your response data in the gatherEvents() method.

and the code:

/// * serviceLocator.dart
import 'package:get_it/get_it.dart';

GetIt serviceLocator = GetIt.instance;

void setupInfoServiceLocator() {
  serviceLocator.registerLazySingleton(() => EventViewModel());
}
/// * main.dart or somewhere else
@override
void initState() {
  setupInfoServiceLocator();
}
class _DashboardPageState extends State<DashboardPage> {

  final EventViewModel _viewModel = serviceLocator<EventViewModel>();

  Future<EventDataState> _gatherEvents;

  @override
  void initState() {
    super.initState();

    _gatherEvents = _viewModel.gatherEvents();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _gatherEvents,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.active:
          case ConnectionState.waiting:
            return Center(
              child: CircularProgressIndicator();
            );
          case ConnectionState.done:
            if (snapshot.hasError) {
              return Text('Error Loading Data.');
            }

            /// * there is at least three ways to access your data
            final EventDataState data = _viewModel.eventDataState;
            /// * or final EventDataState data = snapshot.data; if you return the response data
            /// * or use Consumer<EventViewModel>(builder: (context, vm, child) => vm.eventDataState);

            /// * use your data
            return Text('Data done loading...');
          default:
            return Container();
        }
      },
    );
  }

}

Upvotes: 0

isacjunior
isacjunior

Reputation: 492

You can use in initState life cycle, see example below:

initState() {
  super.initState();
  Provider.of<EventViewModel>(context, listen: false).gatherEvents;
}

Upvotes: -1

Related Questions