I'm new to flutter and in need of help with this project I'm working on.
I'm trying to change the content of the body
property of the Scaffold
widget dynamically based on what the user clicked on the bottomNavigationBar
.
I am trying to use the Provider state management 'cause I'm trying to learn while I'm creating some apps.
Here's what I got;
main.dart
void main() {
runApp(Pananta());
}
class Pananta extends StatefulWidget {
Pananta({Key? key}) : super(key: key);
@override
State<Pananta> createState() => _PanantaState();
}
class _PanantaState extends State<Pananta> {
int pageIndex = 0;
void changePage() {
setState(() {
Provider.of<ScreenData>(context).pageIndex;
});
}
@override
Widget build(BuildContext context) {
int screenDataIndex = Provider.of<ScreenData>(context).selectedIndex;
return ChangeNotifierProvider<ScreenData>(
create: (_) => ScreenData(),
builder: (context, child) =>
ScreenData.pages[context.watch<ScreenData>().selectedIndex],
child: MaterialApp(
theme: ThemeData(fontFamily: 'Poppins'),
home: SafeArea(
child: Scaffold(
body: screenDataIndex == 0
? ScreenData.pages[0]
: ScreenData.pages[screenDataIndex],
bottomNavigationBar: BottomBarMenu(),
),
),
),
);
}
}
screen_data.dart
class ScreenData extends ChangeNotifier {
int pageIndex = 0;
static List<Widget> pages = [
const HomePage(),
UserProfile(),
];
set selectedpage(int selected) {
pageIndex = selected;
}
int get selectedIndex {
notifyListeners();
return pageIndex;
}
}
The error that I'm getting:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown building Pananta(dirty, state:
_PanantaState#683a3):
Error: Could not find the correct Provider<ScreenData> above this Pananta 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 Pananta is under your MultiProvider/Provider<ScreenData>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
` Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>().toString()),
);
}`
consider using `builder` like so:
`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, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}`
I think the int screenDataIndex = Provider.of<ScreenData>(context).selectedIndex;
line is what's causing the issue, because when I remove it and set the body
to ScreenData.pages[0]
the error goes away -- I can't figure out what to do with it. Any help or articles that I can read will be very much appreciated. Thank you!
CodePudding user response:
try a configuration like this:
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
Create a list of widget and put inside screens you want show.Create relatives BottomNavigationBarItem and the onTap function which update index with selectedIndex. I think your code is harder than necessary. Try and let me know. Nice coding!
CodePudding user response:
I will suggest separating the context to read the provider, and using Consumer
widget inside widget tree.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ScreenData>(
create: (_) => ScreenData(),
child: MaterialApp(
theme: ThemeData(fontFamily: 'Poppins'),
home: Pananta(),
),
);
}
}
class Pananta extends StatefulWidget {
Pananta({Key? key}) : super(key: key);
@override
State<Pananta> createState() => _PanantaState();
}
class _PanantaState extends State<Pananta> {
int pageIndex = 0;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(body: Consumer<ScreenData>(
builder: (context, value, child) {
final currentPage = value.pageIndex;
return currentPage == 0
? ScreenData.pages[0]
: ScreenData.pages as Widget;
},
)),
);
}
}