Duplicate GlobalKey detected in widget tree with nested Navigator - flutter

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(),
),
);

Related

How to change go_router's location when using OpenContainer from animations?

I'm trying to open a page with OpenContainer, and passing the RouteSettings with name.
Is there any way to update the router.location from go_router package?
That's how I use OpenContainer, which OpenBuilder returns the page that I'm passing.
class _OpenContainerWrapper extends StatelessWidget {
const _OpenContainerWrapper({
required this.closedBuilder,
required this.transitionType,
required this.onClosed,
required this.page,
this.routeSettings,
this.borderRadius = 4,
});
final CloseContainerBuilder closedBuilder;
final ContainerTransitionType transitionType;
final ClosedCallback<bool?> onClosed;
final Widget page;
final RouteSettings? routeSettings;
final double borderRadius;
#override
Widget build(BuildContext context) {
return OpenContainer<bool>(
closedElevation: 0,
closedColor: Colors.transparent,
middleColor: Colors.transparent,
openColor: Colors.transparent,
routeSettings: routeSettings,
closedShape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(borderRadius),
),
),
transitionType: transitionType,
openBuilder: (BuildContext context, VoidCallback _) {
return page;
},
onClosed: onClosed,
tappable: false,
closedBuilder: closedBuilder,
);
}
}
And that's how I'm using that wrapper as a button to open page that I'm passing.
return _OpenContainerWrapper(
routeSettings: const RouteSettings(
name: 'categories',
),
page: const CategoryPage(),
transitionType: ContainerTransitionType.fade,
onClosed: (_) {},
closedBuilder: (_, func) {
return WidgetWithBackground(
onTap: func,
backgroundColor: textColor.withOpacity(
0.25,
),
// More UI Code here relevant to the button.
This might be relevant: https://github.com/flutter/flutter/issues/51399

Replace a page with a BottomNavigationBar (Tabview) with another page on navigation in Flutter

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
);
}
},

BlocProvider.of() called with a context that does not contain a Bloc of type FicheMvtBloc

