Skip to content

Instantly share code, notes, and snippets.

@micimize
Last active April 3, 2024 19:52
Show Gist options
  • Save micimize/a4ae34ae4f551bab94ac564dd18d74a9 to your computer and use it in GitHub Desktop.
Save micimize/a4ae34ae4f551bab94ac564dd18d74a9 to your computer and use it in GitHub Desktop.
Approach for tab-local navigators in flutter. Generalized from https://stackoverflow.com/a/57627826/2234013
import 'package:flutter/material.dart';
class AppScaffold extends StatefulWidget {
final List<WidgetBuilder> pages;
final List<BottomNavigationBarItem> navItems;
const AppScaffold({
Key key,
@required this.pages,
@required this.navItems,
}) : assert(pages.length == navItems.length,
'must have an even number of pages and navItems'),
super(key: key);
@override
_AppScaffoldState createState() => _AppScaffoldState();
static _AppScaffoldState of(
BuildContext context, {
bool isNullOk = false,
}) {
assert(isNullOk != null);
assert(context != null);
final _AppScaffoldState result = context.ancestorStateOfType(
const TypeMatcher<_AppScaffoldState>(),
) as _AppScaffoldState;
if (isNullOk || result != null) {
return result;
}
throw FlutterError(
'PageContainer.of() called with a context that does not contain a PageContainer.\n',
);
}
static NavigatorState tabNavigatorOf(
BuildContext context, {
@required int tabIndex,
@required bool setToCurrent,
}) {
final scaffoldState = of(context);
if (setToCurrent &&
tabIndex != scaffoldState.tabIndex &&
scaffoldState.mounted) {
scaffoldState.setState(() {
scaffoldState.tabIndex = tabIndex;
});
}
return scaffoldState.navStates[tabIndex].currentState;
}
static void navigateTo(
BuildContext context, {
@required int tabIndex,
@required Widget widget,
}) {
tabNavigatorOf(
context,
tabIndex: tabIndex,
setToCurrent: true,
).push(
MaterialPageRoute(builder: (context) => widget),
);
}
}
class _AppScaffoldState extends State<AppScaffold> {
int tabIndex = 0;
List<GlobalKey<NavigatorState>> navStates;
@override
void initState() {
navStates = widget.pages.map((p) => GlobalKey<NavigatorState>()).toList();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: IndexedStack(
index: tabIndex,
children: widget.pages
.map(
withIndex(
(pageBuilder, index) => Navigator(
key: navStates[index],
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: pageBuilder,
),
),
),
)
.toList(),
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
onTap: onTapNav,
type: BottomNavigationBarType.fixed,
currentIndex: tabIndex,
items: widget.navItems,
),
);
}
void onTapNav(int index) {
// double tap
if (index == tabIndex) {
navStates[index].currentState.popUntil((route) => route.isFirst);
} else if (mounted) {
setState(() {
tabIndex = index;
});
}
}
}
/// helper for mapping with an index
MapFn<A, B> withIndex<A, B>(IndexedMapFn<A, B> mapFn) {
int index = -1;
return (A a) => mapFn(a, ++index);
}
@rodydavis
Copy link

Very nice!

@mrkpatchaa
Copy link

I want to try your implementation but I got this : function withIndex is not defined.

@rodydavis
Copy link

i created a package, try that one instead

@micimize
Copy link
Author

@rmkpatchaa sorry about that, withIndex is a helper I failed to add:

MapFn<A, B> withIndex<A, B>(IndexedMapFn<A, B> mapFn) {
  int index = -1;
  return (A a) => mapFn(a, ++index);
}

@micimize
Copy link
Author

Also here's @AppleEducate's scaffold_tab_bar package

@rodydavis
Copy link

Gotcha, 👍🏼

@mrkpatchaa
Copy link

Thank you guys. I understand that I can't hide the bottom navigation on specific chid route of a tab right ?

@micimize
Copy link
Author

@rmkpatchaa There probably is, because you have access to the navigation state - maybe look How to get current route path. you might want to just render something on top of the tabs though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment