Flutter: have drawer block bottom navigation bar - flutter

I have a simple app that has 4 main pages as mentioned on the bottom navigation bar, and also a subpage (a page you can access from the main page but its not a page you can access from the bottom navigation bar.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('Home'),
),
body: Container(
child: const Center(
child: Text("Random Page"),
),
),
);
}
}
class PageDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
endDrawer: const Drawer(),
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('drawer'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("I want the drawer to cover the nav bar here"),
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SubPage()),
);
},
child: const Text('Go to sub page'),
),
),
],
),
),
);
}
}
class SubPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.green,
title: const Text('subpage'),
),
body: const Padding(
padding: EdgeInsets.all(15.0),
child: Center(
child: Text(
"The navbar is active here and has a different appbar from the previous page"),
),
));
}
}
class CustomTab extends StatelessWidget {
CustomTab({required this.child});
final Widget child;
late BuildContext tabContext;
#override
Widget build(BuildContext context) {
return CupertinoTabView(
builder: (BuildContext context) {
tabContext = context;
return child;
},
);
}
}
class Tabbed extends StatefulWidget {
#override
_TabbedState createState() => _TabbedState();
}
class _TabbedState extends State<Tabbed> {
int _currentTab = 0;
final List<CustomTab> tabs = <CustomTab>[
CustomTab(
child: Page(),
),
CustomTab(
child: PageDrawer(),
),
CustomTab(
child: Page(),
),
CustomTab(
child: Page(),
),
];
Future<void> _setTab(int index) async {
if (_currentTab == index) {
if (Navigator.of(tabs[index].tabContext).canPop()) {
Navigator.of(tabs[index].tabContext)
.popUntil((Route<dynamic> r) => r.isFirst);
}
return;
}
setState(() {
_currentTab = index;
});
}
#override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
_buildStack(),
_buildTabs(),
],
),
);
}
Widget _buildStack() {
return Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
child: IndexedStack(
sizing: StackFit.expand,
index: _currentTab,
children: tabs,
),
),
);
}
Widget _buildTabs() {
return Container(
color: Colors.blue,
child: SafeArea(
top: false,
child: SizedBox(
height: 55.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
iconSize: 30.0,
color: _currentTab == 0
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.home),
onPressed: () {
_setTab(0);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 1
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.search),
onPressed: () {
_setTab(1);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 2
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.notifications),
onPressed: () {
_setTab(2);
},
),
IconButton(
iconSize: 30.0,
color: _currentTab == 3
? const Color.fromRGBO(58, 66, 86, 1.0)
: const Color.fromRGBO(58, 66, 86, 0.3),
icon: const Icon(Icons.settings),
onPressed: () {
_setTab(3);
},
)
],
),
),
),
);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Tabbed(),
);
}
}
void main() => runApp(App());
The issue I have with it currently is that I want the drawer (on the second page) to open over the navigational bar. I understand that to do so I must have the drawer on the same level or above the navbar, but the only way I can think of doing that is to have a predefined appbar in the scaffold. The reason this doesnt work for me is that I would like the sub page to have a different appbar style but I have no known way of differentiating between them since the page index for the subpage and the main page is the same.
So this would be an invalid solution:
appbar: (_index == 1) ? Appbar() : null,
At this point im out of ideas and would appreciate any suggestions :)

Related

How to move Scaffold's body along with bottomSheet on Flutter?

