In my app, I got a class "home" where the user is navigated to after he is authorized. Within "home" there is a PageView with BottomNavigationBar. When the user is getting a push notification from firebase (new message) he should be navigated to ChatHome and to the specific chat after tapping. How can I do this?
Home does look like this
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: PageView(
children: <Widget>[
Feed(userID: widget.userID),
SearchView(),
ChatHome(),
Profile(
uid: currentUser?.uid,
auth: widget.auth,
logoutCallback: widget.logoutCallback,
),
],
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: CupertinoTabBar(
currentIndex: pageIndex,
inactiveColor: Colors.white,
backgroundColor: Color.fromRGBO(0, 111, 123, 1),
activeColor: Color.fromRGBO(251, 174, 23, 1),
onTap: onTap,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home, size: 20),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.search, size: 20),
title: Text("Search"),
),
BottomNavigationBarItem(
icon: Icon(Icons.chat, size: 20),
title: Text("Chats"),
),
BottomNavigationBarItem(
icon: Icon(Icons.profile, size: 20),
title: Text("Profile"),
),
]),
);
}
In ChatHome I got ListTiles with all the chat partners. On click the user is navigated to the specific chat.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
chatPartnerId: chatPartner[index].uid,
chatPartnerName: chatPartner[index].username,
chatPartnerPhotoUrl: chatPartner[index].photoUrl)));
In home page, I got my onResume
I tried different things but could find a good solution
With the following code, I was able to navigate to ChatHome but the BottomNavigationBar disappeared.
onResume: (Map<String, dynamic> message) async {
final screen = message['screen'];
print(screen);
if(screen == "ChatHome"){
Navigator.pushNamed(context, '/chatHome');
}
},
The following code didn't work well as the app jumped around and always ends in home.
onResume: (Map<String, dynamic> message) async {
final screen = message['screen'];
print(screen);
if(screen == "ChatHome"){
Navigator.pushNamed(context, '/home').then((_) => pageController.jumpToPage(2));
}
},
routes: {
'/rootPage': (BuildContext context) => new RootPage(auth: new Auth()),
'/chatHome': (BuildContext context) => ChatHome(),
'/home': (BuildContext context) => Home(),
},
Any help is appreciated.
You need to provide a navigatorKey to your MaterialApp Widget:
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
....
navigatorKey: navigatorKey,
.....
);
}
}
Then, you can use this navigatorKey to navigate in your onResume handler:
onResume: (message) async {
navigatorKey.currentState.pushNamed("/sample-route/" + message["data"]["..."])
}
For the onLaunch handler (which is called directly onLaunch, means the first app view isn't built yet which means you cannot use the navigatorKey instantly, I only found a hacky solution:
onLaunch: (message) async {
Timer.periodic(
Duration(milliseconds: 500),
(timer) {
if (navigatorKey.currentState == null) return;
navigatorKey.currentState.pushNamed("/sample-route/" + message["data"]["..."]);
timer.cancel();
},
);
}
Of course, the solution for onLaunch isn't clean, but it works.
Related
I am working with nested navigators for multiple sections in my app and I have defined the keys for these navigators, you can see below the code where I have added Navigator keys in class so i can use it in my project by accessing class
class NavigatorKeys {
static final GlobalKey<NavigatorState> homeNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: "homeNavigatorKey");
static final GlobalKey<NavigatorState> shiftNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: "shiftNavigatorKey");
static final GlobalKey<NavigatorState> requestNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: "requestNavigatorKey");
static final GlobalKey<NavigatorState> messageNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: "messageNavigatorKey");
static final GlobalKey<NavigatorState> profileNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: "ProfileNavigatorKey");
}
Below is the code for one of the sections i.e ProfileNavigator which uses key
import '/theme/styles.dart';
import '/utils/constants.dart';
import '/views/profile/balances_page.dart';
import '/views/profile/change_password_page.dart';
import '/views/profile/language_page.dart';
import '/views/profile/profile_page.dart';
import '/views/profile/test_page.dart';
import '/views/profile/wage_accounts.dart';
import '/widgets/page_route_builder.dart';
import 'package:flutter/material.dart';
class ProfileNavigator extends StatelessWidget {
const ProfileNavigator({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: NavigatorKeys.profileNavigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/':
return pageRouteBuilder(
ProfilePage(),
);
case '/langPage':
return pageRouteBuilder(
LanguagePage(),
);
case '/changePass':
return pageRouteBuilder(
ChangePasswordPage(),
);
case '/balancePage':
return pageRouteBuilder(
BalancesPage(),
);
case '/testPage':
return pageRouteBuilder(
TestPage(),
);
case '/balancePage':
return pageRouteBuilder(
BalancesPage(),
);
case '/wageAccounts':
return pageRouteBuilder(
WageAccountsPage(),
);
}
return pageRouteBuilder(
Container(
child: Center(
child: Text(
"Hmm...Thats Weird",
style: kTextStyleLargeBlack,
),
),
),
);
},
);
}
}
here is my implementation for BottomBar
List<Widget> mobilePages = [
HomeNavigator(),
GetConfigurationProvider().getConfigurationModel.shiftEnabled? ShiftNavigator():null,
RequestNavigator(),
GetConfigurationProvider().getConfigurationModel.messagesEnabled?MessageNavigator():null,
ProfileNavigator(),
];
Widget _bottomNavigatorBar() {
return Theme(
data: Theme.of(context).copyWith(
// sets the background color of the `BottomNavigationBar`
canvasColor: Theme.of(context).primaryColor,
// sets the active color of the `BottomNavigationBar` if `Brightness` is light
),
child: BottomBar(
height: Platform.isIOS ? 90 : 60,
backgroundColor: Theme.of(context).primaryColor,
duration: Duration(milliseconds: 800),
items: <BottomBarItem>[
BottomBarItem(
title: Text(pagesInfoList[0].pageName),
icon: Icon(pagesInfoList[0].pageIcon),
activeColor: Colors.white,
inactiveColor: Colors.grey[300]),
BottomBarItem(
title: Text(pagesInfoList[1].pageName),
icon: Icon(pagesInfoList[1].pageIcon),
activeColor: Colors.white,
inactiveColor: Colors.grey[300]),
BottomBarItem(
title: Text(pagesInfoList[2].pageName),
icon: Icon(pagesInfoList[2].pageIcon),
activeColor: Colors.white,
inactiveColor: Colors.grey[300]),
BottomBarItem(
title: Text(pagesInfoList[3].pageName),
icon: Icon(pagesInfoList[3].pageIcon),
activeColor: Colors.white,
inactiveColor: Colors.grey[300]),
BottomBarItem(
title: Text(pagesInfoList[4].pageName),
icon: Icon(pagesInfoList[4].pageIcon),
activeColor: Colors.white,
inactiveColor: Colors.grey[300]),
],
selectedIndex: _selectedIndex,
onTap: (int index) {
setState(() {
_selectedIndex = index;
});
}),
);
}
And below code I used for android back button to work
List<GlobalKey<NavigatorState>> _navigatorKeys = [
NavigatorKeys.homeNavigatorKey,
NavigatorKeys.shiftNavigatorKey,
NavigatorKeys.requestNavigatorKey,
NavigatorKeys.messageNavigatorKey,
NavigatorKeys.profileNavigatorKey,
];
Future<bool> _systemBackButtonPressed() {
if (_navigatorKeys[_selectedIndex].currentState.canPop()) {
_navigatorKeys[_selectedIndex]
.currentState
.pop(_navigatorKeys[_selectedIndex].currentContext);
} else {
SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _systemBackButtonPressed,
..........
So what happens is when I logout the user and Login again I start getting error of Duplicate Global Key detected it mostly initiates in ProfileNavigator, and then debug console keep showing the message of Duplicate Global key in infinite times
on Logout this is the code that take user to Login Page start of t
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (_) => LoginPage(),
),
);
In your Logout code, you have to use pushReplacement() instead of push(), because it will clear the stack and you will not get a duplicate key error in your widget tree.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => LoginPage(),
),
);
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (_) => LoginPage(),
),
);
Places the new Scaffold on top of the previous. You can't push a 2nd duplicate Scaffold as a third layer when you want to return to the original screen, instead use:
Navigator.of(context).pop();
That removes the 2nd screen, and you just see the original, never really left 1st screen. If you don't want to keep the original screen under the 2nd, use
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => LoginPage(),
),
);
my FutureBuilder methods return my app everytimes I swap pages.
It cause very bad performances when i navigate between pages.
I have checked solutions on post already in this forum, tried to use provider (didn't fixed my problem), I also tried to move my FutureBuilder into my initState so it's called only one time but didn't manage to make it.
For more details, I printed a line when firebase initialization from FutureBuilder is done, and I witness that everytime i swap pages it print my lane again
My main.dart file:
NotificationService notificationService = NotificationService();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
notificationService.init();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
runApp(AppWrapper());
}
class AppWrapper extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _AppWrapperState();
}
}
class _AppWrapperState extends State<AppWrapper> {
#override
Widget build(BuildContext context) {
return OverlaySupport.global(
child: MaterialApp(
theme: ThemeData(
textTheme: GoogleFonts.openSansTextTheme(
Theme.of(context).textTheme,
)),
debugShowCheckedModeBanner: false,
home: App()),
);
}
}
/// We are using a StatefulWidget such that we only create the [Future] once,
/// no matter how many times our widget rebuild.
/// If we used a [StatelessWidget], in the event where [App] is rebuilt, that
/// would re-initialize FlutterFire and make our application re-enter loading state,
/// which is undesired.
class App extends StatefulWidget {
// Create the initialization Future outside of `build`:
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
/// The future is part of the state of our widget. We should not call `initializeApp`
/// directly inside [build].
bool? _isLoggedIn;
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
void initState() {
// initIsLoggedIn();
super.initState();
_asyncMethod();
}
void _asyncMethod() async {
try {
// Get values from storage
final storage = new FlutterSecureStorage();
String isLoggedIn = await storage.read(key: "isLoggedIn") ?? "";
if (isLoggedIn == "true") {
//If the user seems logged in, we check it by trying to authenticate
String mail = await storage.read(key: "mail") ?? "";
String password = await storage.read(key: "password") ?? "";
UserResponse userResponse = await APIUser().login(mail, password);
User? user = userResponse.user;
if (user != null) {
setState(() {
_isLoggedIn = true;
});
} else {
//If we cannot connect the user with the stored mail and password, then we redirect the user to the login page
setState(() {
_isLoggedIn = false;
});
print("returning to login page");
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()),
);
}
} else {
//If we cannot connect the user with the stored mail and password, then we redirect the user to the login page
setState(() {
_isLoggedIn = false;
});
print("returning to login page");
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()),
);
}
} catch (e) {
//print the error and redirect the user to the login page
print(e);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()));
}
}
//The following variables are used to handle the navigation between the 3 main pages
List<Widget> pages = [
SettingsPage(key: PageStorageKey('settings')),
HomePage(key: PageStorageKey('home')),
AccountPage(
key: PageStorageKey('account'),
),
];
int _selectedIndex = 1;
// void _changePage(int index) {
// setState(() {
// _selectedIndex = index;
// });
// }
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return somethingWentWrongWidget();
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done &&
_isLoggedIn != null) {
print("Initialize FireBase done");
return appContentWidget();
}
// Otherwise, show something whilst waiting for initialization to complete
print("loadingpage showing to let initialization load");
return LoadingPage();
},
);
}
Widget somethingWentWrongWidget() {
return Center(
child: Text('Someting went wrong', textDirection: TextDirection.ltr),
);
}
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PageController _pageController = PageController(
initialPage: 1,
);
final _bottomNavigationItems = [
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/settingsActive.png"),
size: 32,
),
icon: ImageIcon(
AssetImage("assets/settings.png"),
size: 32,
color: Colors.black,
),
label: ''),
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/dashboardActive.png"),
size: 32,
),
icon: ImageIcon(
AssetImage("assets/dashboard.png"),
size: 32,
color: Colors.black,
),
label: ''),
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/userActive.png"),
size: 32,
color: Colors.black,
),
icon: ImageIcon(
AssetImage("assets/user.png"),
size: 32,
color: Colors.black,
),
label: ''),
];
Widget appContentWidget() {
double bottomNavbarHeight = 70;
if (Platform.isIOS) {
bottomNavbarHeight = 90;
}
// check if the user is logged in, if not go to login page
bool loggedIn = _isLoggedIn ?? false;
if (!loggedIn) {
return LoginPage();
} else {
return WillPopScope(
onWillPop: () {
return Future.value(false);
},
child: Scaffold(
key: _scaffoldKey,
body: PageView(
physics: AlwaysScrollableScrollPhysics(),
onPageChanged: (int index) {
setState(() {
_selectedIndex = index;
});
},
controller: _pageController,
pageSnapping: true,
children: [
SettingsPage(key: PageStorageKey('settings')),
HomePage(key: PageStorageKey('home')),
AccountPage(
key: PageStorageKey('account'),
),
],
),
bottomNavigationBar: Container(
height: bottomNavbarHeight,
decoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.grey.shade200)]),
child: Theme(
data: ThemeData(
brightness: Brightness.light,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
elevation: 20,
backgroundColor: Colors.white,
selectedFontSize: 0,
//As the label is obligatory, we give it a size of zero
unselectedFontSize: 0,
//As the label is obligatory, we give it a size of zero
items: _bottomNavigationItems,
selectedItemColor: Colors.black,
currentIndex: _selectedIndex,
onTap: (int index) {
_pageController.animateToPage(index,
duration: Duration(milliseconds: 500),
curve: Curves.easeInCubic);
},
),
),
),
),
);
}
}
}
What i would like to achieve is that FutureBuilder initialize only on initState.
you're using await Firebase.initializeApp() already in your main function, why would you need to initialize it one more time in the FutureBuilder ?
On a more general note, your FutureBuilder function is called many times because of the setState on your nav bar, which recalls the build function where your FutureBuilder is.
Whenever you face that, I suggest you split your widget, by having:
-a StatelessWidget containing your FutureBuilder
-a StatefulWidget containing all the display and logic needed.
I have 3 tabs in my application and there is a date picker which is same for all the tabs whenever i choose the date (from which ever tab it may be)all the data in 3 tabs will change corresponding to the choosen date and so many apis have been provided to this.
But the problem is every time whenever i switch the tab all the apis are hiting again.so how can i manage the tabs so that it will not hit the apis on switching until i choose the date again
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
SelectedDates _selectedDates = SelectedDates();
List<DateTime> selectedDates = List();
int _currentIndex = 0;
List<Widget> _children = [
FirstPage(),
ChartPage(),
ActionPage(),
];
onTabSelected(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
final _homeProvider = Provider.of<HomeProvider>(context);
final _chartProvider = Provider.of<ChartListingProvider>(context);
final _actionProvider = Provider.of<ActionProvider>(context);
showDatePickerDialog(BuildContext context) async {
final List<DateTime> picked = await DateRagePicker.showDatePicker(
context: context,
initialFirstDate: DateTime.now(),
firstDate: DateTime(2015),
initialLastDate: (DateTime.now()).add(
Duration(days: 7),
),
lastDate: DateTime(2025),
);
if (picked != null && picked.length == 2 && picked != selectedDates) {
setState(() {
selectedDates = picked;
var formatter = DateFormat('dd/MM/yyyy');
_selectedDates?.fromDate = formatter.format(picked[0]);
_selectedDates?.endDate = formatter.format(picked[1]);
_actionProvider.setDate(_selectedDates);
_chartProvider.setDate(_selectedDates);
_homeProvider.setDate(_selectedDates);
});
}
}
return ValueListenableBuilder(
valueListenable: Hive.box(userDetailsBox).listenable(),
builder: (_, Box box, __) {
String token = box.get(authTokenBoxKey);
String id = box.get(companyIdBoxKey);
_actionProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to third tab
_chartProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to second tab
__homeProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to first tab
return DefaultTabController(
length: 3,
initialIndex: 1,
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showDatePickerDialog(context);
},
child: Icon(Icons.date_range),
backgroundColor: Theme.of(context).accentColor,
),
appBar: AppBar(title: Text("Tab Controller"), actions: <Widget>[]),
bottomNavigationBar: BottomNavigationBar(
onTap: onTabSelected,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text("Chart"),
),
BottomNavigationBarItem(
icon: Icon(Icons.redo),
title: Text("Action"),
),
],
currentIndex: _currentIndex,
),
body: _children[_currentIndex],
),
);
},
);
}
}
It's because of the setstate which force your whole widget to rebuild.
onTabSelected(int index) {
setState(() {
_currentIndex = index;
});
}
For this case you can use providers or other State Management librarys out there like RXdart, Riverpod, Providers or anything else.
These state management librarys give you access to the states and notifies about changes without rebuilding the whole tree. There are a lot of concepts out there you can explore by googling.
Implementation
This is an example implementation using Providers package:
NavigationNotifier:
import 'package:flutter/material.dart';
class NavigationNotifier with ChangeNotifier {
int _currentIndex = 0;
get currentIndex => _currentIndex;
set currentIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}
Your main file/ home, whatever:
class Home extends StatelessWidget {
final List<Widget> _children = [Screen1(), Screen2()];
#override
Widget build(BuildContext context) {
var provider = Provider.of<NavigationNotifier>(context);
...
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
provider.currentIndex = index;
},
currentIndex: provider.currentIndex,
showSelectedLabels: true,
showUnselectedLabels: true,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: 'home'),
BottomNavigationBarItem(
icon: new Icon(Icons.supervised_user_circle_outlined),
label: 'Profile')
],
),
body: _children[provider.currentIndex]),
I have implemented an app which navigate through few screens and the main point is a BottomNavigationBar with four tabs. Inside the second tab. I have a ModalBottomSheet that has a button option to logout. The idea is to navigate back to Login page after logout button clicked.
well it does navigate back to login screen but it takes the BottomNavigationBar with it.
I will provide my navigation stack view and the bottom navigation bar main page.
Login to the BottomNavigationBar Page
Navigator.of(context).push(PageTransition(
child: ProHomeScreen(),
type: PageTransitionType.rightToLeft,
));
The BottomNavigationBar main page
class ProHomeScreen extends StatefulWidget {
const ProHomeScreen({Key key}) : super(key: key);
static String tag = 'pro-home-page';
#override
_ProHomeScreenState createState() => _ProHomeScreenState();
}
class _ProHomeScreenState extends State<ProHomeScreen>with SingleTickerProviderStateMixin {
int _selectedIndex = 0;
List<int> _history = [0];
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
TabController _tabController;
List<Widget> mainTabs;
List<BuildContext> navStack = [null, null, null, null];
//
#override
void initState() {
_tabController = TabController(vsync: this, length: 4);
mainTabs = <Widget>[
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[0] = context;
return DailyGuideScreen();
});
}),
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[1] = context;
return UserProfileViewScreen();
});
}),
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[2] = context;
return PetProfileViewScreen();
});
}),
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[3] = context;
return AllMealPlanPage();
});
}),
];
super.initState();
}
//
final List<BottomNavigationBarRootItem> bottomNavigationBarRootItems = [
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text(''),
),
),
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text(''),
),
),
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.pets),
title: Text(''),
),
),
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.fastfood),
title: Text(''),
),
),
];
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
body: TabBarView(
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
children: mainTabs,
),
bottomNavigationBar: BottomNavigationBar(
items: bottomNavigationBarRootItems.map((e) => e.bottomNavigationBarItem).toList(),
currentIndex: _selectedIndex,
selectedItemColor: Color(0xFF03B898),
unselectedItemColor: Color(0xFF01816B),
onTap: _onItemTapped,
// indicator: UnderlineTabIndicator(
// borderSide: BorderSide(color: lightGreen, width: 5.0),
// insets: EdgeInsets.only(bottom: 44.0),
// ),
),
),
onWillPop: () async{
if (Navigator.of(navStack[_tabController.index]).canPop()) {
Navigator.of(navStack[_tabController.index]).pop();
setState((){ _selectedIndex = _tabController.index; });
return false;
}else{
if(_tabController.index == 0){
setState((){ _selectedIndex = _tabController.index; });
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); // close the app
return true;
}else{
_tabController.index = 0; // back to first tap if current tab history stack is empty
setState((){ _selectedIndex = _tabController.index; });
return false;
}
}
},
);
}
void _onItemTapped(int index) {
_tabController.index = index;
setState(() => _selectedIndex = index);
}
}
////
class BottomNavigationBarRootItem {
final String routeName;
final NestedNavigator nestedNavigator;
final BottomNavigationBarItem bottomNavigationBarItem;
BottomNavigationBarRootItem({
#required this.routeName,
#required this.nestedNavigator,
#required this.bottomNavigationBarItem,
});
}
abstract class NestedNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
NestedNavigator({Key key, #required this.navigatorKey}) : super(key: key);
}
class HomeNavigator extends NestedNavigator {
HomeNavigator({Key key, #required GlobalKey<NavigatorState> navigatorKey})
: super(
key: key,
navigatorKey: navigatorKey,
);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/':
builder = (BuildContext context) => AllMealPlanPage();
break;
case '/home/1':
builder = (BuildContext context) => MealPlanDetailsScreen();
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
);
}
}
////
Second Tab The Logout option Navigation(which is inside a ModalBottomSheet)
_userProfileBtmSheet() {
showModalBottomSheet(
...
).whenComplete(() {
if(goLogOut){
_isLoading ? null : _handleLogout(context);
}
});
}
////
Within _handleLogout()
Navigator.pop(
context, new MaterialPageRoute(builder: (context) => LoginScreen()));
I need to navigate out from the BottomNavigationBar page and replace the screen with the login screen.
https://github.com/jasonwaku/jason_pawfect/tree/master
Instead of using
Navigator.pop(
context, new MaterialPageRoute(builder: (context) => LoginScreen()));
Try using
Navigator.of(context).pushReplacment(
context, new MaterialPageRoute(builder: (context) => LoginScreen()));
I faced the same issue today where I have to go to login page from one of the tab through logout button, and bottom navigation bar also passed along, what i have done is used bloc state management and navigate from navigation bar page according to the state generated by authentication bloc.
logout button:
await BlocProvider.of<AuthenticationCubit>(context).logout();
authentication cubit:
Future<void> logout() async {
emit(AuthenticationLoadingState());
await userRepository.logout();
emit(UnAutheticatedState());
}
And listen to the Unauthenticated state in bloc listner widget in the home page of navigation bar
Scaffold(
body: BlocListener<AuthenticationCubit, AuthenticationState>(
listener: (context, state) {
if (state is UnautheticatedState) {
Navigator.pushAndRemoveUntil<dynamic>(
context,
MaterialPageRoute<dynamic>(
builder: (BuildContext context) => Login(),
),
(route) =>
false, //if you want to disable back feature set to false
);
}
},
Trying to activate CupertinoTabBar's tab 0 while tab 1 is active on a stream event like this:
{
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
int _currentTabIndex = 0;
#override
void initState() {
super.initState();
_drawerStream.listen((state) {
if (_currentTabIndex != 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() => _currentTabIndex = 0);
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
elevation: 0.0,
child: DrawerScreen(),
),
body: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
onTap: (index) {
_currentTabIndex = index;
},
currentIndex: _currentTabIndex,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text('Main'),
icon: Icon(IconData(0xe800), size: 20),
),
BottomNavigationBarItem(
title: Text('Goodies'),
icon: Icon(IconData(0xe84b), size: 20),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
switch (index) {
case 0: return MainScreen();
case 1: return GoodiesScreen();
}
},
);
},
),
);
}
}
}
It nothing happens visually when an event comes from _drawerStream. Still tracing what's going on using the debugger it found that CupertinoTabBar widget builds 2 times and 1st time it has current index parameter 0, what we actually need. But the second run it rebuilds with current index parameter set to 1, which is not what we want.
What the reason for that, how we can switch to a tab on an external event?
It's solved using StreamBuilder, a method that was actually attempted first. This method has a different caveat: it switches to tab 0 when we have to stay on, for example, 1st tab and it happens when we come back from another screen open on top of the screen with tabs.
What's wrong this time? The stream builder re-emits last event (BehaviourSubject based stream) and this way _currentTabIndexis set to 0, while we need it to keep a current value. The solution is to "remember" the last event and recognize it just re-emitted. This may not solve all similar issues, but at least gives a clue.
A piece of code to illustrate the solution:
class HomeScreenState extends State<HomeScreen> {
int _currentTabIndex = 0;
AsyncSnapshot<UIState> lastSnapshot;
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
elevation: 0.0,
child: DrawerScreen(),
),
body: StreamBuilder(
stream: _drawerStream,
builder: (context, AsyncSnapshot<UIState> snapshot) {
if (_currentTabIndex != 0 && lastSnapshot != snapshot) {
SchedulerBinding.instance.addPostFrameCallback((_) =>
setState(() => _currentTabIndex = nextTab));
}
lastSnapshot = snapshot;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
onTap: (index) {
_currentTabIndex = index;
},
currentIndex: _currentTabIndex,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text('Main'),
icon: Icon(IconData(0xe800), size: 20),
),
BottomNavigationBarItem(
title: Text('Goodies'),
icon: Icon(IconData(0xe84b), size: 20),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
switch (index) {
case 0: return MainScreen();
case 1: return GoodiesScreen();
}
},
);
},
),
);
}
}
You can also use GlobalKeys and your problem should go away. Here is a clear tutorial on how to do it
https://medium.com/#info_4766/ios-tab-bar-in-flutter-9379cf09df31