how to manage tabs in flutter application - flutter

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]),

Related

Flutter - setState does not work after pushAndRemoveUntil

I have this:
class _NavigationMenuState extends State<NavigationMenu> {
#override
Widget build(BuildContext context) {
return NavigationBar(
destinations: const [
NavigationDestination(
icon: Icon(
Icons.home,
color: Colors.white,
),
label: "Home"),
NavigationDestination(icon: Icon(Icons.forum), label: "Forums"),
],
backgroundColor: const Color.fromARGB(255, 154, 15, 5),
onDestinationSelected: (int index) {
final parentState = context.findAncestorStateOfType<BeastBurstState>();
parentState?.setState(() {
PageService.CurrentPage = index;
});
},
selectedIndex: PageService.CurrentPage,
);
}
}
setState in this specific portion of the code above:
onDestinationSelected: (int index) {
final parentState = context.findAncestorStateOfType<BeastBurstState>();
parentState?.setState(() {
PageService.CurrentPage = index;
});
},
Seems to not work when I execute swap of pages with this code:
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const HomePage()),
(Route<dynamic> route) => false);
However if I restart the app, the portion parentState?.setState(() {.... starts to work again normally.
Any idea why is that and how can I fix it ?

dart - Flutter FutureBuilder called everytime PageView swap pages causing laggy performances

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.

Using json file to give appbar logo for flutter. Facing Loading issue while navigation

I wanted to show appbar logo for all the pages from a json file. Now the issue is if I use Futurebuilder
then in every page load, appbar logo awaits before showing. I tried to use shared preference but having issues. Maybe I am doing it wrong. I am a newbie in flutter. If possible please give the answer in simple way so I can understand. Or anyone can help me by creating that part for appbar. That will be helpful.
Here is my json file for logo
import 'dart:convert';
import 'package:models/app_logo.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './home_screen.dart';
import './login_screen.dart';
import '../constants.dart';
import '../screens/courses_screen.dart';
import '../screens/my_courses_screen.dart';
import '../screens/my_wishlist_screen.dart';
import '../screens/account_screen.dart';
import '../widgets/filter_widget.dart';
import '../providers/auth.dart';
import 'package:http/http.dart' as http;
class TabsScreen extends StatefulWidget {
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
List<Widget> _pages = [
HomeScreen(),
LoginScreen(),
LoginScreen(),
LoginScreen(),
];
var _isInit = true;
var _isLoading = false;
int _selectedPageIndex = 0;
bool _isSearching = false;
final searchController = TextEditingController();
Future<AppLogo> futureLogo;
Future<AppLogo> fetchMyLogo() async {
var url = BASE_URL + '/app_logo';
try {
final response = await http.get(url);
print(response.body);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
print(response.body);
return AppLogo.fromJson(jsonDecode(response.body));
}
// print(extractedData);
} catch (error) {
throw (error);
}
}
#override
void initState() {
super.initState();
this.fetchMyLogo();
// Provider.of<Auth>(context).tryAutoLogin().then((_) {});
}
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
final _isAuth = Provider.of<Auth>(context, listen: false).isAuth;
if (_isAuth) {
_pages = [
HomeScreen(),
MyCoursesScreen(),
MyWishlistScreen(),
AccountScreen(),
];
}
}
_isInit = false;
super.didChangeDependencies();
}
void _handleSubmitted(String value) {
final searchText = searchController.text;
if (searchText.isEmpty) {
return;
}
searchController.clear();
Navigator.of(context).pushNamed(
CoursesScreen.routeName,
arguments: {
'category_id': null,
'seacrh_query': searchText,
'type': CoursesPageData.Search,
},
);
// print(searchText);
}
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: kSecondaryColor, //change your color here
),
title: !_isSearching
? FutureBuilder<AppLogo>(
future: fetchMyLogo(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Container(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text("Error Occured"),
);
} else {
return Image.network(
snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
}
}
},
)
: TextFormField(
decoration: InputDecoration(
labelText: 'Search Here',
prefixIcon: Icon(
Icons.search,
color: Colors.grey,
),
),
controller: searchController,
onFieldSubmitted: _handleSubmitted,
),
backgroundColor: kBackgroundColor,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: kSecondaryColor,
),
onPressed: () {
setState(() {
_isSearching = !_isSearching;
});
}),
],
),
body: _pages[_selectedPageIndex],
floatingActionButton: FloatingActionButton(
onPressed: () => _showFilterModal(context),
child: Icon(Icons.filter_list),
backgroundColor: kDarkButtonBg,
),
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
items: [
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.school),
title: Text('Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.shopping_basket),
title: Text('My Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.favorite_border),
title: Text('Wishlist'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.account_circle),
title: Text('Account'),
),
],
backgroundColor: kBackgroundColor,
unselectedItemColor: kSecondaryColor,
selectedItemColor: kSelectItemColor,
currentIndex: _selectedPageIndex,
type: BottomNavigationBarType.fixed,
),
);
}
}
There are some ways you can do to reduce the load time of the logo in your AppBar. Since this AppBar is common between the tabs, you should only load it once and avoid loading again every time the tab is changed.
First is to use StreamBuilder instead of FutureBuilder to reduce the number of loads.
// Create a StreamController
final _controller = StreamController<AppLogo>();
// Run fetchMyLogo() in your initState() like in your code
// In your fetchMyLogo(), add the result to the stream
fetchMyLogo() async {
// ... other lines
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var logo = AppLogo.fromJson(jsonDecode(response.body));
_controller.add(logo);
}
// Then, listen to this logo in your StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// ... other lines
title: !_isSearching
? StreamBuilder<AppLogo>(
stream: _controller.stream,
builder: (context, snapshot) {
// ... other lines
Second is to use the cached_network_image instead of Image.network, so that your logo image is cached, which reduce load time for network images.
return CachedNetworkImage(
imageUrl: snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
Small note: For each of the page in your _pages list, if you want the page to persist (not reload after every tab change), you can use the AutomaticKeepAliveClientMixin to persist the state of each page, or use IndexedStack like:
// The IndexedStack will load all pages initially
body: IndexedStack(
children: _pages,
index: _selectedPageIndex,
),
// ... other lines

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

Flutter CupertinoTabBar: cannot switch to a tab by index on a stream event

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