I'm trying to achieve a particular behavior for my Scaffold when showing a BottomSheet. I want the Scaffold's body to move along with the bottom sheet. That is, when the Bottomheet comes out, the body of the Scaffold should go up with it. Like the image at the right. I'm not sure if my approach is the correct one. Maybe there are other better options to make this behavior possible.
The code with which I'm currently working is here:
Scaffold(
backgroundColor: Colors.purple[100],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
height: 900,
child: Builder(
builder: (context) => Container(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
if (bottomSheetIsOpen) {
bottomSheetIsOpen = false;
Navigator.of(context).pop();
}
},
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 50),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 300,
child: TextField(
cursorWidth: 3,
cursorColor: Colors.purple,
onTap: () {
bottomSheetIsOpen = true;
showBottomSheet(
clipBehavior: Clip.hardEdge,
context: context,
builder: (context) => Container(
child: Container(
height: 200,
color: Colors.red,
),
),
);
},
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
style: TextStyle(fontSize: 24),
showCursor: true,
readOnly: _readOnly,
),
),
Container(
height: 300,
width: 300,
color: Colors.yellow,
),
Container(
height: 250,
width: 300,
color: Colors.orange,
),
],
),
),
),
),
),
),
),
);
You could achieve this with a Stack and two AnimatedPositioned widget:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottomsheet Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final _isOpenBottomSheet = useState(false);
return Scaffold(
appBar: AppBar(title: Text('Bottomsheet Demo')),
body: LayoutWithBottomSheet(
children: List.generate(
10,
(index) => Container(
height: 100,
color: Colors.red.withGreen(index * 25),
child: Center(
child: Text(
index.toString(),
style: TextStyle(fontSize: 24.0),
),
),
),
).toList(),
bottomSheetChild: Container(color: Colors.yellow),
bottomSheetHeight: 400,
animationSpeed: Duration(milliseconds: 300),
animationCurve: Curves.easeInOutQuad,
isOpenBottomSheet: _isOpenBottomSheet.value,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_isOpenBottomSheet.value = !_isOpenBottomSheet.value;
},
child: Icon(_isOpenBottomSheet.value
? Icons.arrow_downward
: Icons.arrow_upward),
),
);
}
}
class LayoutWithBottomSheet extends HookWidget {
final List<Widget> children;
final Widget bottomSheetChild;
final Duration animationSpeed;
final Curve animationCurve;
final double bottomSheetHeight;
final bool isOpenBottomSheet;
const LayoutWithBottomSheet({
Key key,
this.children,
this.bottomSheetChild,
this.animationSpeed,
this.animationCurve,
this.bottomSheetHeight,
this.isOpenBottomSheet,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final _scrollController = useScrollController();
final childrenBottom = useState<double>();
final bottomSheetBottom = useState<double>();
useEffect(() {
if (isOpenBottomSheet) {
childrenBottom.value = bottomSheetHeight;
bottomSheetBottom.value = 0;
if (_scrollController.hasClients) {
Future.microtask(
() => _scrollController.animateTo(
_scrollController.offset + bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
),
);
}
} else {
childrenBottom.value = 0;
bottomSheetBottom.value = -bottomSheetHeight;
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.offset - bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
);
}
}
return;
}, [isOpenBottomSheet]);
return Stack(
children: [
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
top: 0,
bottom: childrenBottom.value,
child: ListView(
controller: _scrollController,
children: children,
),
),
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
bottom: bottomSheetBottom.value,
height: bottomSheetHeight,
child: bottomSheetChild,
),
],
);
}
}
Instead of showing a bottom sheet, you can add a new widget to a Column
reserve:true is the key parameter for navigating to bottom
like:
return Scaffold(
body: SingleChildScrollView(
reserve: true,
child: Column(
children: [
YourWidget(),
if (isOpenBottomSheet)
YourBottomSheet()
],
),
),
);
the complete example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isOpenBottomSheet = false;
final _controller = ScrollController();
void _incrementCounter() {
setState(() {
isOpenBottomSheet = !isOpenBottomSheet;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
controller: _controller,
reverse: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// your widget
Container(
height: MediaQuery.of(context).size.height,
color: Colors.black),
// your bottom sheet
if (isOpenBottomSheet) Container(height: 400, color: Colors.yellow),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
you can use sliding_up_panel with parallax effect:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: SlidingUpPanel(
parallaxEnabled: true,
parallaxOffset: 0.4
panel: Center(
child: Text("This is the sliding Widget"),
),
body: Center(
child: Text("This is the Widget behind the sliding panel"),
),
),
);
}

bottom navigation bar with Floating Action Button flutter

I want to make a bottom navigation bar, with a Floating Action Button, in Flutter
when I click the Floating Action Button, the window will appear on the screen,
the photo in below will help to understand my request:
You can copy paste run full code below
You can use OverlayEntry and set color
entry = OverlayEntry(
builder: (context) {
return Center(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.black.withOpacity(0.5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("abc"),
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class BottomAppBarPage extends StatefulWidget {
#override
_BottomAppBarPageState createState() => _BottomAppBarPageState();
}
class _BottomAppBarPageState extends State<BottomAppBarPage> {
OverlayEntry entry;
#override
void initState() {
super.initState();
entry = OverlayEntry(
builder: (context) {
return Center(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.black.withOpacity(0.5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("abc"),
),
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("def"),
),
RaisedButton(
onPressed: () {
entry.remove();
},
child: Text("123"),
),
],
),
),
);
},
);
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bottom App Bar')),
body: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
Overlay.of(context).insert(entry);
},
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 4.0,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
)
],
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BottomAppBarPage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Theme Data not applying in inherited classes

