Navigating between Cupertino routes not working - flutter

I have a simple app that uses the Cupertino App. I have four tabs at the bottom that allows me to navigate between pages. on the last tab AddCashPage I've added on button on the page that will allow me to navigate to the second page CalendarPage
The app is only one page so I'll post the entire thing
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// import 'styles.dart';
import 'pages/summary_page.dart';
import 'pages/calendar_page.dart';
import 'pages/remove_cash_page.dart';
final scakey = new GlobalKey<_BottomState>();
class CashOnHandApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cash on Hand',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Cash on Hand'),
),
child: Bottom(key: scakey),
),
);
}
}
class Bottom extends StatefulWidget {
Bottom({Key key}) : super(key: key);
#override
_BottomState createState() => _BottomState();
}
class _BottomState extends State<Bottom> {
final myKey = new GlobalKey<_BottomState>();
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
print(_selectedIndex);
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
key: myKey,
tabBar: CupertinoTabBar(
onTap: _onItemTapped,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Summary Page'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.clock_solid),
title: Text('Calendar'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.minus_circled),
title: Text('Remove Cash'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.plus_circled),
title: Text('Add Cash'),
),
],
),
tabBuilder: (context, index) {
switch (index) {
case 0:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: SummaryPage(),
);
});
case 1:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: CalendarPage(),
);
});
case 2:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: RemoveCashPage(),
);
});
case 3:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: AddCashPage(),
);
});
}
},
);
}
}
class AddCashPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CupertinoButton(
onPressed: () {
scakey.currentState._onItemTapped(1);
},
child: Text('data'),
),
],
),
);
}
}
I was able to use scakey.currentState._onItemTapped(1); on material app before so I thought it would work the same on a Cupertino App

According to official document https://api.flutter.dev/flutter/cupertino/CupertinoTabScaffold/tabBar.html
providing a different CupertinoTabBar.currentIndex does not affect the scaffold or the tab bar's active tab index. To programmatically change the active tab index, use a CupertinoTabController.
You need to use tab controller
code snippet
final CupertinoTabController _controller = CupertinoTabController();
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
_controller.index = index;
print(_selectedIndex);
});
}
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
final scakey = new GlobalKey<_BottomState>();
class CashOnHandApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cash on Hand',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Cash on Hand'),
),
child: Bottom(key: scakey),
),
);
}
}
class Bottom extends StatefulWidget {
Bottom({Key key}) : super(key: key);
#override
_BottomState createState() => _BottomState();
}
class _BottomState extends State<Bottom> {
final myKey = new GlobalKey<_BottomState>();
int _selectedIndex = 0;
final CupertinoTabController _controller = CupertinoTabController();
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
_controller.index = index;
print(_selectedIndex);
});
}
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
controller: _controller,
key: myKey,
tabBar: CupertinoTabBar(
onTap: _onItemTapped,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Summary Page'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.clock_solid),
title: Text('Calendar'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.minus_circled),
title: Text('Remove Cash'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.plus_circled),
title: Text('Add Cash'),
),
],
),
tabBuilder: (context, index) {
switch (index) {
case 0:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: SummaryPage(),
);
});
case 1:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: CalendarPage(),
);
});
case 2:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: RemoveCashPage(),
);
});
case 3:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: AddCashPage(),
);
});
}
},
);
}
}
class AddCashPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CupertinoButton(
onPressed: () {
scakey.currentState._onItemTapped(1);
},
child: Text('data'),
),
],
),
);
}
}
class SummaryPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Summary");
}
}
class CalendarPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Calendar");
}
}
class RemoveCashPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("RemoveCash");
}
}

Related

how to keep bottom navigation bar in all pages with stateful widget in Flutter

