BottomNavigationBar with BLoC pattern - flutter

I really like the BLoC pattern and I am trying to understand it. But I can't seem to figure it out exactly how it should apply with the BottomNavigationBar.
Making a list of navigation pages and setting the current index on navigation bar tap event causes the whole app to redraw because of setState().
Can I use the Navigator to show the clicked navigation page without losing the navigation bar ?
Did anyone use the BLoC pattern with the BottomNavigationBar ? How do I do this ? I'd love to see a sample code.

I finally got it. I'm putting the whole code here to help others.
First read this wonderful article from didier boelens : https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/
using his bloc provider and base bloc create blocs. mine is like the following:
import 'dart:async';
import 'bloc_provider.dart';
import 'package:rxdart/rxdart.dart';
class NewsfeedBloc implements BlocBase {
BehaviorSubject<int> _ctrl = new BehaviorSubject<int>();
NewsfeedBloc(
// listen _ctrl event and do other business logic
);
void dispose() {
_ctrl.close();
}
}
then create the page that will use the bloc:
import 'package:flutter/material.dart';
import '../blocs/newsfeed_bloc.dart';
import '../blocs/bloc_provider.dart';
class NewsfeedPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NewsfeedBloc bloc = BlocProvider.of<NewsfeedBloc>(context);
// here you should use a stream builder or such to build the ui
return Container(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {/* do something with the bloc */},
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () {/* do something with the bloc */},
),
],
),
),
],
),
),
);
}
}
and finally the main.dart file containing a navigationbottombar and a drawer:
import 'dart:async';
import 'package:flutter/material.dart';
import 'blocs/bloc_provider.dart';
import 'blocs/application_bloc.dart';
import 'blocs/newsfeed_bloc.dart';
import 'blocs/tracking_bloc.dart';
import 'blocs/notifications_bloc.dart';
import 'blocs/item1_bloc.dart';
import 'blocs/item2_bloc.dart';
import 'pages/newsfeed.dart';
import 'pages/tracking.dart';
import 'pages/notifications.dart';
import 'pages/item1.dart';
import 'pages/item2.dart';
Future<void> main() async {
return runApp(BlocProvider<ApplicationBloc>(
bloc: ApplicationBloc(),
child: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
// define your blocs here so that you dont lose the state when your app rebuilds for some reason. thanks boformer for pointing that out.
NewsfeedBloc _newsfeedBloc;
PageController _pageController;
var _page = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Movies',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: AppBar(
title: new Text('App Title'),
),
body: PageView(
children: <Widget>[
BlocProvider<NewsfeedBloc>(
bloc: _newsfeedBloc(),
child: NewsfeedPage(),
),
// ...
],
controller: _pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.timeline),
title: Text("Timeline"),
),
BottomNavigationBarItem(
icon: Icon(Icons.art_track),
title: Text("Some Page"),
),
BottomNavigationBarItem(
icon: Icon(Icons.notifications),
title: Text("Notifications"),
),
],
onTap: navigationTapped,
currentIndex: _page,
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Settings'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider<Item1Bloc>(
bloc: Item1Bloc(),
child: Item1Page(),
);
}
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider<Item2Bloc>(
bloc: Item2Bloc(),
child: Item2Page(),
);
}
},
),
],
),
),
),
);
}
void navigationTapped(int page) {
_pageController.animateToPage(
page,
duration: Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
void onPageChanged(int page) {
setState(() {
this._page = page;
});
}
#override
void initState() {
super.initState();
_pageController = new PageController();
_newsfeedBloc = NewsfeedBloc();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
}

Related

flutter bottom bar change page setstate as default for some pages

I have a problem with my BottomNavigationBar in Flutter.
Please help me with this issue. i really need it to done.
I don't want to keep my page alive if I press jump to any page from screens, not from my BottomNavigationBar.
eg, if I have three screens that navigate from the bottom bar its works fine but if I add a button in any of the three pages that navigate to another screen that does not belong to the bottom bar then it keeps that screen alive the previous screen.
here my implementation.
BottomNavigation :
footer.dart
import 'package:flutter/material.dart';
import 'MyPage.dart';
import 'MyPage2.dart';
import 'MyPage3.dart';
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'Notifications.dart';
void main() {
runApp(MaterialApp(
home: Footer(),
));
}
class Footer extends StatefulWidget {
#override
_Footer createState() => _Footer();
}
class _Footer extends State<Footer> {
late List<Widget> _pages;
List<BottomNavigationBarItem> _items = [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.messenger_rounded),
label: "Messages",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Settings",
)
];
late int _selectedPage;
#override
void initState() {
super.initState();
_selectedPage = 0;
_pages = [
MyPage(
count: 1,
),
MyPage2(
count: 2,
),
MyPage3(
count: 3,
),
// This avoid the other pages to be built unnecessarily
//Notifications(),
// SizedBox(),
];
}
late DateTime currentBackPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
body: DoubleBackToCloseApp(
snackBar: SnackBar(
content: const Text(
'Tap back again to leave',
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.normal),
),
duration: Duration(seconds: 4),
backgroundColor: Colors.blue,
width: 340.0,
padding: EdgeInsets.all(15),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
child: _pages[_selectedPage],
),
bottomNavigationBar: BottomNavigationBar(
items: _items,
currentIndex: _selectedPage,
onTap: (index) {
setState(() {
// now check if the chosen page has already been built
// if it hasn't, then it still is a SizedBox
_selectedPage = index;
});
},
)
);
}
}
MyPage.dart:
import 'package:flutter/material.dart';
import 'MyCustomPage.dart';
import 'Notifications.dart';
class MyPage extends StatefulWidget {
final count;
MyPage({Key? key, this.count}) : super(key: key);
#override
_MyPage createState() => _MyPage();
}
class _MyPage extends State<MyPage>{
#override
Widget build(BuildContext context) {
// You'll see that it will only print once
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.text),
actions: <Widget>[
Row(
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => Notifications()));
},
child: Icon(Icons.notifications),
)
],
)
],
),
body: Center(
child: RaisedButton(
child: Text('my page1'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => MyCustomPage()));
},
),
),
);
},
);
},
);
}
}
MyCustomPage.dart
import 'package:flutter/material.dart';
class MyCustomPage extends StatefulWidget {
MyCustomPage ({Key? key}) : super(key: key);
#override
_MyCustomPage createState() => _MyCustomPage();
}
class _MyCustomPage extends State<MyCustomPage>{
#override
Widget build(BuildContext parentContext) {
return Scaffold(
appBar: AppBar(
title: Text('custompage'),
),
body: Column(
children: [
Expanded(
child: Container(
child: ListView.builder(
itemCount: 15,
itemBuilder: (context, index) {
return Container(
width: double.infinity,
child: Card(
child: Center(
child: Text('My Custom Page'),
),
),
);
},
),
),
),
],
),
);
}
}
Here are my three files that navigate the inner screen
the flow of navigation is first load Footer.dart file that is the main file inside there is three pages in BottomNavigationBar() in the first screen on tap home icon it loads MyPage.dart file which contains a text and button. on the button press, it navigates MyCustomPage.dart file. now the issue is when I click on the home icon from BottomNavigationBar() it loads MyPage screen and on press button inside that screen it loads MyCustomPage but when MyCustomPage.dart file load it keeps home icon alive and I don't have access to press the home icon again.
I hope you understand what I am trying to say.
If anyone knows please help me.

