Search code examples
flutterdartflutter-provider

Could not find the correct Provider<X> above this ModalBottomSheet Widget


I'm new to flutter and I'm trying to understand How to use provider state management in an application which users Moor to save some data into sqlite table. My application is a task recording application. I'm getting the above error in my widget tree when I open my bottom sheet add a task. I'm using provider: ^4.3.1

class TaskView extends StatelessWidget {
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Provider<TaskViewModel>(
        create: (_) => TaskViewModel(),
        child: Scaffold(
            appBar: AppBar(
              title: Text('Tasks'),
            ),
            body: Text("Temporary body!"),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                showMaterialModalBottomSheet(
                  context: context,
                  builder: (context, scrollController) => Container(
                    child: bottomSheet(context),
                  ),
                );
              },
              child: Icon(
                Icons.add,
                color: Colors.white,
              ),
              backgroundColor: Colors.blueAccent,
            )
        )
    );
  }

  Widget bottomSheet(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(
          left: 16.0,
          top: 16.0,
          right: 16.0,
          bottom: 16.0 + MediaQuery.of(context).viewInsets.bottom),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          TextField(
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Task',
            ),
          ),
          SizedBox(height: 8),
          TextField(
            readOnly: true,
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              suffixIcon: IconButton(
                icon: Icon(Icons.date_range, color: Colors.grey),
                onPressed: () => _selectDate(context),
              ),
              labelText: 'Date',
            ),
          ),
          SizedBox(height: 8),
          Align(
              alignment: Alignment.topRight,
              child: context.watch<TaskViewModel>().state == ViewState.IDLE
                  ? FlatButton(
                      child: Text("Save"),
                      color: Colors.blueAccent,
                      textColor: Colors.white,
                      onPressed: () => _onClickInsertTask(context))
                  : _loadingButtonChild(context))
        ],
      ),
    );
  }

  Widget _loadingButtonChild(BuildContext context) {
    return Container(
      height: 20,
      width: 20,
      margin: EdgeInsets.all(5),
      child: CircularProgressIndicator(
          strokeWidth: 2,
          valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
    );
  }

  /// This function is responsible for displaying the date picker when user click
  /// on task due date inputFiled
  Future<Null> _selectDate(BuildContext context) async {
    final DateTime picked = await showDatePicker(
        context: context,
        initialDate: selectedDate,
        firstDate: DateTime(2015, 8),
        lastDate: DateTime(2101));

    if (picked != null && picked != selectedDate) {
      print("Date selected ${selectedDate.toString()}");
    }
  }

  /// This function is responsible for triggering insert task block event
  void _onClickInsertTask(BuildContext context) {

    var insertTask = TaskData(task: "task", dueDate: selectedDate);
    context.read<TaskViewModel>().insertTask(insertTask);
  }
}

The error suggested checking.

- The provider you are trying to read is in a different route.

I have not given the provider to s route but as the direct parent view.

- You used a BuildContext that is an ancestor of the provider you are trying to read.

I didn't understand what it means but I made the suggested fix in the error. like below

@override
  Widget build(BuildContext context) {
    return Provider<TaskViewModel>(
        create: (_) => TaskViewModel(),
        builder: (context, child) => Scaffold(
            appBar: AppBar(
              title: Text('Tasks'),
            ),
            body: Text("Temporary body!"),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                showMaterialModalBottomSheet(
                  context: context,
                  builder: (context, scrollController) => Container(
                    child: bottomSheet(context),
                  ),
                );
              },
              child: Icon(
                Icons.add,
                color: Colors.white,
              ),
              backgroundColor: Colors.blueAccent,
            )));
  }

Still get the same error. Another thing to note here is error suggested the below.

Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // we use `builder` to obtain a new `BuildContext` that has access to the provider
      builder: (context) {
        // No longer throws
        return Text(context.watch<Example>()),
      }
    ),
  }

But I could not find a builder: (context) so I used the builder: (context, child). Please let me know what I should change to get this working. Thanks.

Edit:

BaseModel

class BaseViewModel extends ChangeNotifier {

  ViewState _state = ViewState.IDLE;

  ViewState get state => _state;

  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }
}

TaskViewModel

class TaskViewModel extends BaseViewModel{

  final TaskRepository _repository = TaskRepository();

  Resource<int> insertTaskStatus;

  Future<void> insertTask(TaskData task) async {

    setState(ViewState.PROCESSING);
    var tasksCompanion = TasksCompanion(task: Value(task.task),dueDate: Value(task.dueDate));
    insertTaskStatus  = await  _repository.insertTask(tasksCompanion);
    setState(ViewState.COMPLETED);

  }

}

Solution

  • Although you call showMaterialModalBottomSheet in the Scaffold wrapped by the provider, the provider is not above both TaskView's Scaffold and the modalBottomSheet. Why?

    The provider you are trying to read is in a different route.

    So, it seems that the modalBottomSheet is on a different route that doesn't have a provider above. If you take a look at the implementation of showModalBottomSheet you'll see:

    return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(....);

    Clearly, it's a new route. So, to access the provider it should be above both routes. Since, the modalBottomSheet route is managed by the MaterialApp, you have to place the provider above the MaterialApp.

    Provider uses lazy loading by default. So, objects are created when they are required and not on app start. However, if you don't want this behavior you can set lazy: false individually. For more info check the offical docs.