How to navigate to sibling item in Flutter Cupertino Bottom Navigation Bar - flutter

I'm using Cupertino design in Flutter and using CupertinoTabBar. I want to visit to different tab item when coming from different screen.
For example, I've 2 items: Home and Profile. CupertinoTabBar is defined in main screen. From HomeScreen, I want to have a button to visit ProfileScreen tab. How to navigate in this situation?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MainScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MainScreenState();
}
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return Material(
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home")
),
BottomNavigationBarItem(
icon: Icon(Icons.user),
title: Text("My Appointments")
)
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
builder: (BuildContext context) {
return HomeScreen();
},
defaultTitle: 'Home',
);
break;
case 1:
return CupertinoTabView(
builder: (BuildContext context) => ProfileScreen(),
defaultTitle: 'Profile',
);
break;
}
return null;
},
),
);
}
}
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return Container(
child: CupertinoButton(
child: Text("Check Profile"),
onPressed: () {
// Pop this page and Navigate to Profile page
},
)
);
}
}
class ProfileScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return Container(
child: Text("Profile")
);
}
}

Maybe can help you
int currentTabIndex = 0;
onTapped(int index) {
setState(() {
currentTabIndex = index;
});
}
List<Widget> tabs = [
InicioPage(),
OfertasPage(),
FavoritosPage(),
Login(),
];
tabBuilder: (context, index) {
switch (index) {
case 0:
return InicioPage();
break;
case 1:
return OfertasPage();
break;
case 2:
return FavoritosPage();
break;
case 3:
return Login();
default:
return InicioPage();
break;
}
}

Related

How to reload page on indexedstack in Flutter

Currently i'm using indexedstack and bottomnavigationbar, there are two pages "HomePage" & "SearchPage". These two pages i put in a children inside indexedstack widget. Now the problem is
If i switch to Search Page or switch back to Home Page it does not reload the page. How to solve this issue using the current widget which is indexedstack widget.
Whenever i run the app it loads all the pages including Search page which is not a current page.
Below is the sample code.
main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: App(),
);
}
}
class App extends StatefulWidget {
#override
State<StatefulWidget> createState() => AppState();
}
class AppState extends State<App> {
static int currentTab = 0;
final List<TabItem> tabs = [
TabItem(
tabName: "Home",
icon: Icons.home_outlined,
page: HomePage(),
),
TabItem(
tabName: "Search",
icon: Icons.search,
page: SearchPage(),
),
];
AppState() {
tabs.asMap().forEach((index, details) {
details.setIndex(index);
});
}
void _selectTab(int index) {
if (index == currentTab) {
tabs[index].key.currentState!.popUntil((route) => route.isFirst);
} else {
setState(() => currentTab = index);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await tabs[currentTab].key.currentState!.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentTab != 0) {
_selectTab(0);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
body: IndexedStack(
index: currentTab,
children: tabs.map((e) => e.page).toList(),
),
bottomNavigationBar: BottomNavigation(
onSelectTab: _selectTab,
tabs: tabs,
),
),
);
}
}
TabItem
class TabItem {
final String tabName;
final IconData icon;
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
int _index = 0;
late Widget _page;
TabItem({
required this.tabName,
required this.icon,
required Widget page,
}) {
_page = page;
}
void setIndex(int i) {
_index = i;
}
int getIndex() => _index;
Widget get page {
return Visibility(
visible: _index == AppState.currentTab,
maintainState: true,
child: Navigator(
key: key,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (_) => _page,
);
},
),
);
}
}
BottomNavigation
class BottomNavigation extends StatelessWidget {
BottomNavigation({
required this.onSelectTab,
required this.tabs,
});
final ValueChanged<int> onSelectTab;
final List<TabItem> tabs;
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: tabs
.map(
(e) => _buildItem(
index: e.getIndex(),
icon: e.icon,
tabName: e.tabName,
),
)
.toList(),
onTap: (index) => onSelectTab(
index,
),
);
}
BottomNavigationBarItem _buildItem(
{required int index, required IconData icon, required String tabName}) {
return BottomNavigationBarItem(
icon: Icon(
icon,
color: _tabColor(index: index),
),
title: Text(
tabName,
style: TextStyle(
color: _tabColor(index: index),
fontSize: 12,
),
),
);
}
Color _tabColor({required int index}) {
return AppState.currentTab == index
? Colors.red
: Colors.black;
}
}

I have got no error regarding syntax but the app doesn't work