I'm developing a new Flutter Mobile app using the BLoC pattern . But I've got a problem and I don't find the solution yet.
The first one is my home page (with the MultiBlocProvider)
When I press on the FloatingActionButton.
It push a new screen to add a new "FicheMvt"
When I hit the add button.
It uses an onSave callback function to notify its parent of newly created "FicheMvt"
It gives me an error.
BlocProvider.of() called with a context that does not contain a Bloc
of type FicheMvtBloc.
No ancestor could be found starting from the context that was passed
to BlocProvider.of().
This can happen if the context you used comes from a widget above the
BlocProvider.
This is the home page (render 5 tab body)
class EtatCollecteScreen extends StatelessWidget {
final FicheMvtDAO ficheMvtDAO = FicheMvtDAO();
final FicheMvtReferenceDAO ficheMvtReferenceDAO = FicheMvtReferenceDAO();
#override
Widget build(BuildContext context) {
final FicheModel fiche = ModalRoute.of(context).settings.arguments;
return MultiBlocProvider(
providers: [
BlocProvider<TabEtatCollecteBloc>(
create: (context) => TabEtatCollecteBloc(),
),
BlocProvider<FicheMvtBloc>(
create: (context) => FicheMvtBloc(
ficheMvtDAO: ficheMvtDAO,
)..add(FicheMvtRequested(idFiche: fiche.id)),
),
BlocProvider<FicheMvtReferenceBloc>(
create: (context) => FicheMvtReferenceBloc(
ficheMvtReferenceDAO: ficheMvtReferenceDAO,
)..add(FicheMvtReferenceRequested(idFiche: fiche.id)),
),
],
child: EtatCollecteContent(
ficheModel: fiche,
),
);
}
}
class EtatCollecteContent extends StatelessWidget {
final FicheModel ficheModel;
const EtatCollecteContent({Key key, #required this.ficheModel});
#override
Widget build(BuildContext context) {
return BlocBuilder<TabEtatCollecteBloc, EtatCollecteTab>(
builder: (context, activeTab) {
return Scaffold(
appBar: AppBar(
title: Text("${ficheModel.id} - ${ficheModel.description}"),
actions: <Widget>[
RefreshMvtButton(
visible: activeTab == EtatCollecteTab.completed,
ficheModel: ficheModel,
),
SendMvtButton(
visible: activeTab == EtatCollecteTab.uncommitted,
ficheModel: ficheModel,
),
],
),
body: EtatCollecteBody(
activeTab: activeTab,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return FicheMvtAddScreen(onSaveCallback: (idFiche, indicateurModel, codeSite) {
BlocProvider.of<FicheMvtBloc>(context).add(
FicheMvtAdded(
idFiche: idFiche,
indicateurModel: indicateurModel,
codeSite: codeSite,
),
);
});
},
settings: RouteSettings(
arguments: ficheModel,
),
),
);
},
child: Icon(Icons.add),
tooltip: "Add",
),
bottomNavigationBar: TabEtatCollecteSelector(
activeTab: activeTab,
onTabSelected: (tab) => BlocProvider.of<TabEtatCollecteBloc>(context).add(TabEtatCollecteUpdated(tab)),
),
);
},
);
}
}
And this is the code of the form to add new "FicheMvt" which contains another block that manages the dynamic form (FicheMvtAddBloc).
typedef OnSaveCallback = Function(
int idFiche,
IndicateurModel indicateurModel,
String codeSite,
);
class FicheMvtAddScreen extends StatelessWidget {
final OnSaveCallback onSaveCallback;
const FicheMvtAddScreen({Key key, #required this.onSaveCallback}) : super(key: key);
#override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final FicheModel fiche = ModalRoute.of(context).settings.arguments;
final FicheMvtRepository ficheMvtRepository = FicheMvtRepository();
return Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text("${fiche.id} - ${fiche.description}"),
),
backgroundColor: Colors.white,
body: BlocProvider<FicheMvtAddBloc>(
create: (context) => FicheMvtAddBloc(
ficheMvtRepository: ficheMvtRepository,
idFiche: fiche.id,
)..add(NewFicheMvtFormLoaded(idFiche: fiche.id)),
child: FicheMvtAddBody(
ficheModel: fiche,
onSave: onSaveCallback,
),
),
);
}
}
This is the content of the form
class FicheMvtAddBody extends StatefulWidget {
final FicheModel ficheModel;
final OnSaveCallback onSave;
#override
_FicheMvtAddBodyState createState() => _FicheMvtAddBodyState();
FicheMvtAddBody({Key key, #required this.ficheModel, #required this.onSave});
}
class _FicheMvtAddBodyState extends State<FicheMvtAddBody> {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
void _onIndicateurChanged(String indicateur) =>
BlocProvider.of<FicheMvtAddBloc>(context).add(NewFicheMvtIndicateurChanged(indicateur: indicateur));
void _onSiteChanged(String site) => BlocProvider.of<FicheMvtAddBloc>(context).add(NewFicheMvtSiteChanged(site: site));
final FicheModel fiche = ModalRoute.of(context).settings.arguments;
final txtIndicateur = Text("Indicateur");
final txtSite = Text("Site");
return BlocBuilder<FicheMvtAddBloc, FicheMvtAddState>(
builder: (context, state) {
return Form(
key: _formKey,
child: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
SizedBox(height: 24.0),
txtIndicateur,
DropdownButtonFormField<String>(
isExpanded: true,
hint: Text("Choisissez l'indicateur"),
value: state.indicateur?.code ?? null,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
onChanged: (String newValue) {
_onIndicateurChanged(newValue);
},
items: state.indicateurs?.isNotEmpty == true
? state.indicateurs
.map((CodeDescriptionModel model) => DropdownMenuItem(value: model.code, child: Text(model.description)))
.toList()
: const [],
validator: (value) {
if (value == null || value.isEmpty) {
return 'Entrer l\'indicateur s\'il vous plait';
}
return null;
},
),
SizedBox(height: 24.0),
txtSite,
DropdownButtonFormField<String>(
isExpanded: true,
hint: Text("Choisissez le site"),
value: state.site?.code ?? null,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
onChanged: (String newValue) {
_onSiteChanged(newValue);
},
items: state.sites?.isNotEmpty == true
? state.sites.map((CodeDescriptionModel model) => DropdownMenuItem(value: model.code, child: Text(model.description))).toList()
: const [],
validator: (value) {
if (value == null || value.isEmpty) {
return 'Entrer le site s\'il vous plait';
}
return null;
},
),
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
onPressed: () {
if (_formKey.currentState.validate()) {
widget.onSave(
fiche.id,
state.indicateur,
state.site?.code ?? null,
);
Navigator.pop(context);
}
},
padding: EdgeInsets.all(12),
color: Colors.blue,
child: Text('Create', style: TextStyle(color: Colors.white)),
),
)
],
),
),
);
},
);
}
}
Thanks for your help
You are using the wrong context in onSaveCallback. Here is a simplified hierarchy of your widgets:
- MaterialApp
- EtatCollecteScreen
- MultiBlocProvider
- FicheMvtAddScreen
So in your onSaveCallback you are accessing the context of FicheMvtAddScreen and it's obvious from the hierarchy above that BlocProvider couldn't find the requested Bloc. It's easy to fix this:
MaterialPageRoute(
builder: (pageContext) {
return FicheMvtAddScreen(onSaveCallback: (idFiche, indicateurModel, codeSite) {
BlocProvider.of<FicheMvtBloc>(context).add(
FicheMvtAdded(
idFiche: idFiche,
indicateurModel: indicateurModel,
codeSite: codeSite,
),
);
});
},
settings: RouteSettings(
arguments: ficheModel,
),
),
I've renamed context variable to pageContext in route builder function (so it wouldn't shadow required context). Now BlocProvider should able to find requested Bloc by accessing right context.
Another way to fix is to put MultiBlocProvider higher in widgets hierarchy.

Listview Builder Navigate to page when onTab

Using listview builder, I wanted to pass a string variable (userlist[index].page) to each list so that when onTap is pressed it it navigate to that page.
can someone help to fix the code?
this is the code
child: ListTile(
title: Text(
userlist[index].title,
),
trailing: IconButton(
icon: Icon(
alreadySaved ? Icons.star : Icons.star_border,
color: alreadySaved ? Colors.blue : Colors.blue,
),
onPressed: () {
setState(() {
if (alreadySaved) {
usersavedlist.remove(userlist[index]);
} else {
usersavedlist.add(userlist[index]);
}
});
},
), //subtitle: Text(subtitle),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => (userlist[index].page())));
}),
You need to create a page to navigate and pass the item list as a parameter. By example:
class UserDetail extends StatelessWidget {
final User user;
//constructor
UserDetail(this.user);
Widget build(BuildContext context) {
// Return the widget with the user info or whatever you want
return ...
}
}
And in your navitgator you pass like this:
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => UserDetail(userlist[index])));
}),

Flutter FCM Navigation onResume/onLaunch

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.