I have declared a theme in main.dart , and it works fine as long as the context is used in main.dart but when I use Theme.of(context).primaryColor in the child class context doesn't pickup the theme.
Main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expenso',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.amber
),
home: Expenso(),
);
}
}
class Expenso extends StatefulWidget {
#override
_ExpensoState createState() => _ExpensoState();
}
class _ExpensoState extends State<Expenso> {
final List<Transaction> _transactions=[
];
void _addTransaction(String txTitle,double txAmount)
{
final newTx=Transaction(
title: txTitle,
amount: txAmount,
id: DateTime.now().toString(),
date: DateTime.now()
);
setState(() {
_transactions.add(newTx);
});
}
void _startAddNewTransaction(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
child: NewTransaction(_addTransaction),
behavior: HitTestBehavior.opaque,
);
},
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor:Theme.of(context).primaryColor,
title: Text('Expenso'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () => _startAddNewTransaction(context),
)
],
),
body:SingleChildScrollView(
child: Column(
//mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
width: double.infinity,
height: 50,
child: Card(
color: Colors.blue,
child: Text('chart')
)
),
TransactionList(_transactions)
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed:() => _startAddNewTransaction(context),
backgroundColor: Theme.of(context).accentColor,
),
)
);
}
}
transaction_list.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/transaction.dart';
class TransactionList extends StatelessWidget {
final List<Transaction> tractn;
TransactionList(this.tractn);
#override
Widget build(BuildContext context) {
return Container(
height: 500,
child :tractn.isEmpty? Column(
children: <Widget>[
Text('No Transaction added yet'),
SizedBox(
height: 20,
),
Container(
height:300,
child: Image.asset('assets/Images/waiting.png',
fit: BoxFit.cover,),
)
],
): ListView.builder(
itemBuilder: (ctx,index){
return Card(
child: Row(
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(
vertical: 10,
horizontal: 15,
),
decoration: BoxDecoration(border: Border.all(
color: Theme.of(context).primaryColor,
width: 2,
style: BorderStyle.solid
)
),
padding: EdgeInsets.all(10),
child: Text(
'₹ ${tractn[index].amount.toStringAsFixed(2)}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic ,
fontSize: 20,
color: Colors.green,
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
tractn[index].title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Theme.of(context).primaryColor
),
),
Text(
DateFormat().format(tractn[index].date) ,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.grey
),),
Text(
tractn[index].id
)
],
)
],
),
);
},
itemCount: tractn.length,
)
);
}
}
Please guide me the way to implement the theme in inherited classes
I Agree with #Nolence comment.
If you are expecting to achieve the below result
Remove MaterialApp widget and make Scaffold as your primate return widget inside
class _ExpensoState
Sample Code:
import 'package:flutter/material.dart';
import 'package:flutter_app/Transaction.dart';
import 'transaction_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expenso',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.amber
),
home: Expenso(),
);
}
}
class Expenso extends StatefulWidget {
#override
_ExpensoState createState() => _ExpensoState();
}
class _ExpensoState extends State<Expenso> {
final List<Transaction> _transactions=[
];
void _addTransaction(String txTitle,double txAmount)
{
final newTx=Transaction(
title: txTitle,
amount: txAmount,
id: DateTime.now().toString(),
date: DateTime.now()
);
setState(() {
_transactions.add(newTx);
});
}
void _startAddNewTransaction(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
child: NewTransaction(_addTransaction),
behavior: HitTestBehavior.opaque,
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor:Theme.of(context).primaryColor,
title: Text('Expenso'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () => _startAddNewTransaction(context),
)
],
),
body:SingleChildScrollView(
child: Column(
//mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
width: double.infinity,
height: 50,
child: Card(
color: Colors.blue,
child: Text('chart')
)
),
TransactionList(_transactions)
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed:() => _startAddNewTransaction(context),
backgroundColor: Theme.of(context).accentColor,
),
);
}
}
If this is not what you wanted, please elaborate your question or comment below.
In the build method of your Expenso widget you define another MaterialApp, which creates its own default theme (different to your first theme). Remove that MaterialApp widget and it should work.

How to keep AppBar and back arrow stationary when navigating to a new screen