I am trying to make a tabbar and include some pages to each tab. I do not get errors and the Simulator runs the app, however, it only shows the tabbar and its animation and not other pages that I need.
I had some errors when I had the Widget buildPages at the button of the page, so I related the widget code under class _MyAppState.
I am not sure which part of the code is dislocated or if there is any extra function like var index;
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:flutter/material.dart';
import 'package:gg/tabBarScreens/Following.dart';
import 'package:gg/tabBarScreens/Liked%20Albums.dart';
import 'package:gg/tabBarScreens/profileTabBar.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
//State class
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int page = 0;
Widget buildPages(){
var index;
switch (index){
case 2:
return profileTabBar();
case 1:
return likedAlbums();
case 0:
default: 0;
return Following();
}
}
GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
#override
Widget build(BuildContext context) {
return MaterialApp( home: Scaffold(
bottomNavigationBar: CurvedNavigationBar(
key: _bottomNavigationKey,
items: <Widget>[
Icon(Icons.add, size: 30),
Icon(Icons.list, size: 30),
Icon(Icons.compare_arrows, size: 30),
],
onTap: (index) {
setState(() {
page = index;
});
},
),
body: buildPages(),
),
);
}
}
Inside of your buildPages function remove "var index" and inside switch statement replace your variable_expression to page
Widget buildPages() {
switch (page) {
case 2:
return profileTabBar();
case 1:
return likedAlbums();
case 0:
return Following();
default:
return Following();
}
}
Full code for a basic working example, which you can even paste here and see it in action
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
//State class
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int page = 0;
Widget buildPages() {
switch (page) {
case 2:
return Container(color: Colors.red);
case 1:
return Container(color: Colors.blue);
case 0:
return Container(color: Colors.green);
default:
return Container(color: Colors.green);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: "",
icon: Icon(Icons.add, size: 30),
),
BottomNavigationBarItem(
label: "",
icon: Icon(Icons.list, size: 30),
),
BottomNavigationBarItem(
label: "",
icon: Icon(Icons.compare_arrows, size: 30),
),
],
onTap: (index) {
setState(() {
page = index;
});
},
),
body: buildPages(),
),
);
}
}
Third tab is tapped
First tab is tapped

Get current route name of CupertinoTabView in Flutter?

I'm using CupertinoTabScaffold and CupertinoTabView to build navigation bottom bar in my App. For one CupertinoTabView I go to others pushed routes name, I would like to get the current name of a CupertinoTabView, but I get Null
I define the routes in main like that
CupertinoApp(
home: MyApp(),
title: 'machin',
routes: appRoutes,)
final appRoutes = {
'/pushedName': (context) => PushedName(),
};
MyApp class //
final GlobalKey<NavigatorState> profileTabNavKey =
GlobalKey<NavigatorState>();
CupertinoTabScaffold(
tabBar: CupertinoTabBar(
activeColor: Color(0xff077018),
border: Border.all(color: Color(0xffffffff)),
currentIndex: widget.currentIndex,
onTap: (index) {},
items: <BottomNavigationBarItem>[....],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: profileTabNavKey,
routes: appRoutes,
builder: (BuildContext context) =>
SettingsView());
break;
default:
return HomePage();
}
},
),
In the SettingsView I pushed a named route by using
Navigator.pushNamed(context, '/pushedName')
I tried to get the route name in the my app class by using
print(ModalRoute.of(profileTabNavKey.currentContext).settings.name);
nb: in the pushedName View i get it perfectly any help , thanks in advance
Just use the BuildContext from the build widget to get the ModalRoute data :
ModalRoute.of(context).settings.name
Working example :
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
theme: CupertinoTheme.of(context).copyWith(
brightness: Brightness.light,
),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Home',
icon: Icon(CupertinoIcons.home),
),
BottomNavigationBarItem(
label: 'Setting',
icon: Icon(CupertinoIcons.settings),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 1:
return CupertinoTabView(
routes: <String, WidgetBuilder>{
'/setting': (context) => SettingsPage(),
'/setting/2': (context) => SettingsPage(2),
'/setting/2/3': (context) => SettingsPage(3),
},
builder: (context) => SettingsPage(),
);
break;
default:
return Center(
child: Text('Home page'),
);
}
},
);
}
}
class SettingsPage extends StatelessWidget {
final int index;
SettingsPage([this.index = 1]);
#override
Widget build(BuildContext context) {
// here we go to get the current route name
print(ModalRoute.of(context).settings.name);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
middle: Text(index > 1 ? 'Settings page - $index' : 'Settings page'),
),
child: Center(
child: CupertinoButton.filled(
child: Text('Go'),
onPressed: () {
if (index == 1) {
Navigator.pushNamed(context, '/setting/2');
} else if (index == 2) {
Navigator.pushNamed(context, '/setting/2/3');
}
},
),
),
);
}
}
Go to Dartpad
I found a solution that doesn't sound like best practice, but it works!
Instead of using ModalRouter and other libraries like navigation_history_observer, I used Navigator.popUntil and blocked the popup from getting the current route from the argument and assigned it to a variable.
WillPopScope(
onWillPop: () async {
String currentRoute;
navigatorKeys[_tabController.index].currentState.popUntil((route) {
currentRoute = route.settings.name;
return true;
});
if (currentRoute == '/') {
return Future.value(false);
} else {
return !await navigatorKeys[_tabController.index]
.currentState
.maybePop();
}
},
// ...
);