In Flutter CupertinoTabBar + StreamBuilder code is running but widgets are not rebuilt

I have a 2 tab screen where I want to dynamically rebuild the content depending on user's interactions. On the first tab there are multiple states which I try to handle by setting a current step name using model and provider. For the second screen after pressing a button I get the print message in a console but the content of CupertinoTabView remains unchanged.
Minimum runnable code snippet:
main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_app/screens/login.dart';
import 'package:my_app/screens/tab_bar.dart';
import 'package:my_app/models/model.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => Model(),
),
],
child: CupertinoApp(
theme: const CupertinoThemeData(
brightness: Brightness.light,
scaffoldBackgroundColor: CupertinoColors.white,
),
initialRoute: '/',
routes: {
'/': (context) => LoginScreen(),
'/tab_bar': (context) => TabBarScreen(),
//'/add_item': (context) => AddItemScreen(),
},
),
);
}
}
login.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_app/models/model.dart';
class LoginScreen extends StatelessWidget {
final model = Model();
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('app',),
),
child: Container(
alignment: FractionalOffset.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
child: Text('next'),
onPressed: (){
model.setStep('step1');
Navigator.pushNamed(context, '/tab_bar');
},
style: ElevatedButton.styleFrom(
primary: CupertinoColors.white,
side: BorderSide(color: Colors.blue, width: 2.0)
),
),
]
),
),
);
}
}
tab_bar.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_app/models/model.dart';
class TabBarScreen extends StatefulWidget {
#override
State createState() => TabBarScreenState();
}
class TabBarScreenState extends State<TabBarScreen> {
final model = Model();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
print(model.getStep);
return Consumer<Model>(
builder: (context, model, _) => CupertinoTabScaffold(
tabBar: CupertinoTabBar(
backgroundColor: CupertinoColors.white,
inactiveColor: Colors.blue,
activeColor: Colors.blueAccent,
border: const Border(
top: BorderSide(
color: CupertinoColors.activeBlue,
width: 2.0,
),
),
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.favorite_outline_outlined),
),
BottomNavigationBarItem(
icon: Icon(Icons.more_vert_sharp),
),
],
),
tabBuilder: (context, index) {
late final CupertinoTabView returnValue;
switch (index) {
case 0:
if (model.getStep == 'step1') {
print('step1');
returnValue = CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: SafeArea(
child: GestureDetector(
onTap: (){ },
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.only(bottom: 15.0),
child: FloatingActionButton(
onPressed: () { model.setStep('step2'); setState(() {
}); },
tooltip: 'step2',
child: Icon(Icons.add),
backgroundColor: CupertinoColors.white,
foregroundColor: CupertinoColors.black,
),
),
],
),
],
),
),
),
);
});
}
else if (model.getStep == 'step2') {
print('step2');
returnValue = CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: SafeArea(
child: Column(
children: <Widget> [
Text('step2'),
],
),
),
);
});
}
else {
returnValue = CupertinoTabView(builder: (context) {
return Container();
});
}
break;
case 1:
returnValue = CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: Text("Tab2"),
);
});
break;
}
return returnValue;
},
),
);
}
}
model.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Model extends ChangeNotifier {
static String currentStep = 'login';
// login
// step1 - step1
// step2 - step2
String get getStep {
return currentStep;
}
void setStep(String step) {
currentStep = step;
notifyListeners();
}
}
I want the tab to rebuild on pressing a button from step1 to show the step2 content. In a console I can see the print of 'step2' when pressing, but the actual tab remains unchanged.
Appreciate your help
Since nobody have answered I figured it out on my own.
The resolution is to use Navigator like this:
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Page 2 of tab'),
),
child: Center(
child: CupertinoButton(
child: const Text('Back'),
onPressed: () { Navigator.of(context).pop(); },
),
),
);
},
),
);
},
and therefore you won't need 'else if' conditioning.

