I have a menu page which has tabA and tabB. The appBar has a search button.
menu.dart
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Item List'),
actions: [
_currentIndex == 0
? Row(
children: [
IconButton(
onPressed: () {
Navigator.pushNamed(context, SearchScreen.ROUTE,
arguments: list);
},
icon: Icon(
Icons.search,
color: Colors.white,
)),
IconButton(
icon: Icon(
Icons.tune,
color: Colors.white,
),
onPressed: () {
...
}
}
});
},
)
],
)
: Container()
],
bottom: TabBar(
unselectedLabelColor: Colors.white,
labelColor: Colors.white,
isScrollable: false,
tabs: <Widget>[
Tab(
text: "Tab A",
),
Tab(
text: "Tab B",
),
],
controller: _tabController,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
),
bottomOpacity: 1,
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
ShowTabA(),
ShowTabB()
],
),
);
}
}
In tabA, it has a listView. When I tap on one of the listview item, it will navigate to EditPage. When return from EditPage, I will refresh the data in tabA. All the list item will be updated.
tabA.page
initState(){
_bloc.getItemList();
}
Navigator.pushNamed(EditPage.ROUTE,context).then((onValue){
_bloc.getItemList();
});
My problem here:
When I click the search icon in menu page , it will navigate to SearchScreen page.Once user click the list item in SearchScreen, it will navigate to EditPage.
When back from EditPage, I want the list in TabA page refreshed but it can't because I navigate using pushReplacementNamed in menu page . How to achieve that ?
SearchScreen.dart
Navigator.pushReplacementNamed(context);
Updated answer on Modified Question:
Since you are using pushReplacement your await won't work. You can use the following approach to make it work:
First you have to define your routes:
routes: <String, WidgetBuilder> {
'/': (BuildContext context) => // Your MainPage,
'/menu': (BuildContext context) => MenuPage(),
'/search': (BuildContext context) => SearchPage(),
'/edit': (BuildContext context) => EditPage(),
}
Navigation from Menu to Search:
void goToSearch() async {
await Navigator.pushNamed(context, "/search", arguments: list);
_bloc.getItemList().then((onValue){
setState(() {
list = onValue;
});
});
}
Use pushNamed instead of pushReplacementNamed when navigating from Search to Edit:
Navigator.of(context).pushNamed("/edit");
Important step When you have to go back to Menu from Edit, you've to use popUntil method:
Navigator.popUntil(context, ModalRoute.withName('/menu'));
Related
I have made a flutter project in which I am facing issue with two pages.
Plugin used for two pages- https://pub.dev/packages/flutter_inappwebview
One Page that is "Single App page" render a Webpage & WillPopScope is working here absolutely fine.
Another Page that is "Compare App Page" render a different Webpages into different tabs & here WillPopScope is only working for 1st tab and not working for the rest of tab.
I want to implement WillPopScope for each tab so that each tab have its own history and when a person present on a particular tab & hitting back button(I want to do this inbuilt back button & not via a created back button) takes him to back in history.
Note- A common widget is used in both Single & Compare App as children.
Below are the Main code
class NewCompareApp extends StatefulWidget {
#override
_NewCompareAppState createState() => _NewCompareAppState();
}
class _NewCompareAppState extends State<NewCompareApp> {
List apps;
#override
void initState() {
super.initState();
apps = getCompareApps();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: apps.length,
child: Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Card(
elevation: 10,
child: Container(
height: 35,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
padding: EdgeInsets.only(left: 5),
child: TextField(
autofocus: false,
cursorColor: Colors.grey,
decoration: InputDecoration(
hintText: 'Search', border: InputBorder.none),
),
),
),
bottom: TabBar(
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
text: ca.name,
))
.toList(),
),
),
resizeToAvoidBottomInset: false,
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: apps
.map((ca) => WebApp(
url: ca.url,
forWidget: 'cmp',
))
.toList(),
)),
);
}
}
class WebApp extends StatefulWidget {
final String url;
final String forWidget;
WebApp({Key key, #required this.url, #required this.forWidget})
: super(key: key);
#override
_WebAppState createState() => _WebAppState();
}
class _WebAppState extends State<WebApp>
with AutomaticKeepAliveClientMixin<WebApp> {
#override
bool get wantKeepAlive => true;
var currentUrl = '';
InAppWebViewController controller;
Future<bool> _handleBack(context) async {
var status = await controller.canGoBack();
if (status) {
controller.goBack();
} else {
getExitDialog(context, extra: {
"in_app": true,
});
}
return false;
}
#override
Widget build(BuildContext context) {
Widget mainWidget = Column(
children: <Widget>[
Expanded(
child: WillPopScope(
onWillPop: () => _handleBack(context),
child: InAppWebView(
initialUrl: widget.url,
onWebViewCreated: (InAppWebViewController webViewController) {
controller = webViewController;
},
onLoadStart: (InAppWebViewController controller, String url) {
this.currentUrl = url;
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
horizontalScrollBarEnabled: false,
verticalScrollBarEnabled: false),
),
),
),
),
Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_back),
onPressed: () => _handleBack(context),
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.refresh),
onPressed: () {
if (controller != null) {
controller.reload();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_forward),
onPressed: () {
if (controller != null) {
controller.goForward();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.share),
onPressed: null,
),
],
),
),
],
);
return widget.forWidget == 'single_app'
? Scaffold(body: SafeArea(top: true, child: mainWidget))
: mainWidget;
}
}
Code in detail- https://gist.github.com/ycv005/13dec1df2b57535271eb346e132c6775
Thanks in advance.
After struggling I found below solution where a global controller that keep changing on tab change and plus have a local controller as well to handle other stuff.
In bottom, selecting global controller and TabBarView(wrapping with WillPopScope)
bottom: TabBar(
onTap: (int index) async {
currentIndex = index;
print('here is index- $index');
if (tabWebControllerMap.containsKey(currentIndex)) {
globalController = tabWebControllerMap[currentIndex];
final hereUrl = await globalController.getUrl();
print('here url- $hereUrl');
}
},
controller: _tabController,
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
child: Text(
ca.name,
style: TextStyle(fontSize: 12),
),
))
.toList(),
),
),
Please, anyone, tell me how can I make Bottom Navigation Bar visible on every page of my app in flutter? I know there's an option called Custom Navigator (https://pub.dev/packages/custom_navigator), but how to use this for more than 2 subpages? Please help me I am stucked on a big project. Thank you in Advance :)
you just need to change widgets on the same page, not navigating, check this code out!
import 'package:flutter/material.dart';
import './pages/home.dart'; //imported widget 1
import './pages/listed_homes.dart'; //imported widget 2
import './widgets/form_card.dart'; //imported widget 3
class BottomNav extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return BottomNavState();
}
}
class BottomNavState extends State<BottomNav> {
int _currentIndex = 0; //initialize index that alters widgets when increased or decreased`
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
_currentIndex = value;
setState(() {});
},
items: [
BottomNavigationBarItem(
//<--- item 1 text and icon declared
icon: Icon(Icons.book),
title: Text('Find a home')),
BottomNavigationBarItem(
//<--- item 2 text and icon declared
icon: Icon(Icons.add_a_photo),
title: Text('Enlist a home')),
BottomNavigationBarItem(
//<--- item 3 text and icon declared
icon: Icon(Icons.message),
title: Text('Messages')),
]),
body: Stack(children: [
[
Home(_cent), //widget one
FormCard(widget.model), //widget two
Messages() //widget three
][_currentIndex], //Alter widgets with changing index
Positioned(
top: 30,
left: 15,
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
),
)
]),
);
}
}
Check this method to keep a widget on every page:
MaterialApp(
title: 'Flutter Demo',
initialRoute:"/home",
routes: [
...
],
builder: (context, child) {
return Stack(
children: [
child!,
Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
return YourCustomWidget(); *//This widget now appears on all pages*
},
),
],
),
],
);
},
I am creating an admin dashboard, I currently have two view widgets in a row:
A side bar - 300px (not drawer, because I want it to show permanently) - which has a list.
A content widget - expanded.
Admin Dashboard View
Here is the code for the page:
import 'package:flutter/material.dart';
import 'package:webenrol/widgets/dashboard_admin.dart';
import 'package:webenrol/widgets/drawer.dart';
//TODO: add flappy_search_bar package and add to appBar
class AdminDashboard extends StatelessWidget {
//TODO: Add title
static String id = '/admin_dashboard';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Admin Dashboard - Overview'),),
body: Container(child: Row(
children: <Widget>[
//Sidebar
DashboardSideBar(),
//Main Dashboard Content
DashboardAdmin(),
],
)),
);
}
}
I am going to create other content widgets for the links in the sidebar, what I preferably would like, is have the content widget update to what is clicked on the menu, and also have the ListTile selected as active without the page needing to reload.
Is this possible and is my WebApp laid out correctly for this, or do I need to change it?
So I found a solution, I needed to use a TabController and TabView.
When I setup my TabController, I setup a Listener to listen for any events on its index.
class _State extends State<AdminDashboard> with SingleTickerProviderStateMixin{
int active = 0;
//TODO: Add title
#override
void initState() {
super.initState();
tabController = TabController(length: 5, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
active = tabController.index;
});
});
}
Then I modified my menu to animate to the correct page onTap and also be selected if the page I was on was true.
Widget adminMenu() {
return ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
selected: tabController.index == 0 ? true : false,
onTap: () {
tabController.animateTo(0);
},
),
ListTile(
leading: Icon(Icons.add),
title: Text('Add New Centre'),
selected: tabController.index == 1 ? true : false,
onTap: () {
tabController.animateTo(1);
},
),
ListTile(
leading: Icon(Icons.list),
title: Text('List Centres'),
selected: tabController.index == 2 ? true : false,
onTap: () {
tabController.animateTo(2);
},
),
ListTile(
leading: Icon(Icons.people),
title: Text('Users'),
selected: tabController.index == 3 ? true : false,
onTap: () {
tabController.animateTo(3);
},
),
ListTile(
leading: Icon(Icons.exit_to_app),
title: Text('Logout'),
selected: tabController.index == 4 ? true : false,
onTap: () {
tabController.animateTo(4);
},
),
],
);
}
Then I had to simply setup my TabBarView in the content area:
return Scaffold(
appBar: AppBar(
//TODO: Make title dynamic to page using tabController.index turnkey operator
title: Text('Admin Dashboard - Overview'),
),
body: Container(
child: Row(
children: <Widget>[
//Responsive Sidebar
DashboardSideBar(),
//Main Dashboard Content
Expanded(
child: TabBarView(controller: tabController,
children: <Widget>[
DashboardAdmin(),
Container(child: Text('Hello World!'),),
Container(child: Text('Page 3'),),
Container(child: Text('Page 4'),),
Container(child: Text('Page 5'),),
],),
),
],
)),
);
I still need to refactor my code and clean it up, but for anyone wanting to create a clean dynamic Web Dashboard this is how :)
I have DefaultTabController with 3 tabs. One of the tabs is a list. I need that when clicking on a list item, current list change on other widget. How do I do this? Thank you. Tab State Class
class StationState extends State<Stations> {
Widget secondWidget;
#override
Widget build(BuildContext context) {
secondWidget = Styles(this);
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Color(0xFF000000),
appBar: AppBar(
title: HeaderLogo(),
backgroundColor: Color(0xFF000000),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(
text: 'Favorites',
),
Tab(
text: 'Genres',
),
Tab(
text: 'Networks',
),
],
),
),
body: TabBarView(children: [
Favorites(),
secondWidget,
Networks(),
]),
),
);
;
}
}
Please add you code to help you better.
But you can pass a Callback to the list item, and when the list item is clicked, the callback is fired.
Something like this
return ListItem(
onPressed: () {
setState((){
//Do what you need on the parent
});
}
);
I have a navigation_bar.dart file that handles changing to new pages within my app. Within it, I am using the bottomNavigationBar to build out four different pages based on what tab is currently selected like so:
class NavigationBar extends StatefulWidget {
#override
_NavigationBarState createState() => _NavigationBarState();
}
class _NavigationBarState extends State<NavigationBar> {
int _selectedIndex = 0;
final List<Widget> _pageOptions = <Widget>[
Page1(),
Page2(),
Page3(),
Page4(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
String userID =
Provider.of<FirebaseUser>(context, listen: false) != null ? Provider.of<FirebaseUser>(context).uid : 'null';
return MultiProvider(
providers: [StreamProvider<MyUser>.value(value: DatabaseService().streamUser(userID))],
child: Scaffold(
body: IndexedStack(
children: _pageOptions,
index: _selectedIndex,
),
bottomNavigationBar: Theme(
data: Theme.of(context).copyWith(
canvasColor: Color(0xff271037).withOpacity(0.90),
splashColor: Colors.transparent,
),
child: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
unselectedItemColor: Colors.white,
selectedItemColor: Color(0xff3ADEA7),
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Container(),
title: Icon(FontAwesomeIcons.fire, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.fastfood, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.directions_bike, color: Colors.white),
),
BottomNavigationBarItem(
icon: Container(),
title: Icon(Icons.person, color: Colors.white),
)
],
),
),
),
);
}
}
Now, in a different file which is Page3.dart, on that page there is an alert dialog that pops up and when clicked, I want it to navigate to Page4().
Future<void> _showMissingDataDialog(String data) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('You have not set your $data yet.'),
actions: <Widget>[
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.grey[200],
textColor: Colors.black,
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.grey[200],
textColor: Colors.black,
child: Text('Set $data', style: TextStyle(fontWeight: FontWeight.bold)),
onPressed: () {
Navigator.of(context).pop();
// TODO: Redirect to page4() here as if it was tapped on the BottomNavigationBar
})
],
);
},
);
}
How can I have it so that clicking the "Set $data" button would route to Page4()? I want to make it so that the bottomNavigationBar reacts to this as if you tapped on the actual fourth BottomNavigationBarItem item.
Give your Nav Bar a Global Key. I declared this outside of all widgets on my main Dart file.
GlobalKey navBarGlobalKey = GlobalKey(debugLabel: 'bottomAppBar');
bottomNavigationBar: BottomNavigationBar(
key: navBarGlobalKey,
onTap: _onItemTapped,
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
items: [ ... ]
Then use the global key to call the onTap Method in the onPressed method of your button. You will have to import the other dart file into this page before the global key is available.
final BottomNavigationBar navigationBar = navBarGlobalKey.currentWidget;
initialIndex = 0;
navigationBar.onTap(3); //Starts at index 0, so passing in 3 should do the trick.