How do I make BottomNavigationBar page transition with Flutter's onWillpop?

I am using BottomNavigationBar in Flutter Project.
This question is for line 30 "//TODO: back to the FirstTab".
When a user is in the SecondTab or ThirdTab and the BackButton in the Android device is pressed, I want the user to go to the FirstTab.
Now, in onWillpopScope, there is a process to pop when you can. It is used when the user is in NextPage.
Then, when the user is not in the FirstTab (SecondTab or ThirdTab) and not in the NextTab, I want to move him to the FirstTab in onWillpopScope. (I want to force the BottomNavigationBar to switch.)
How should I describe it? Please tell me.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<GlobalKey<NavigatorState>> navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
];
#override
Widget build(BuildContext context) {
int currentIndex = 0;
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab = await navigatorKeys[currentIndex].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentIndex != 0) {
//TODO: back to the FirstTab
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(label: 'Home', icon: Icon(Icons.home)),
BottomNavigationBarItem(label: 'Search', icon: Icon(Icons.search)),
BottomNavigationBarItem(label: 'Setting', icon: Icon(Icons.settings)),
],
onTap: (index) {
// back home only if not switching tab
if (currentIndex == index) {
switch (index) {
case 0:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 1:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 2:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
}
}
currentIndex = index;
},
currentIndex: currentIndex,
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
navigatorKey: navigatorKeys[index],
builder: (BuildContext context) {
switch (index) {
case 0:
return FirstTab();
case 1:
return SecondTab();
case 2:
return ThirdTab();
default:
return FirstTab();
}
},
);
},
),
),
);
}
}
class FirstTab extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('first page now'),
),
backgroundColor: Colors.red[200],
child: Center(
child: CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.of(context).push(CupertinoPageRoute(builder: (context) => NextPage()));
},
),
),
);
}
}
//Different color from Firsttab
class SecondTab extends StatelessWidget {}
//Different color from Firsttab
class ThirdTab extends StatelessWidget {}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('second page now'),
),
backgroundColor: Colors.white,
child: Center(
child: CupertinoButton(
child: const Text('Back'),
onPressed: () {
Navigator.of(context).pop();
},
),
),
);
}
}
You can try to see it docs.
I'll resume here. First you need to create a controller
final CupertinoTabController _controller = CupertinoTabController();
and add to your CupertinoTabScaffold like this
CupertinoTabScaffold(
...
controller: _controller,
)
in the end you change the page like this:
_controller.index = 0,
(this is how get and set work in Flutter)
first move
int currentIndex = 0;
to class member
next
//TODO: back to the FirstTab
setState((){
currentIndex = 0;
});

Screen navigation in flutter

Update:
The app has two stateful widget screens: Home, and Search. Both screens have search boxes and a bottom navigation.
The problem that needs to be solved is when a user taps the search box at the top of the home screen, the app should take them to the search screen without hiding the bottom navigation (just like what the eBay app does).
I have tried calling the Search class when the user taps the search box on the Home screen. And this approach works. However, the new screen hides the navigation bar at the bottom.
The following code handles the navigation between screens.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case 'Search':
return MaterialPageRoute(builder: (context) => Search());
default:
return MaterialPageRoute(builder: (context) => UserHome());
}
}),
bottomNavigationBar: BottomNavigationBar(
onTap: _onTap,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home),title: Text('Home'))
BottomNavigationBarItem(icon: Icon(Icons.search), title: Text('Search'))
],
),
);
}
void _onTap(int tappedIndex) {
setState(() => _currentIndex = tappedIndex);
switch (tappedIndex) {
case 0:
_navigatorKey.currentState.pushReplacementNamed('Home');
break;
case 1:
_navigatorKey.currentState.pushReplacementNamed('Search');
break;
}
}
}
If you are trying to do this for automated testing. You can do so using widget testing. Widget tests in flutter can simulate button taps and check for the expected output
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
bool home;
#override
void initState() {
super.initState();
home = true;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: home ? UserHome() : Search(),
bottomNavigationBar: BottomNavigationBar(
onTap: _onTap,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.search), title: Text('Search'))
],
),
);
}
void _onTap(int tappedIndex) {
setState(() {
if (tappedIndex == 0) {
home = true;
} else {
home = false;
}
});
}
}
class UserHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Center(
child: Container(
color: Colors.yellow,
child: Text('USER HOME'),
),
)
],
);
}
}
class Search extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Center(
child: Container(
color: Colors.green,
child: Text('SEARCH'),
),
)
],
);
}
}
I know this is not exactly very similar to your initial solution but it achieves the behavior you intend.