How to make Modal Bottom Sheet elevated equally with Bottom App Bar in Flutter?

So, I am a new Flutter Developer and currently trying to make my own flutter app without any tutorial. I am confused with the elevation of the Modal Bottom Sheet and Bottom App Bar. I want both of the widgets to be elevated equally. Currently, my app behavior is like this.. The Bottom Modal Sheet just covers the Bottom App Bar and everything else. My code is something like this.
home_screen.dart (where my Bottom Modal Sheet, FAB, Bottom App Bar is)
// Packages
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:moneige/models/transaction.dart';
// UIs
import '../ui/home_screen/app_bar_title.dart';
import '../ui/home_screen/bottom_app_bar.dart';
import '../ui/home_screen/transaction_list_view.dart';
// Widgets
import '../widget/add_button.dart';
// Styles
import '../constants/styles.dart' as Styles;
class HomeScreen extends StatelessWidget {
final int totalBalance = 100000;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: MyAppBarTitle(totalBalance: totalBalance),
),
body: ValueListenableBuilder(
valueListenable: Hive.box('transactions').listenable(),
builder: (context, transactionBox, widget) {
return (transactionBox.length > 0)
? TransactionListView(transactionBox: transactionBox)
: Center(
child: Text('You have no transaction yet',
style: Styles.textMedium));
},
),
bottomNavigationBar: MyBottomAppBar(),
floatingActionButton: AddButton(() {
Hive.box('transactions').add(Transaction(
date: DateTime.now(),
changes: 123000,
notes: 'Crazier than usual'));
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
backgroundColor: Colors.white,
context: context,
elevation: 10,
useRootNavigator: true,
builder: (BuildContext context) {
return Container(height: 200);
});
}),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
bottom_app_bar.dart (where MyBottomAppBar is)
// Packages
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
// Widgets
import '../../widget/settings_button.dart';
import '../../widget/transaction_report_switch.dart';
// Styles
import '../../constants/styles.dart' as Styles;
class MyBottomAppBar extends StatelessWidget {
const MyBottomAppBar({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BottomAppBar(
shape: CircularNotchedRectangle(),
color: Styles.colorPrimary,
child: Row(
children: [
Spacer(),
Container(
child: TransactionReportSwitch(() => Hive.box('transactions').deleteAll(Hive.box('transactions').keys)),
),
Container(
child: SettingsButton(),
),
],
),
);
}
}
add_button.dart (where AddButton is)
// Packages
import 'package:flutter/material.dart';
// Styles
import '../constants/styles.dart' as Styles;
class AddButton extends StatelessWidget {
final Function handler;
AddButton(this.handler);
#override
Widget build(BuildContext context) {
return FloatingActionButton(
child: Icon(
Icons.add_rounded,
color: Colors.white,
size: 28,
),
backgroundColor: Styles.colorPrimary,
focusColor: Colors.white12,
hoverColor: Colors.white12,
foregroundColor: Colors.white12,
splashColor: Colors.white24,
onPressed: handler,
);
}
}
I saw a really good animated FAB, Modal Bottom Sheet, and Bottom App Bar composition in the Flutter Gallery app, Reply example. . (https://play.google.com/store/apps/details?id=io.flutter.demo.gallery&hl=en) When the Modal Bottom Sheet appears, the FAB disappears animatedly and the Sheet and App Bar are equally elevated, also the Sheet is above the Bottom App Bar. This is the behavior I wanted in my app, do you guys have any solution?
A solution would be to have the BottomAppBar in other ascendant Scaffold in your widget tree.
Reduced example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold( // Your Ascendant Scaffold
body: MyScreen(),
bottomNavigationBar: MyBottomAppBar() // Your BottomAppBar here
),
);
}
}
class MyScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold( // Your descendant Scaffold
body: Center(
child: Text('Hello world'),
),
);
}
}
Complete example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Item 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Item 2',
),
],
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Builder(
builder: (context) => ElevatedButton(
child: Text('Show modal bottom sheet'),
onPressed: () => _displaysBottomSheet(context),
),
),
),
);
}
void _displaysBottomSheet(BuildContext context) {
Scaffold.of(context).showBottomSheet(
(context) => Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
),
);
}
}
RESULT ON DEVICE

