I have a page which contains a button. When the user presses the button, a dialog box shows up which contains a bunch of textfields, etc. There is a toggle in that dialog box which changes the GridView
based on what the user toggles.
The set up is:
In my main
class, I have:
void main() {
Bloc.observer = const GlobalApplicationObserver();
ExpenditureRepository expenditureRepository = ExpenditureRepository();
//runApp(MyApp());
runApp(MaterialApp(
home: MultiBlocProvider(
providers: [
// BlocProvider<OnboardingBloc>(
// create: (BuildContext context) => OnboardingBloc()),
BlocProvider<FinanceBloc>(
create: (BuildContext context) => FinanceBloc(expenditureRepository)),
],
child: OnboardingPageWidget(),
)));
}
In my OnboardingPageWidget
(a stateful
widget, I have, I have a button):
Center(
child: IconButton(
style: TextButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white),
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext _) => expenditureForm
.showAddExpenditureDialogBox(context, true),
),
tooltip: 'Click to add an expense!',
icon: const Icon(Icons.add),
))
And in my showAddExpenditureDialogBox
method, it consists of:
class ExpenditureForm {
AlertDialog showAddExpenditureDialogBox(
BuildContext context, isFixedExpenditure) {
final amountTextEditingController = TextEditingController();
final IncomeOrExpenseToggle incomeExpense = IncomeOrExpenseToggle(context);
final List<String> list = <String>['Travel', 'Food', 'Bill'];
final nameOfExpenditureTextEditingController = TextEditingController();
String categoryOfExpenditure = "Travel";
return AlertDialog(
title: const Text('Expenditure'),
content: const Text('Input details about it!'),
actions: <Widget>[
Column(
children: [
Container(child: Center(child: incomeExpense)),
Row(
children: [
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: nameOfExpenditureTextEditingController,
decoration: InputDecoration(hintText: 'Name of expense'),
),
),
),
Expanded(
flex: 1,
child: Container(
width: MediaQuery.of(context).size.width * 0.3,
child: TextField(
controller: amountTextEditingController,
decoration: InputDecoration(hintText: 'Amount - £'),
),
),
),
],
),
//This is practising drop down menus
DropdownMenu<String>(
initialSelection: list.first,
onSelected: (value) => {categoryOfExpenditure = value!},
dropdownMenuEntries:
list.map<DropdownMenuEntry<String>>((String value) {
return DropdownMenuEntry<String>(value: value, label: value);
}).toList(),
),
// SizedBox(
// child: BlocBuilder<FinanceBloc, FinanceState>(
// buildWhen: (previous, current) => current is TypeToggledState,
// builder: (context, state) {
// if (state is TypeToggledState) {
// if (state.income) {
// return SizedBox(
// width: MediaQuery.of(context).size.width * 1,
// height: MediaQuery.of(context).size.height * 0.5,
// child: GridView.count(
// crossAxisCount: 2,
// children: List.generate(100, (index) {
// return Center(
// child: Text(
// 'Item $index',
// style:
// Theme.of(context).textTheme.headlineSmall,
// ),
// );
// }),
// ),
// );
// } else {
// return SizedBox(
// width: MediaQuery.of(context).size.width * 1,
// height: MediaQuery.of(context).size.height * 0.5,
// child: GridView.count(
// crossAxisCount: 2,
// children: List.generate(23, (index) {
// return Center(
// child: Text(
// 'Item $index',
// style:
// Theme.of(context).textTheme.headlineSmall,
// ),
// );
// }),
// ),
// );
// }
// }
// return SizedBox(
// width: MediaQuery.of(context).size.width * 1,
// height: MediaQuery.of(context).size.height * 0.5,
// child: GridView.count(
// crossAxisCount: 2,
// children: List.generate(100, (index) {
// return Center(
// child: Text(
// 'Item $index',
// style: Theme.of(context).textTheme.headlineSmall,
// ),
// );
// }),
// ),
// );
// }),
// ),
TextButton(
onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => saveExpenditure(
context,
amountTextEditingController.text,
categoryOfExpenditure,
nameOfExpenditureTextEditingController.text,
incomeExpense.getToogle(),
isFixedExpenditure),
child: const Text('Save'),
)
],
)
],
);
}
void saveExpenditure(
BuildContext context,
String amountOfExpenditure,
String categoryOfExpenditure,
String nameOfExpenditure,
bool isIncome,
bool isFixed) {
Expenditure expenditure = Expenditure(
nameOfExpenditure: nameOfExpenditure,
amountOfExpenditure: amountOfExpenditure,
categoryOfExpenditure: categoryOfExpenditure,
typeOfExpenditure: isIncome == true ? "Income" : "Expense",
isFixedExpenditure: isFixed);
context.read<FinanceBloc>().add(SaveFinanceEvent(expenditure));
//Closing the popup
Navigator.of(context, rootNavigator: true).pop();
}
}
Now when I uncomment out the bloc of code in the showAddExpenditureDialogBox
method, I get the following:
Error: Could not find the correct Provider<FinanceBloc> above this BlocBuilder<FinanceBloc,
FinanceState> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that BlocBuilder<FinanceBloc, FinanceState> is under your
MultiProvider/Provider<FinanceBloc>.
I don’t see how or why the error is occurring. From what I see, the bloc provider is being created at the entry point of the app, being passed into OnboardingPageWidget
, and then being passed into the ExpenditureForm
class via constructor. So surely I should be able to use the context
form the constructor to use BlocBuilder
? I think it might be stemming from the fact that there is not overrided build
method in my ExpenditureForm
class? The reason why I created an explicit class and not extending a stateful/stateless widget is because this alert dialog box is being used in another place in my app.
2
Answers
You should provide the
BLoc
above theMaterialApp
widget:that’s because
BLoC
s are provided above thehome
screen only, whenever you navigate to another screen the home screen is removed from theMaterialApp
and another screen takes its place, hence providers (FinanceBloc, OnbardingBloc) are removed with home screen.So, make the roviders above the material app:
The dialog is separated for the Material context. You can pass the instance like