I want to have the top half of by screen appear static when navigating between pages in Flutter.
To try to make this happen I put use the Hero widget and use it on a column that contains an AppBar and some other content that I want to appear static when pushing a new page.
The App Bar itself remains static but the back arrow disappears when the animation starts and reappears when the animation is done.
How can I have the back arrow remain visible the entire time while the rest of the page is animating into place?
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('First'),
backgroundColor: Color.fromARGB(255, 50, 64, 182),
),
Container(
height: 80.0,
)
],
),
),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('Second'),
),
Container(
height: 80.0,
// color: Colors.green,
),
],
),
),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
);
}
}
Things weren't quite set up right in your code. It should go Scaffold/Hero/your content. I've also used this simple fading page route when performing the navigation:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
leading: Icon(null),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MyCustomRoute(builder: (context) {
return SecondScreen();
}));
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second'),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
Navigator.pop(context);
},),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
You could do automaticallyImplyLeading: false and then do
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
I have it done this way, by adding automaticallyImplyLeading: true,
Hope this solves your problem!
appBar: AppBar(
automaticallyImplyLeading: true,
),

Flutter: BottomNavigationBar rebuilds Page on change of tab

I have a problem with my BottomNavigationBar in Flutter. I want to keep my page alive if I change the tabs.
here my implementation
BottomNavigation
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
List<Widget> _children;
final Key keyOne = PageStorageKey("IndexTabWidget");
#override
void initState() {
_children = [
IndexTabWidget(key: keyOne),
PlaceholderWidget(Colors.green),
NewsListWidget(),
ShopsTabWidget(),
PlaceholderWidget(Colors.blue),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(MyApp.appName),
textTheme: Theme.of(context).textTheme.apply(
bodyColor: Colors.black,
displayColor: Colors.blue,
),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
key: IHGApp.globalKey,
fixedColor: Colors.green,
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.perm_contact_calendar),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Container(height: 0.0),
),
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
Column buildButtonColumn(IconData icon) {
Color color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
],
);
}
}
This is my index page (first tab):
class IndexTabWidget extends StatefulWidget {
IndexTabWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new IndexTabState();
}
}
class IndexTabState extends State<IndexTabWidget>
with AutomaticKeepAliveClientMixin {
List<News> news = List();
FirestoreNewsRepo newsFirestore = FirestoreNewsRepo();
#override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: new Container(
child: new SingleChildScrollView(
child: new ConstrainedBox(
constraints: new BoxConstraints(),
child: new Column(
children: <Widget>[
HeaderWidget(
CachedNetworkImageProvider(
'https://static1.fashionbeans.com/wp-content/uploads/2018/04/50-barbershop-top-savill.jpg',
),
"",
),
AboutUsWidget(),
Padding(
padding: const EdgeInsets.all(16.0),
child: SectionTitleWidget(title: StringStorage.salonsTitle),
),
StreamBuilder(
stream: newsFirestore.observeNews(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
news = snapshot.data;
return Column(
children: <Widget>[
ShopItemWidget(
AssetImage('assets/images/picture.png'),
news[0].title,
news[0],
),
ShopItemWidget(
AssetImage('assets/images/picture1.png'),
news[1].title,
news[1],
)
],
);
}
},
),
Padding(
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 16.0),
child: SectionTitleWidget(title: StringStorage.galleryTitle),
),
GalleryCategoryCarouselWidget(),
],
),
),
),
),
);
}
#override
bool get wantKeepAlive => true;
}
So if I switch from my index tab to any other tab and back to the index tab, the index tab will always rebuild. I debugged it and saw that the build function is always being called on the tab switch.
Could you guys help me out with this issue?
Thank you a lot
Albo
None of the previous answers worked out for me.
The solution to keep the pages alive when switching the tabs is wrapping your Pages in an IndexedStack.
class Tabbar extends StatefulWidget {
Tabbar({this.screens});
static const Tag = "Tabbar";
final List<Widget> screens;
#override
State<StatefulWidget> createState() {
return _TabbarState();
}
}
class _TabbarState extends State<Tabbar> {
int _currentIndex = 0;
Widget currentScreen;
#override
Widget build(BuildContext context) {
var _l10n = PackedLocalizations.of(context);
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: widget.screens,
),
bottomNavigationBar: BottomNavigationBar(
fixedColor: Colors.black,
type: BottomNavigationBarType.fixed,
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.format_list_bulleted),
title: new Text(_l10n.tripsTitle),
),
BottomNavigationBarItem(
icon: new Icon(Icons.settings),
title: new Text(_l10n.settingsTitle),
)
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
You need to wrap every root page (the first page you see when you press a bottom navigation item) with a navigator and put them in a Stack.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final int _pageCount = 2;
int _pageIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: _body(),
bottomNavigationBar: _bottomNavigationBar(),
);
}
Widget _body() {
return Stack(
children: List<Widget>.generate(_pageCount, (int index) {
return IgnorePointer(
ignoring: index != _pageIndex,
child: Opacity(
opacity: _pageIndex == index ? 1.0 : 0.0,
child: Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(
builder: (_) => _page(index),
settings: settings,
);
},
),
),
);
}),
);
}
Widget _page(int index) {
switch (index) {
case 0:
return Page1();
case 1:
return Page2();
}
throw "Invalid index $index";
}
BottomNavigationBar _bottomNavigationBar() {
final theme = Theme.of(context);
return new BottomNavigationBar(
fixedColor: theme.accentColor,
currentIndex: _pageIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text("Page 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
title: Text("Page 2"),
),
],
onTap: (int index) {
setState(() {
_pageIndex = index;
});
},
);
}
}
The pages will be rebuild but you should separate your business logic from you UI anyway. I prefer to use the BLoC pattern but you can also use Redux, ScopedModel or InhertedWidget.
Just use an IndexedStack
IndexedStack(
index: selectedIndex,
children: <Widget> [
ProfileScreen(),
MapScreen(),
FriendsScreen()
],
)
I'm not sure but CupertinoTabBar would help.
If you don't want it, this video would be great url.
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final List<dynamic> pages = [
new Page1(),
new Page2(),
new Page3(),
new Page4(),
];
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async {
await Future<bool>.value(true);
},
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
iconSize: 35.0,
onTap: (index) {
setState(() => currentIndex = index);
},
activeColor: currentIndex == 0 ? Colors.white : Colors.black,
inactiveColor: currentIndex == 0 ? Colors.green : Colors.grey,
backgroundColor: currentIndex == 0 ? Colors.black : Colors.white,
currentIndex: currentIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_4),
title: Text(''),
),
],
),
tabBuilder: (BuildContext context, int index) {
return new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: new CupertinoTabView(
routes: <String, WidgetBuilder>{
'/Page1': (BuildContext context) => new Page1(),
'/Page2': (BuildContext context) => new Page2(),
'/Page3': (BuildContext context) => new Page3(),
'/Page4': (BuildContext context) => new Page4(),
},
builder: (BuildContext context) {
return pages[currentIndex];
},
),
);
},
),
);
}
}
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
String title;
#override
void initState() {
title = 'Page1';
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
leading: new IconButton(
icon: new Icon(Icons.text_fields),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
},
)),
body: new Center(
child: new Text(title),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page2'),
leading: new IconButton(
icon: new Icon(Icons.airline_seat_flat_angled),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page12()));
},
)),
body: new Center(
child: Column(
children: <Widget>[
CupertinoSlider(
value: 25.0,
min: 0.0,
max: 100.0,
onChanged: (double value) {
print(value);
}
),
],
),
),
);
}
}
class Page3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page3'),
),
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: new Text('Cupertino'),
textColor: Colors.white,
color: Colors.red,
onPressed: () {
List<int> list = List.generate(10, (int i) => i + 1);
list.shuffle();
var subList = (list.sublist(0, 5));
print(subList);
subList.forEach((li) => list.remove(li));
print(list);
}
),
new SizedBox(height: 30.0),
new RaisedButton(
child: new Text('Android'),
textColor: Colors.white,
color: Colors.lightBlue,
onPressed: () {
var mes = 'message';
var messa = 'メッセージ';
var input = 'You have a new message';
if (input.contains(messa) || input.contains(mes)) {
print('object');
} else {
print('none');
}
}
),
],
),
),
);
}
}
class Page4 extends StatelessWidget {
static List<int> ints = [1, 2, 3, 4, 5];
static _abc() {
print(ints.last);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page4'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Static', style: new TextStyle(color: Colors.white)),
color: Colors.lightBlue,
onPressed: _abc,
)),
);
}
}
class Page12 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page12'),
actions: <Widget>[
new FlatButton(
child: new Text('GO'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
},
)
],
),
body: new Center(
child: new RaisedButton(
child: new Text('Swiper', style: new TextStyle(color: Colors.white)),
color: Colors.redAccent,
onPressed: () {},
)),
);
}
}
class Page13 extends StatefulWidget {
#override
_Page13State createState() => _Page13State();
}
class _Page13State extends State<Page13> with SingleTickerProviderStateMixin {
final List<String> _productLists = Platform.isAndroid
? [
'android.test.purchased',
'point_1000',
'5000_point',
'android.test.canceled',
]
: ['com.cooni.point1000', 'com.cooni.point5000'];
String _platformVersion = 'Unknown';
List<IAPItem> _items = [];
List<PurchasedItem> _purchases = [];
#override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await FlutterInappPurchase.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
var result = await FlutterInappPurchase.initConnection;
print('result: $result');
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
// refresh items for android
String msg = await FlutterInappPurchase.consumeAllItems;
print('consumeAllItems: $msg');
}
Future<Null> _buyProduct(IAPItem item) async {
try {
PurchasedItem purchased = await FlutterInappPurchase.buyProduct(item.productId);
print('purchased: ${purchased.toString()}');
} catch (error) {
print('$error');
}
}
Future<Null> _getProduct() async {
List<IAPItem> items = await FlutterInappPurchase.getProducts(_productLists);
print(items);
for (var item in items) {
print('${item.toString()}');
this._items.add(item);
}
setState(() {
this._items = items;
this._purchases = [];
});
}
Future<Null> _getPurchases() async {
List<PurchasedItem> items = await FlutterInappPurchase.getAvailablePurchases();
for (var item in items) {
print('${item.toString()}');
this._purchases.add(item);
}
setState(() {
this._items = [];
this._purchases = items;
});
}
Future<Null> _getPurchaseHistory() async {
List<PurchasedItem> items = await FlutterInappPurchase.getPurchaseHistory();
for (var item in items) {
print('${item.toString()}');
this._purchases.add(item);
}
setState(() {
this._items = [];
this._purchases = items;
});
}
List<Widget> _renderInApps() {
List<Widget> widgets = this
._items
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
FlatButton(
color: Colors.orange,
onPressed: () {
print("---------- Buy Item Button Pressed");
this._buyProduct(item);
},
child: Row(
children: <Widget>[
Expanded(
child: Container(
height: 48.0,
alignment: Alignment(-1.0, 0.0),
child: Text('Buy Item'),
),
),
],
),
),
],
),
),
))
.toList();
return widgets;
}
List<Widget> _renderPurchases() {
List<Widget> widgets = this
._purchases
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
)
],
),
),
))
.toList();
return widgets;
}
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width-20;
double buttonWidth=(screenWidth/3)-20;
return new Scaffold(
appBar: new AppBar(),
body: Container(
padding: EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'Running on: $_platformVersion\n',
style: TextStyle(fontSize: 18.0),
),
),
Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async {
print("---------- Connect Billing Button Pressed");
await FlutterInappPurchase.initConnection;
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Connect Billing',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async {
print("---------- End Connection Button Pressed");
await FlutterInappPurchase.endConnection;
setState(() {
this._items = [];
this._purchases = [];
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'End Connection',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print("---------- Get Items Button Pressed");
this._getProduct();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Items',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print(
"---------- Get Purchases Button Pressed");
this._getPurchases();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchases',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print(
"---------- Get Purchase History Button Pressed");
this._getPurchaseHistory();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchase History',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
]),
],
),
Column(
children: this._renderInApps(),
),
Column(
children: this._renderPurchases(),
),
],
),
],
),
),
);
}
}
Use IndexedStack widget:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: IndexedStack(
index: _currentIndex,
children: const [
HomePage(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build home');
return Center(child: Text('Home'));
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build settings');
return Center(child: Text('Settings'));
}
}
Make sure the IndexedStack children widget list is constant. This will prevent the widgets from rebuilding when setState() is called.
IndexedStack(
index: _currentIndex,
children: const [
HomeWidget(),
SettingsWidget(),
],
),
The problem with IndexedStack is that all the widgets will be built at the same time when IndexedStack is initialized. For small widgets (like the example above), it won't be a problem. But for big widgets, you may see some performance issues.
Consider using the lazy_load_indexed_stack package. According to the package:
[LazyLoadIndexedStack widget] builds the required widget only when it is needed, and returns the pre-built widget when it is needed again
Again, make sure the LazyLoadIndexedStack children widgets are constant, otherwise they will keep rebuilding when setState is called.
If, you just need to remember the scroll position inside a list, the best option is to simply use a PageStoreKey object for the key property:
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
key: PageStorageKey<String>('some-list-key'),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => _onElementTapped(index),
child: makeCard(items[index])
);
},
),
);
}
According to https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html, this should work on ANY scrollable widget.
if i use the IndexedStack in the body it is loading only the main screen content not every other screen which is present in the bottom nativation bar.
Using IndexedStack with bloc pattern solved everthing.