How to add navigation route to Card in Flutter

In the code below, I have a method myMenu on a card. How do I navigate to another page when the card is tapped? There are going to be several of these cards which will link to its own page content. Each time I add a function to for an example it gives an error. How do I do it properly?
import 'package:flutter/material.dart';
import 'package:tarjous_app/gridview_demo.dart';
void main(List<String> args) {
runApp(
new MaterialApp(home: TarjousAle(), debugShowCheckedModeBanner: false));
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape});
final String title;
final IconData icon;
final MaterialColor shape;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => GridViewDemo()),
),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
In the inkwell widget, I add a function that works for all the cards. But what I really want it for each card to navigate to its own page. E.g Records should navigate to its own records page, the same thing for Academy to academy page
You could receive the page in the constructor and then go to that page, like this:
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.page});
final Widget page;
...
}
Then, in onTap:
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
)
So now you can do this:
MyMenu(
...
page: GridViewDemo1(),
),
MyMenu(
...
page: GridViewDemo2(),
)
Note that to navigate to some page, your context must contain a Navigator instance of parent. So if you try to navigate directly from MaterialApp, you might run into issues. I will not belabour the point here since it was explained very well in this thread, but it is something to keep in mind in case you happen to run into it.
Edited to address comments:
I'd do something like this for your case. Named routes make it easy to specify which route you'd like the card to take you to, which you kind of need to do if you want the same widget to take you to different routes.
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
new MaterialApp(
home: TarjousAle(),
debugShowCheckedModeBanner: false,
routes: {
GridViewDemo.route: (context) => GridViewDemo(),
AnotherDemo.route: (context) => AnotherDemo(),
},
),
);
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
route: GridViewDemo.route
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
route: AnotherDemo.route
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.route});
final String title;
final IconData icon;
final MaterialColor shape;
final String route;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.pushNamed(context, route),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
class GridViewDemo extends StatelessWidget {
static String route = '/demo';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.brown,
appBar: AppBar(title: Text('Grid view demo')),
body: Center(
child: Text('Grid view demo'),
),
);
}
}
class AnotherDemo extends StatelessWidget {
static String route = '/another';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(title: Text('Another demo')),
body: Center(
child: Text('Another demo'),
),
);
}
}
You can read more about the basics of navigation in official docs, and also another docs page if you fancy the named routes.
Wrap the card with GestureDetector and you can use opnTap property.
for more details Official Documentation
Try wrapping your Card in a GestureDetector like below:
GestureDetector (
child: Card(),
onTap: () {},
),
wrap the card with InkWell widget and define your navigator.push in the onTap method.
class CardWidget extends StatelessWidget {
final Function onTapCard;
const CardWidget({Key key, #required this.onTapCard}) : super(key: key);
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: onTapCard,
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
then we have our list here
class CardList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CardWidget(
onTapCard: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourSecondPage()),
),
),
CardWidget(
onTapCard: Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourThirdPage()),
),
),
],
);
}
}