I am able to navigate to multiple different pages with visible bottom navigation bar on all pages but not able to switch between all of them so how can I switch between tabs with bottom bar being there in all pages
I got till here using this Answer but not able to make it work i.e to switch between bottom navigation tabs...
in short I want to add view for my message tab i.e second tab and move to it also without losing my bottom navigation bar for every page i navigate to...
so far my code,
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.orange,
items: [
BottomNavigationBarItem(icon: Icon(Icons.call), label: 'Call'),
BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
],
),
body: Navigator(
onGenerateRoute: (settings) {
Widget page = Page1();
if (settings.name == 'page2') page = Page2();
return MaterialPageRoute(builder: (_) => page);
},
),
);
}
}
// 1st Page:
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page1')),
body: Center(
child: RaisedButton(
onPressed: () => Navigator.pushNamed(context, 'page2'),
child: Text('Go to Page2'),
),
),
);
}
}
// 2nd Page:
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}
Try like this:
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int activeIndex = 0;
void changeActivePage(int index) {
setState(() {
activeIndex = index;
});
}
List<Widget> pages = [];
#override
void initState() {
pages = [
Page1(() => changeActivePage(2)),
Page2(),
Page3(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: SizedBox(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(onPressed: () => changeActivePage(0), icon: Icon(Icons.call)),
IconButton(onPressed: () => changeActivePage(1), icon: Icon(Icons.message)),
],
),
),
body: pages[activeIndex]);
}
}
// 1st Page:
class Page1 extends StatelessWidget {
final Function callback;
const Page1(this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page1')),
body: Center(
child: RaisedButton(
onPressed: () => callback(),
child: Text('Go to Page3'),
),
),
);
}
}
// 2nd Page:
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) =>
Scaffold(appBar: AppBar(title: Text('Page2')));
}
// 3rd Page:
class Page3 extends StatelessWidget {
const Page3();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page3')),
body: Center(child: Text('Page3')),
);
}
}

Flutter - Best way to aggregate data from child widgets in an IndexedStack

I have an IndexedStack in a Scaffold that I use to manage my registration. The Registration widget itself is Stateful, but the widgets that compose it are Stateless. The parent widget looks like this:
class Registration extends StatefulWidget {
#override
_RegistrationState createState() => _RegistrationState();
}
class _RegistrationState extends State<Registration> {
int _index = 0;
void _nextPage() {
setState(() {
_index++;
});
}
void _prevPage() {
setState(() {
_index--;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
leading: new IconButton(
icon: new Icon(Icons.arrow_back,
color: Theme.of(context).primaryColor),
onPressed: () {
if (_index == 0) {
Navigator.pop(context);
} else {
_prevPage();
}
}),
elevation: 0.0,
),
body: IndexedStack(
children: <Widget>[
RegistrationPhone(_nextPage),
RegistrationName(_nextPage),
RegistrationBirthday(_nextPage),],
index: _index,
),
);
}
}
What is the best way to take data from these child widgets?
Should I pass in a callback function and hold the data in the parent? Should I pass the information down the line from widget to widget until it's submitted? I don't know what the practices are for sharing data across multiple screens.
Use Provider
Add Dependency :
dependencies:
provider: ^4.3.3
here is the Example :
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),
/// Extracted as a separate widget for performance optimization.
/// As a separate widget, it will rebuild independently from [MyHomePage].
///
/// This is totally optional (and rarely needed).
/// Similarly, we could also use [Consumer] or [Selector].
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),
/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Count extends StatelessWidget {
const Count({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(
/// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headline4);
}
}

flutter drawer to remember the clicked item

I want to remember the item that was clicked in drawer .
I am using the same widget for drawer ( sameDrawerOnly ) in all three widgets ( MyHomePage , FirstPage and SecondPage) and using variable itemClicked to trackthe item that was tapped inside setState . But the conditional formatting is not working.
Here is the code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
DrawerOnly sameDrawerOnly = DrawerOnly();
class MyApp extends StatelessWidget {
final appTitle = 'Drawer Demo';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('My Page!')),
drawer: sameDrawerOnly,
);
}
}
class DrawerOnly extends StatefulWidget {
const DrawerOnly ({
Key key,
}) : super(key: key);
#override
_DrawerOnlyState createState() => _DrawerOnlyState();
}
class _DrawerOnlyState extends State<DrawerOnly > {
int itemClicked = 0;
#override
Widget build(BuildContext ctxt) {
return Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Text("DRAWER HEADER.."),
decoration: new BoxDecoration(
color: Colors.orange
),
),
new ListTile(
title: new Text("Item => A", style: itemClicked==1 ? TextStyle( fontWeight: FontWeight.bold, color: Colors.red.withOpacity(0.6) ) : null),
onTap: () {
Navigator.pop(ctxt);
setState(() {
itemClicked=1;
});
Navigator.push(ctxt,
new MaterialPageRoute(builder: (ctxt) => new FirstPage()));
},
),
new ListTile(
title: new Text("Item => 2", style: itemClicked==2 ? TextStyle( fontWeight: FontWeight.bold , color: Colors.green.withOpacity(0.6) ) : TextStyle()),
onTap: () {
Navigator.pop(ctxt);
setState(() {
itemClicked=2;
});
Navigator.push(ctxt,
new MaterialPageRoute(builder: (ctxt) => new SecondPage()));
},
),
],
)
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
drawer: sameDrawerOnly,
appBar: new AppBar(title: new Text("First Page"),),
body: new Text("I belongs to First Page"),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
drawer: sameDrawerOnly,
appBar: new AppBar(title: new Text("Second Page"),),
body: new Text("I belongs to Second Page"),
);
}
}
What went wrong
Although sameDrawerOnly was declared at the top most part of your file. Everytime the widget re-draws your app's screens, eg. opening FirstPage via MaterialPageRoute, the variable in the DrawerOnly widget will always stay to zero. Because it is always re-drawn based on your configuration.
What you can do
Hotfix: Make itemClicked a static variable. (Not Recommended)
// Before
int itemClicked
// After
static int itemClicked
Alternatively, you can refactor your code and use PageView instead of opening a new Scaffold widget every time you switch between drawer items. Then, you can now use currentPageValue to determine what item was selected by the user.
MyHomePage.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Drawer Demo';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
PageController _pageController;
double currentPageValue = 0.0;
#override
void initState() {
super.initState();
_pageController = PageController();
_pageController.addListener(() {
setState(() {
currentPageValue = _pageController.page;
// Do whatever you like with the page value
});
});
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: PageView(
controller: _pageController,
children: <Widget>[
FirstPage(),
SecondPage(),
],
),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
_pageController.jumpToPage(0);
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
_pageController.jumpToPage(1);
Navigator.pop(context);
},
),
],
),
),
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(color: Colors.yellow);
}
}
View on dartpad.dev.
More on:
https://flutter.dev/docs/cookbook/design/drawer

