I am a beginner with flutter and dart. I have been trying to implement a navigationBar on three different pages in my app. The toggling works well for an individual page but I have problems persisting the active and inactive tabs state on all the pages. It seems like when it navigates to another page, I lose the active state too the tabs. This is my code.
AppFooter.dart
import 'package:flutter/material.dart';
class AppFooter extends StatefulWidget {
#override
_AppFooterState createState() => _AppFooterState();
}
class _AppFooterState extends State<AppFooter> {
int index = 0;
#override
Widget build(BuildContext context) {
return new Theme(
data: Theme.of(context).copyWith(
// sets the background color of the `BottomNavigationBar`
canvasColor: Colors.white,
// sets the active color of the `BottomNavigationBar` if `Brightness` is light
primaryColor: Colors.green,
textTheme: Theme.of(context)
.textTheme
.copyWith(caption: new TextStyle(color: Colors.grey))),
child: new BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: index,
onTap: (int index) {
setState(() {
this.index = index;
});
switch (index){
case 0: Navigator.of(context).pushNamed('/dashboard');
break;
case 1: Navigator.of(context).pushNamed('/medical centre');
break;
case 2: Navigator.of(context).pushNamed('/history');
break;
}
},
items: [
new BottomNavigationBarItem(
backgroundColor: Colors.white,
icon: index==0?new Image.asset('assets/images/dashboard_active.png'):new Image.asset('assets/images/dashboard_inactive.png'),
title: new Text('Dashboard', style: new TextStyle(fontSize: 12.0))),
new BottomNavigationBarItem(
backgroundColor: Colors.white,
icon: index==1?new Image.asset('assets/images/medical_sevice_active.png'):new Image.asset('assets/images/medical_sevice_inactive.png'),
title: new Text('Health Services', style: new TextStyle(fontSize: 12.0))),
new BottomNavigationBarItem(
icon: InkWell(
child: Icon(
Icons.format_align_left,
// color: green,
size: 20.0,
),
),
title: new Text('History', style: new TextStyle(fontSize: 12.0))),
]),
);
}
}
If I understand your question correctly, you need the bottom navigation bar persisted on all three pages. There is a well-written article on how to achieve it. You can find the details here.
https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf
https://github.com/bizz84/nested-navigation-demo-flutter
All credits go to the original author.
Use PageView and bottomNavigationBar:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter App';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: App(),
);
}
}
class App extends StatefulWidget {
App({Key key}) : super(key: key);
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
PageController _myPage;
var selectedPage;
#override
void initState() {
super.initState();
_myPage = PageController(initialPage: 1);
selectedPage = 1;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
physics: NeverScrollableScrollPhysics(),
controller: _myPage,
children: <Widget>[
Center(
child: Text("Another Page"),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Page 1"),
RaisedButton(
onPressed: () {
_myPage.jumpToPage(0);
setState(() {
selectedPage = 0;
});
},
child: Text("Go to another page"),
)
],
)),
Center(child: Text("Page 2")),
Center(child: Text("Page 3")),
],
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
icon: Icon(Icons.home),
color: selectedPage == 1 ? Colors.blue : Colors.grey,
onPressed: () {
_myPage.jumpToPage(1);
setState(() {
selectedPage = 1;
});
},
),
IconButton(
icon: Icon(Icons.star),
color: selectedPage == 2 ? Colors.blue : Colors.grey,
onPressed: () {
_myPage.jumpToPage(2);
setState(() {
selectedPage = 2;
});
},
),
IconButton(
icon: Icon(
Icons.settings,
),
color: selectedPage == 3 ? Colors.blue : Colors.grey,
onPressed: () {
_myPage.jumpToPage(3);
setState(() {
selectedPage = 3;
});
},
),
],
),
));
}
}
In addition, if you want preserve the state between pages such that going to another page won't cause the previous page to lose its state, use AutomaticKeepAliveClientMixin
Also, to lazily load the pages, PageView.builder is another solution.
Hope it helps.
Another great solution is the persistent_bottom_nav_bar package provided by Bilal Shahid.
It is easy to use and offers you a bunch of features:
Highly customizable persistent bottom navigation bar.
Ability to push new screens with or without bottom navigation bar.
20 styles for the bottom navigation bar.
Includes functions for pushing screen with or without the bottom navigation bar i.e. pushNewScreen() and pushNewScreenWithRouteSettings().
Based on flutter's Cupertino(iOS) bottom navigation bar.
Can be translucent for a particular tab.
Custom styling for the navigation bar. Click here for more information.
Handles hardware/software Android back button.
Before I found this package I followed the solution from the article #Abin mentioned in his answer. But I ran into the problem, that all screens from the navbar beeing loaded on first load of the navbar which is not that perfomant. I did not mangaed to solve this, but luckily Bilal Shahid provide a good solution with his package.
All credits to him.
Just copy & past :)
main.dart:
void main() async{
runApp(MyGrillApp());
}
class MyGrillApp extends StatelessWidget {
const MyGrillApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/mainlayout': (context) => MainLayout(),
'/page1': (context) => Page1(),
'/page2': (context) => Page2(),
'/page3': (context) => Page3(),
'/page4': (context) => Page4(),
},
initialRoute: '/mainlayout',
);
}
}
main_layout.dart:
class MainLayout extends StatefulWidget {
#override
_MainLayoutState createState() => _MainLayoutState();
}
class _MainLayoutState extends State<MainLayout> {
int _currentIndex = 0;
final _page1 = GlobalKey<NavigatorState>();
final _page2 = GlobalKey<NavigatorState>();
final _page3 = GlobalKey<NavigatorState>();
final _page4 = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterDocked,
floatingActionButton: Padding(
padding: const EdgeInsets.all(6.0),
child: FloatingActionButton(
backgroundColor: Colors.redAccent,
child: const Icon(Icons.add, color: Colors.white),
onPressed: () {
// ToDo...
},
),
),
body: IndexedStack(
index: _currentIndex,
children: <Widget>[
Navigator(
key: _page1,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => Page1(),
),
),
Navigator(
key: _page2,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => Page2(),
),
),
Navigator(
key: _page3,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => Page3(),
),
),
Navigator(
key: _page4,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => Page4(),
),
),
],
),
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
clipBehavior: Clip.antiAlias,
child: BottomNavigationBar(
backgroundColor: Colors.white,
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.redAccent,
unselectedItemColor: Colors.grey,
showSelectedLabels: false,
showUnselectedLabels: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.date_range), label: 'Statistics'),
BottomNavigationBarItem(icon: Icon(Icons.wallet_giftcard), label: 'Wallet'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}
}
Details screen:
class ItemDetailsPage extends StatefulWidget {
const ItemDetailsPage({Key? key}) : super(key: key);
#override
_ItemDetailsPageState createState() => _ItemDetailsPageState();
}
class _ItemDetailsPageState extends State<ItemDetailsPage> with AutomaticKeepAliveClientMixin{
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
backgroundColor: themeColorPrimary,
title: Text('Item details',),
),
body : Container(child: Text('Hello from details'),));
}
#override
bool get wantKeepAlive => true;
}
A note about routing in my solution:
If you encounter trouble when you routing by:
Navigator.pushNamed(context, '/page3');
or by:
Navigator.of(context).pushNamed(Page3());
You can fix it using MaterialPageRoute:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Page3(),
),
);
You can use IndexedStack to persist State when you touch/change the page
Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
//Permet de garder le state des vues même quand on change de vue
index: _currentIndex,
children: _children,
),
),
bottomNavigationBar: BottomNavigationBar( items: [ ] ),
);
I highly recommend using stack. This gives you pretty much total control over how and when you would like to show bottom app bar.
Make list of all pages you want to show using your botttomAppBar. Let's say has three icons.
final List<Widget> pages=[FirstScreen(),SecondScreen(),ThirdScreen()];
In the Build Method
Scaffold(
child: Stack(
children: <Widget>[
Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => pages[cur_ind],
);
},
),
],
bottomNavigationBar: BottomNavigationBar(
onTap: (int index){
setState(() {
cur_ind=index;
});
},
currentIndex: cur_ind,
fixedColor: Colors.green, //let's say
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Messages'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
),
),
),
where cur_ind is the variable used to control which page to show. And since the body is stacked, the Bottom Navigation Bar will be persistent always.
I created a small, super easy to use package that let you do that effect CustomNavigator.
And wrote a tutorial about it on Medium you can find it here.
So it goes like this
// Here's the custom scaffold widget
// It takes a normal scaffold with mandatory bottom navigation bar
// and children who are your pages
CustomScaffold(
scaffold: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: _items,
),
),
// Children are the pages that will be shown by every click
// They should placed in order such as
// `page 0` will be presented when `item 0` in the [BottomNavigationBar] clicked.
children: <Widget>[
Page('0'),
Page('1'),
Page('2'),
],
// Called when one of the [items] is tapped.
onItemTap: (index) {},
);
The cool thing about this library that it works efficiently. It creates a nested navigator (which is very unpleasant to do) and uses it for navigation in your widget tree.
And of course you can always use the default navigator from MaterialApp
If you are looking for a solution that performs well (that doesn't build the tabs/pages unnecessarily) even using IndexedStack take a look at my answer here
For anyone looking for this in the future auto_route handle this pretty much well with very little boilerplate using AutoTabsScaffold.
Widget build(context) {
return AutoTabsScaffold(
routes: const [
BooksRouter(),
AccountRouter(),
],
bottomNavigationBuilder: (_, tabsRouter) {
return BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.book),
label: 'Books',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box),
label: 'Account',
),
],
);
},
);
}
I am working on a beta version of an express_app plugin, which achieve the required result.
Two days ago, I implemented an addition where you can set an ExpressHome and it can be any part of your tree, in addition to setting your routes of course. When changing the routes, everything under ExpressHome will change only and the rest will stay the same (i.e. you can have a permanent bar easily.
I will publish a more-recent version this evening, and if you would like a specific demo about your use case, let me know.
i had this issue too...after days of research i came across this package
persistent_bottom_nav_bar: ^4.0.0
it quite easy to implement.
You can use a scaffold widget to contain the whole screen then put IndexedStack widget as a Body option then use at the bottom navigation option in the scaffold widget you favorite implementation of the bottom navigation bar
Scaffold(
// here is the IndexedStack as body
body: IndexedStack(
index: this._bottomNavIndex,
children: [MangaGridView(), FavoriteManga()]),
backgroundColor: Colors.black,
bottomNavigationBar: AnimatedBottomNavigationBar(
icons: [
Icons.home_outlined,
Icons.favorite_border,
Icons.settings,
],
inactiveColor: Colors.black,
activeIndex: this._bottomNavIndex,
gapLocation: GapLocation.none,
activeColor: Theme.of(context).primaryColor,
notchSmoothness: NotchSmoothness.verySmoothEdge,
leftCornerRadius: 32,
rightCornerRadius: 32,
onTap: (index) => setState(() => this._bottomNavIndex = index),
height: 70,
splashColor: Theme.of(context).primaryColor,
splashRadius: 40.0,
splashSpeedInMilliseconds: 400,
iconSize: 34,
),
);
Navigator.of(context).pushNamed(); is for Navigation with page transition. So, in this situation, the method is not match.
You can use BottomNavigationBar with Scaffold.
example code:
class AppFooter extends StatefulWidget {
#override
_AppFooterState createState() => _AppFooterState();
}
class _AppFooterState extends State<AppFooter> {
int _currentIndex = 0;
List<Widget> _pages = [
Text("page1"),
Text("page2"),
Text("page3"),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
items: [
new BottomNavigationBarItem(
backgroundColor: Colors.white,
icon: _currentIndex == 0
? new Image.asset('assets/images/dashboard_active.png')
: new Image.asset('assets/images/dashboard_inactive.png'),
title:
new Text('Dashboard', style: new TextStyle(fontSize: 12.0))),
new BottomNavigationBarItem(
backgroundColor: Colors.white,
icon: _currentIndex == 1
? new Image.asset('assets/images/medical_sevice_active.png')
: new Image.asset(
'assets/images/medical_sevice_inactive.png'),
title: new Text('Health Services',
style: new TextStyle(fontSize: 12.0))),
new BottomNavigationBarItem(
icon: InkWell(
child: Icon(
Icons.format_align_left,
// color: green,
size: 20.0,
),
),
title: new Text('History', style: new TextStyle(fontSize: 12.0))),
],
),
);
}
}
Just make your index variable static
like:
static int index = 0;
I have the following code:
return new ListTile(
leading: new CircleAvatar(
backgroundColor: Colors.blue,
child: new Image.network(
"http://res.cloudinary.com/kennyy/image/upload/v1531317427/avatar_z1rc6f.png")),
title: new Row(
children: <Widget>[
new Expanded(child: new Text(message)),
new FlatButton(onPressed: null, child: new Text("Delete"))
]
),
subtitle: new Text(from),
);
which created a tile in my ListView.
Each tile should have a button.
When I tap the button - I don't see any ripple effect or animation that I actually clicked. I was pretty sure it's a part of the Material theme as a gesture for FlatButtons, but I tried a solution I found, using InkWell:
return new ListTile(
leading: new CircleAvatar(
backgroundColor: Colors.blue,
child: new Image.network(
"http://res.cloudinary.com/kennyy/image/upload/v1531317427/avatar_z1rc6f.png")),
title: new Row(
children: <Widget>[
new Expanded(child: new Text(message)),
InkWell(
child: FlatButton(onPressed: null, child: new Text("Delete"), color: Colors.amber))
]
),
subtitle: new Text(from),
);
But still - my listview button does not have any ripple effect when tapped.
Any idea?
Flutter buttons are disabled by default. To enable a button, set its onPressed or onLongPress properties to a non-null value.
The ripple is not visible because you've set the property of your onPressed to null.
new FlatButton(onPressed: null, child: new Text("Delete"))
Change the onPressed property to something like this:
new FlatButton(onPressed: (){}, child: new Text("Delete"))
To compare here is an example of a no-null onPressed and a null valued onPressed.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListTile(
leading: new CircleAvatar(
backgroundColor: Colors.blue,
child: new Image.network(
"http://res.cloudinary.com/kennyy/image/upload/v1531317427/avatar_z1rc6f.png")),
title: new Row(children: <Widget>[
//new Expanded(child: new Text(message)),
new TextButton(
onPressed: () {},
child: new Text("Delete"),
),
new TextButton(
onPressed: null,
child: new Text("Delete"),
)
]),
//subtitle: new Text(from),
),
);
}
}
*Note that the FlatButton is now deprecated so I've used TextButton in the example.
hello guys i want to create an onTap option for my icon, my code is like this and I cant figure out how to do so can you help me.this is my code:
trailing: new Column(
children: <Widget>[
new Container(
child: new Icon(Icons.bookmark),
margin: EdgeInsets.only(top: 25.0),
)
],
),
Use IconButton instead.
new IconButton(
icon: new Icon(Icons.bookmark),
onPressed: () { /* Your code */ },
)
In your code, you can use like this
trailing: new Column(
children: <Widget>[
new Container(
child: new IconButton(
icon: new Icon(Icons.bookmark),
onPressed: () { /* Your code */ },
),
margin: EdgeInsets.only(top: 25.0),
)
],
),
Create the button and Wrap it in a GestureDetector with an onTap callback
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Gesture Demo';
return MaterialApp(
title: title,
home: MyHomePage(title: title),
);
}
}
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: MyButton()),
);
}
}
class MyButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Our GestureDetector wraps our button
return GestureDetector(
// When the child is tapped, show a snackbar
onTap: () {
final snackBar = SnackBar(content: Text("Tap"));
Scaffold.of(context).showSnackBar(snackBar);
},
// Our Custom Button!
child: Container(
padding: EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Theme.of(context).buttonColor,
borderRadius: BorderRadius.circular(8.0),
),
child: Text('My Button'),
),
);
}
}
Important : for user interactivity, you can use onPressed property.
I'm new to Flutter,
I want to destruct cards created initially and construct them again as per data provided in API call.
Basically when I tap on button in UI, it should call APIs and based on data from API call, if it is different from the data I already have, I want to destruct cards and construct them again.
How I can achieve this?
The cards will auto update their content when you make the call again, it is like refreshing your data.
I have made a simple example with a single card that shows data from this JSON Where I am calling the API first time in initState and then repeating the call each time I press on the FAB.
I am adding the index variable just to show you the updates (updating my single card with the next item in the list)
Also it is worth noting that I am handling the null or empty values poorly for the sake of time.
Also forget about the UI overflow ¯_(ツ)_/¯
class CardListExample extends StatefulWidget {
#override
_CardListExampleState createState() => new _CardListExampleState();
}
class _CardListExampleState extends State<CardListExample> {
Map cardList = {};
int index = 0;
#override
void initState() {
_getRequests();
super.initState();
}
_getRequests() async {
String url = "https://jsonplaceholder.typicode.com/users";
var httpClinet = createHttpClient();
var response = await httpClinet.get(
url,
);
var data = JSON.decode(response.body);
//print (data);
setState(() {
this.cardList = data[index];
this.index++;
});
print(cardList);
print(cardList["name"]);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton:
new FloatingActionButton(onPressed: () => _getRequests()),
appBar: new AppBar(
title: new Text("Card List Example"),
),
body: this.cardList != {}
? new ListView(children: <Widget>[
new Card(
child: new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Text(
cardList["name"] ?? '',
style: Theme.of(context).textTheme.display1,
),
new Text(
this.cardList['email'] ?? '',
maxLines: 50,
),
],
),
new Text(cardList["website"] ?? '')
],
),
),
])
: new Center(child: new CircularProgressIndicator()),
);
}
}
Yes, Answer from Aziza works.
Though I used the code as below :
void main() =>
runApp(new MaterialApp(
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/about':
return new FromRightToLeft(
builder: (_) => new _aboutPage.About(),
settings: settings,
);
}
},
home : new HomePage(),
theme: new ThemeData(
fontFamily: 'Poppins',
primarySwatch: Colors.blue,
),
));
class HomePage extends StatefulWidget{
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage>{
List data;
Future<String> getData() async{
var response = await http.get(
Uri.encodeFull(<SOMEURL>),
headers: {
"Accept" : "application/json"
}
);
this.setState((){
data = JSON.decode(response.body);
});
return "Success";
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar : new AppBar(
title : new Text("ABC API"),
actions: <Widget>[
new IconButton( // action button
icon: new Icon(Icons.cached),
onPressed: () => getData(),
)],
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new Container(
height: 120.0,
child: new DrawerHeader(
padding: new EdgeInsets.all(0.0),
decoration: new BoxDecoration(
color: new Color(0xFFECEFF1),
),
child: new Center(
child: new FlutterLogo(
colors: Colors.blueGrey,
size: 54.0,
),
),
),
),
new ListTile(
leading: new Icon(Icons.chat),
title: new Text('Support'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/support');
}
),
new ListTile(
leading: new Icon(Icons.info),
title: new Text('About'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/about');
}
),
new Divider(),
new ListTile(
leading: new Icon(Icons.exit_to_app),
title: new Text('Sign Out'),
onTap: () {
Navigator.pop(context);
}
),
],
)
),
body: this.data != null ?
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index){
return new Container(
padding: new EdgeInsets.fromLTRB(8.0,5.0,8.0,0.0),
child: new Card(
child: new Padding(
padding: new EdgeInsets.fromLTRB(10.0,12.0,8.0,0.0),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
enabled: data[index]['active'] == '1' ? true : false,
title: new Text(data[index]['header'],
style:Theme.of(context).textTheme.headline,
),
subtitle: new Text("\n" + data[index]['description']),
),
new ButtonTheme.bar(
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: new Text(data[index]['action1']),
onPressed: data[index]['active'] == '1' ? _launchURL :null,
),
],
),
),
],
),
),
),
);
},
)
:new Center(child: new CircularProgressIndicator()),
);
}
}
_launchURL() async {
const url = 'http://archive.org';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
class FromRightToLeft<T> extends MaterialPageRoute<T> {
FromRightToLeft({ 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;
return new SlideTransition(
child: new Container(
decoration: new BoxDecoration(
boxShadow: [
new BoxShadow(
color: Colors.black26,
blurRadius: 25.0,
)
]
),
child: child,
),
position: new Tween(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
)
.animate(
new CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
)
),
);
}
#override Duration get transitionDuration => const Duration(milliseconds: 400);
}
The above code includes Navigation drawer, page navigation animation and also answer to the above question.
I'm trying to create a SimpleDialog that allows the user to enter their name. But when it is displayed the dialog is half hidden by the on-screen keyboard:
How can I get the Dialog to be fully visible?
Edit: I find it strange that the homepage widget (FocusVisibilityDemo) recognises the reduced height and therefore adjusts the position of the 'Push Me' button to remain in the center. Unfortunately the dialog doesn't behave the same way.
Here is my code:
import 'package:flutter/material.dart';
class FocusVisibilityDemo extends StatefulWidget {
#override
_FocusVisibilityDemoState createState() => new _FocusVisibilityDemoState();
}
class _FocusVisibilityDemoState extends State<FocusVisibilityDemo> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Text Dialog Demo')),
body: new Center(
child: new RaisedButton(
onPressed: _showDialog,
child: new Text("Push Me"),
),
),
);
}
_showDialog() async {
await showDialog<String>(
context: context,
child: new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
autofocus: true,
decoration: new InputDecoration(
labelText: 'Full Name', hintText: 'eg. John Smith'),
),
)
],
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: const Text('OPEN'),
onPressed: () {
Navigator.pop(context);
})
],
),
);
}
}
void main() {
runApp(new MaterialApp(home: new FocusVisibilityDemo()));
}
If your use case is to add multiple TextFields inside your Dialog so your main Form does not get crowded, I think it is better if you build something more customizable than AlertDialog and SimpleDialog as they are used for simple activities (confirmations, radios..etc).
Otherwise, why do you want to use a Dialog for a single TextField ?
When we add multiple TextFields we should be careful about our design choices since other people will interact with this view to fill in the data, in this case I prefer to use fullscreenDialog property of PageRoute class. I am not sure if SimpleDialog can be suitable for that in Flutter.
Here is a quick example on how to use a FullScreenDialog, I hope this help and you should be able to modify it the way you want:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new MyApp(),));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
FullScreenDialog _myDialog = new FullScreenDialog();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Fill this form"),
),
body: new Column(
children: <Widget>[
new TextField(controller: new TextEditingController(
text: "Add a single text field"),),
new Card(child: new ListTile(
title: new Text("Click to add your top 3 amazing skills"),
subtitle: new Text(
"${_myDialog._skillOne} ${_myDialog._skillTwo} ${_myDialog
._skillThree}"),
onTap: () {
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => _myDialog,
fullscreenDialog: true,
));
},
),
),
],
)
);
}
}
class FullScreenDialog extends StatefulWidget {
String _skillOne = "You have";
String _skillTwo = "not Added";
String _skillThree = "any skills yet";
#override
FullScreenDialogState createState() => new FullScreenDialogState();
}
class FullScreenDialogState extends State<FullScreenDialog> {
TextEditingController _skillOneController = new TextEditingController();
TextEditingController _skillTwoController = new TextEditingController();
TextEditingController _skillThreeController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Add your top 3 skills"),
),
body: new Padding(child: new ListView(
children: <Widget>[
new TextField(controller: _skillOneController,),
new TextField(controller: _skillTwoController,),
new TextField(controller: _skillThreeController,),
new Row(
children: <Widget>[
new Expanded(child: new RaisedButton(onPressed: () {
widget._skillThree = _skillThreeController.text;
widget._skillTwo = _skillTwoController.text;
widget._skillOne = _skillOneController.text;
Navigator.pop(context);
}, child: new Text("Save"),))
],
)
],
), padding: const EdgeInsets.symmetric(horizontal: 20.0),)
);
}
}
EDIT
After doing some research, it seems that this is a bug in the current Flutter version, the temporary fix is also documented in this issue.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new FocusVisibilityDemo()));
}
class FocusVisibilityDemo extends StatefulWidget {
#override
_FocusVisibilityDemoState createState() => new _FocusVisibilityDemoState();
}
class _FocusVisibilityDemoState extends State<FocusVisibilityDemo> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Text Dialog Demo')),
body: new Center(
child: new RaisedButton(
onPressed: _showDialog,
child: new Text("Push Me"),
),
),
);
}
_showDialog() async {
await showDialog<String>(
context: context,
child: new _SystemPadding(child: new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
autofocus: true,
decoration: new InputDecoration(
labelText: 'Full Name', hintText: 'eg. John Smith'),
),
)
],
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: const Text('OPEN'),
onPressed: () {
Navigator.pop(context);
})
],
),),
);
}
}
class _SystemPadding extends StatelessWidget {
final Widget child;
_SystemPadding({Key key, this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return new AnimatedContainer(
padding: mediaQuery.viewInsets,
duration: const Duration(milliseconds: 300),
child: child);
}
}