How to go from one screen to another clearing all the previous screens from stack?

In my Flutter project, I have three screens - Screen0, Screen1, Screen2. Now, from the screen0 , I can go to the screen1 with a button click, then from screen1 we can go to screen2 with a button. In screen2, I have a button which I have used to go back to screen0. As Screen0 is the initial screen, I want to clear all the previous screens when I come back to Screen0 and don't want to have any back option like in this image-
And here's my code-
main.dart
import 'package:flutter/material.dart';
import 'screen0.dart';
import 'screen1.dart';
import 'screen2.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context)=> Screen0(),
'/first': (context)=> Screen1(),
'/second': (context)=> Screen2(),
},
);
}
}
I have set all the routes in my main.dart file. Then in the Screen0, I have a button to go to screen1 like below-
screen0.dart
import 'package:flutter/material.dart';
class Screen0 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.purple,
title: Text('Screen 0'),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
color: Colors.red,
child: Text('Go to screen 1'),
onPressed: (){
Navigator.pushNamed(context, '/first');
},
),
],
),
),
);
}
}
In the screen1, I have a button to go to screen2-
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.purple,
title: Text('Screen 1'),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
color: Colors.red,
child: Text('Go to screen 2'),
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return Screen2();
})
);
},
),
],
),
),
);
}
}
Now, in the screen2, i have the button to go to screen0 -
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.purple,
title: Text('Screen 2'),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
color: Colors.red,
child: Text('Go to screen 0'),
onPressed: (){
Navigator.pop(context, Screen2);
Navigator.pushNamed(context, '/');
},
),
],
),
),
);
}
}
I have tried some solution like using Navigator.pushAndRemoveUntil given in the below link-
Flutter - Navigate to a new screen, and clear all the previous screens
But this solution didn't work for me.
So, it would be nice if someone help me out this code to solve the problem.
use pushNamedAndRemoveUntil
for example
Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);