Accessing a method of state class using its stateful widget?

I have a method in state class, but I need to access that method in outside using its widget class reference,
class TestFormState extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _testState();
}
}
class _testFormState extends State<TestFormState> {
int count = 1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text("Count : $count"),
),
);
}
clickIncrease(){
setState(() { count += 1; });
}
}
and I need to access the above widget`s clickIncrease in another widget, like below code,
class TutorialHome extends StatelessWidget {
TestFormState test;
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
body: Column(
children: <Widget>[
test = TestFormState(),
FlatButton(
child: Text("Increase"),
onPressed: (){
test.state.clickIncrease(); // This kind of thing I need to do
},
),
]
),
);
}
I wrote above code just for demostrate the issue.
I have a trick, but I don't know if it is a bad practice or not.
class TestFormState extends StatefulWidget {
_TestFormState _testFormState;
#override
State<StatefulWidget> createState() {
_testFormState = _TestFormState();
return _testFormState;
}
}
class _TestFormState extends State<TestFormState> {
int count = 1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text("Count : $count"),
),
);
}
clickIncrease(){
setState(() { count += 1; });
}
}
Now, you can access it here :
class TutorialHome extends StatelessWidget {
TestFormState test;
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
body: Column(
children: <Widget>[
TextButton(
child: Text("Increase"),
onPressed: () {
test._testFormState
.clickIncrease(); // This is accessable
},
),
]
),
);
}
}
I suggest taking a look at ValueNotifier
I think there is a better way to manage your app state in an easy way and I agree that using provider could be effective.
Provide the model to all widgets within the app. We're using
ChangeNotifierProvider because that's a simple way to rebuild
widgets when a model changes. We could also just use Provider, but
then we would have to listen to Counter ourselves.
Read Provider's docs to learn about all the available providers.
Initialize the model in the builder. That way, Provider can own
Counter's lifecycle, making sure to call dispose when not needed
anymore.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
Simplest possible model, with just one field. ChangeNotifier is a
class in flutter:foundation. Counter does not depend on Provider.
class Counter with ChangeNotifier {
int count = 1;
void clickIncrease() {
count += 1;
notifyListeners();
}
}
Consumer looks for an ancestor Provider widget and retrieves its
model (Counter, in this case). Then it uses that model to build
widgets, and will trigger rebuilds if the model is updated.
You can access your providers anywhere you have access to the context.
One way is to use Provider<Counter>.of(context).
The provider package also defines extension methods on context itself.
You can call context.watch<Counter>() in a build method of any
widget to access the current state of Counter, and to ask Flutter to
rebuild your widget anytime Counter changes.
You can't use context.watch() outside build methods, because that
often leads to subtle bugs. Instead, you should use
context.read<Counter>(), which gets the current state but doesn't
ask Flutter for future rebuilds.
Since we're in a callback that will be called whenever the user taps
the FloatingActionButton, we are not in the build method here. We
should use context.read().
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Count:'),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
// I've change the button to `FloatingActionButton` for better ui experience.
floatingActionButton: FloatingActionButton(
// Here is the implementation that you are looking for.
onPressed: () {
var counter = context.read<Counter>();
counter.increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Complete code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
class Counter with ChangeNotifier {
int count = 1;
void clickIncrease() {
count += 1;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Count:'),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.count}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
var counter = context.read<Counter>();
counter.clickIncrease();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Actual app:
For more information on the provider package (where Provider comes from), please see the package documentation.
For more information on state management in Flutter, and a list of other approaches, head over to the State management page at flutter.dev.
There is a built in method findAncestorStateOfType to find Ancestor _MyAppState class of the Parent MyApp class.
Here is the Code
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
static void setLocale(BuildContext context, Locale locale) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state!.setLocale(locale);
}
#override
_MyAppState createState() => _MyAppState();
}
// ignore: use_key_in_widget_constructors
class _MyAppState extends State<MyApp> {
// const MyApp({Key? key}) : super(key: key)
late Locale _locale;
void setLocale(Locale value) {
setState(() {
_locale = value;
});
}
}
class TestForm extends StatelessWidget {
final int _count;
TestForm(int count) : _count = count;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text('Count : $_count'),
),
);
}
}
class TutorialHome extends StatefulWidget {
#override
State<TutorialHome> createState() => _TutorialHomeState();
}
class _TutorialHomeState extends State<TutorialHome> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TestForm(_count), // <---
TextButton(
child: Text("Increase"),
onPressed: () => setState(() => _count++),
),
],
),
);
}
}

Flutter BottomNavigationBar and advanced navigation

I'm building an app with 3 items in the bottom navigation bar. When I change the tab, a different widget is rendering. So far, so good...
import 'package:flutter/material.dart';
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text("Red")
),
BottomNavigationBarItem(
icon: Icon(Icons.location_on),
title: Text("Blue")
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text("Green")
)
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
)
);
}
_definePages() {
_pages = [
Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
])
),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//TODO: How to navigate to another page with still displaying the bottom navigation bar?
}
),
);
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
PostHeader(),
Text('This is a post.')
]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
But I can't figure out a best practice for more advance navigation. There are 2 problems that I'm currently facing.
When tabbing the FloatingActionButton on the first page, I want to display a fourth page but the BottomNavigationBar still needs to be visible and operable.
Building a more complex app, I'm dealing with a handful of nested classes. So on my root page, there is a class "Post" and the post contains a class "PostHeader". In PostHeader, there is a ListTile with an onTap callback that should affect my _selectedContent. How do I define this callback? Passing it trough all the different classes didn't seem right.
I thought about defining it in my BottomTest.dart and passing it trough Post and PostTile but that doesn't seem like best practice to me, especially when talking about lots of required callbacks.
Thank you very, very much in advance!
I'm assuming that the fourth page will be shown as any of the other three pages and since the button is in the first page, the fourth page will take the place of the first page and still signal the first bottom "red" field as active.
If that is the case you should create an independent widget for the first page that includes all the logic you need to show other content. Thus you avoid rebuilding the main layout, including the BottomNavigationBar.
You could use something along these lines, by using a FirstPage widget:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new BottomTest(),
);
}
}
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.add), title: Text("Red")),
BottomNavigationBarItem(
icon: Icon(Icons.location_on), title: Text("Blue")),
BottomNavigationBarItem(
icon: Icon(Icons.people), title: Text("Green"))
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
));
}
_definePages() {
_pages = [
FirstPage(),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[PostHeader(), Text('This is a post.')]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
bool showFirst = true;
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _onButtonPressed,
),
);
}
_onButtonPressed() {
setState(() {
showFirst = !showFirst;
});
}
_buildFirst() {
return Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
_buildFourth() {
return Container(
color: Colors.grey,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
#override
Widget build(BuildContext context) {
return showFirst ? _buildFirst() : _buildFourth();
}
}
For the second point, perhaps you should open another question so you keep two, more or less, unrelated matters in different answers.