Navigation Hub
Introduction
Navigation Hubs are a central place where you can manage the navigation for all your widgets. Out the box you can create bottom, top and journey navigation layouts in seconds.
Let's imagine you have an app and you want to add a bottom navigation bar and allow users to navigate between different tabs in your app.
You can use a Navigation Hub to build this.
Let's dive into how you can use a Navigation Hub in your app.
Basic Usage
You can create a Navigation Hub using the below command.
metro make:navigation_hub base
The command will walk you through an interactive setup:
- Choose a layout type - Select between
navigation_tabs(bottom navigation) orjourney_states(sequential flow). - Enter tab/state names - Provide comma-separated names for your tabs or journey states.
This will create files under your resources/pages/navigation_hubs/base/ directory:
base_navigation_hub.dart- The main hub widgettabs/orstates/- Contains the child widgets for each tab or journey state
Here's what a generated Navigation Hub looks like:
import 'package:flutter/material.dart';
import 'package:nylo_framework/nylo_framework.dart';
import '/resources/pages/navigation_hubs/base/tabs/home_tab_widget.dart';
import '/resources/pages/navigation_hubs/base/tabs/settings_tab_widget.dart';
class BaseNavigationHub extends NyStatefulWidget with BottomNavPageControls {
static RouteView path = ("/base", (_) => BaseNavigationHub());
BaseNavigationHub()
: super(
child: () => _BaseNavigationHubState(),
stateName: path.stateName());
/// State actions
static NavigationHubStateActions stateActions = NavigationHubStateActions(path.stateName());
}
class _BaseNavigationHubState extends NavigationHub<BaseNavigationHub> {
/// Layout builder
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav();
/// Should the state be maintained
@override
bool get maintainState => true;
/// The initial index
@override
int get initialIndex => 0;
/// Navigation pages
_BaseNavigationHubState() : super(() => {
0: NavigationTab.tab(title: "Home", page: HomeTab()),
1: NavigationTab.tab(title: "Settings", page: SettingsTab()),
});
/// Handle the tap event
@override
onTap(int index) {
super.onTap(index);
}
}
You can see that the Navigation Hub has two tabs, Home and Settings.
The layout method returns the layout type for the hub. It receives a BuildContext so you can access theme data and media queries when configuring your layout.
You can create more tabs by adding NavigationTab's to the Navigation Hub.
First, you need to create a new widget using Metro.
metro make:stateful_widget news_tab
You can also create multiple widgets at once.
metro make:stateful_widget news_tab,notifications_tab
Then, you can add the new widget to the Navigation Hub.
_BaseNavigationHubState() : super(() => {
0: NavigationTab.tab(title: "Home", page: HomeTab()),
1: NavigationTab.tab(title: "Settings", page: SettingsTab()),
2: NavigationTab.tab(title: "News", page: NewsTab()),
});
To use the Navigation Hub, add it to your router as the initial route:
import 'package:nylo_framework/nylo_framework.dart';
appRouter() => nyRoutes((router) {
...
router.add(BaseNavigationHub.path).initialRoute();
});
// or navigate to the Navigation Hub from anywhere in your app
routeTo(BaseNavigationHub.path);
There's a lot more you can do with a Navigation Hub, let's dive into some of the features.
Bottom Navigation
You can set the layout to a bottom navigation bar by returning NavigationHubLayout.bottomNav from the layout method.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav();
You can customize the bottom navigation bar by setting properties like the following:
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav(
backgroundColor: Colors.white,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
elevation: 8.0,
iconSize: 24.0,
selectedFontSize: 14.0,
unselectedFontSize: 12.0,
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
);
You can apply a preset style to your bottom navigation bar using the style parameter.
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav(
style: BottomNavStyle.material(), // Default Flutter material style
);
Custom Nav Bar Builder
For complete control over your navigation bar, you can use the navBarBuilder parameter.
This allows you to build any custom widget while still receiving the navigation data.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav(
navBarBuilder: (context, data) {
return MyCustomNavBar(
items: data.items,
currentIndex: data.currentIndex,
onTap: data.onTap,
);
},
);
The NavBarData object contains:
| Property | Type | Description |
|---|---|---|
items |
List<BottomNavigationBarItem> |
The navigation bar items |
currentIndex |
int |
The currently selected index |
onTap |
ValueChanged<int> |
Callback when a tab is tapped |
Here's an example of a fully custom glass nav bar:
NavigationHubLayout.bottomNav(
navBarBuilder: (context, data) {
return Padding(
padding: EdgeInsets.all(16),
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(25),
),
child: BottomNavigationBar(
items: data.items,
currentIndex: data.currentIndex,
onTap: data.onTap,
backgroundColor: Colors.transparent,
elevation: 0,
),
),
),
),
);
},
)
Note: When using
navBarBuilder, thestyleparameter is ignored.
Top Navigation
You can change the layout to a top navigation bar by returning NavigationHubLayout.topNav from the layout method.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.topNav();
You can customize the top navigation bar by setting properties like the following:
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.topNav(
backgroundColor: Colors.white,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
indicatorColor: Colors.blue,
indicatorWeight: 3.0,
isScrollable: false,
hideAppBarTitle: true,
);
Journey Navigation
You can change the layout to a journey navigation by returning NavigationHubLayout.journey from the layout method.
This is great for onboarding flows or multi-step forms.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.journey(
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.segments(),
),
);
You can also set a backgroundGradient for the journey layout:
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.journey(
backgroundGradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.linear(),
),
);
Note: When
backgroundGradientis set, it takes precedence overbackgroundColor.
If you want to use the journey navigation layout, your widgets should use JourneyState as it contains a lot of helper methods to help you manage the journey.
You can create the whole journey using the make:navigation_hub command with the journey_states layout:
metro make:navigation_hub onboarding
# Select: journey_states
# Enter: welcome, personal_info, add_photos
This will create the hub and all journey state widgets under resources/pages/navigation_hubs/onboarding/states/.
Or you can create individual journey widgets using:
metro make:journey_widget welcome,phone_number_step,add_photos_step
You can then add the new widgets to the Navigation Hub.
_MyNavigationHubState() : super(() => {
0: NavigationTab.journey(
page: Welcome(),
),
1: NavigationTab.journey(
page: PhoneNumberStep(),
),
2: NavigationTab.journey(
page: AddPhotosStep(),
),
});
Journey Progress Styles
You can customize the progress indicator style by using the JourneyProgressStyle class.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.journey(
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.linear(
activeColor: Colors.blue,
inactiveColor: Colors.grey,
thickness: 4.0,
),
),
);
You can use the following progress indicators:
JourneyProgressIndicator.none(): Renders nothing - useful for hiding the indicator on a specific tab.JourneyProgressIndicator.linear(): Linear progress bar.JourneyProgressIndicator.dots(): Dots-based progress indicator.JourneyProgressIndicator.numbered(): Numbered step progress indicator.JourneyProgressIndicator.segments(): Segmented progress bar style.JourneyProgressIndicator.circular(): Circular progress indicator.JourneyProgressIndicator.timeline(): Timeline-style progress indicator.JourneyProgressIndicator.custom(): Custom progress indicator using a builder function.
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.journey(
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.custom(
builder: (context, currentStep, totalSteps, percentage) {
return LinearProgressIndicator(
value: percentage,
backgroundColor: Colors.grey[200],
color: Colors.blue,
minHeight: 4.0,
);
},
),
),
);
You can customize the progress indicator position and padding within the JourneyProgressStyle:
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.journey(
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.dots(),
position: ProgressIndicatorPosition.bottom,
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
),
);
You can use the following progress indicator positions:
ProgressIndicatorPosition.top: Progress indicator at the top of the screen.ProgressIndicatorPosition.bottom: Progress indicator at the bottom of the screen.
Per-Tab Progress Style Override
You can override the layout-level progressStyle on individual tabs using NavigationTab.journey(progressStyle: ...). Tabs without their own progressStyle inherit the layout default. Tabs with no layout default and no per-tab style will not show a progress indicator.
_MyNavigationHubState() : super(() => {
0: NavigationTab.journey(
page: Welcome(),
),
1: NavigationTab.journey(
page: PhoneNumberStep(),
progressStyle: JourneyProgressStyle(
indicator: JourneyProgressIndicator.numbered(),
), // overrides the layout default for this tab only
),
2: NavigationTab.journey(
page: AddPhotosStep(),
),
});
JourneyState
The JourneyState class extends NyState with journey-specific functionality to make it easier to create onboarding flows and multi-step journeys.
To create a new JourneyState, you can use the below command.
metro make:journey_widget onboard_user_dob
Or if you want to create multiple widgets at once, you can use the following command.
metro make:journey_widget welcome,phone_number_step,add_photos_step
Here's what a generated JourneyState widget looks like:
import 'package:flutter/material.dart';
import '/resources/pages/navigation_hubs/onboarding/onboarding_navigation_hub.dart';
import '/resources/widgets/buttons/buttons.dart';
import 'package:nylo_framework/nylo_framework.dart';
class Welcome extends StatefulWidget {
const Welcome({super.key});
@override
createState() => _WelcomeState();
}
class _WelcomeState extends JourneyState<Welcome> {
_WelcomeState() : super(
navigationHubState: OnboardingNavigationHub.path.stateName());
@override
get init => () {
// Your initialization logic here
};
@override
Widget view(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 20),
Text('This onboarding journey will help you get started.'),
],
),
),
),
// Navigation buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!isFirstStep)
Flexible(
child: Button.textOnly(
text: "Back",
textColor: Colors.black87,
onPressed: onBackPressed,
),
)
else
const SizedBox.shrink(),
Flexible(
child: Button.primary(
text: "Continue",
onPressed: nextStep,
),
),
],
),
],
),
);
}
/// Check if the journey can continue to the next step
@override
Future<bool> canContinue() async {
return true;
}
/// Called before navigating to the next step
@override
Future<void> onBeforeNext() async {
// E.g. save data to session
}
/// Called when the journey is complete (at the last step)
@override
Future<void> onComplete() async {}
}
You'll notice that the JourneyState class uses nextStep to navigate forward and onBackPressed to go back.
The nextStep method runs through the full validation lifecycle: canContinue() -> onBeforeNext() -> navigate (or onComplete() if at last step) -> onAfterNext().
You can also use buildJourneyContent to build a structured layout with optional navigation buttons:
@override
Widget view(BuildContext context) {
return buildJourneyContent(
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 20),
Text('This onboarding journey will help you get started.'),
],
),
nextButton: Button.primary(
text: isLastStep ? "Get Started" : "Continue",
onPressed: nextStep,
),
backButton: isFirstStep ? null : Button.textOnly(
text: "Back",
textColor: Colors.black87,
onPressed: onBackPressed,
),
);
}
Here are the properties you can use in the buildJourneyContent method.
| Property | Type | Description |
|---|---|---|
content |
Widget |
The main content of the page. |
nextButton |
Widget? |
The next button widget. |
backButton |
Widget? |
The back button widget. |
contentPadding |
EdgeInsetsGeometry |
The padding for the content. |
header |
Widget? |
The header widget. |
footer |
Widget? |
The footer widget. |
crossAxisAlignment |
CrossAxisAlignment |
The cross axis alignment of the content. |
JourneyState Helper Methods
The JourneyState class has helper methods and properties that you can use to customize the behavior of your journey.
| Method / Property | Description |
|---|---|
nextStep() |
Navigate to the next step with validation. Returns Future<bool>. |
previousStep() |
Navigate to the previous step. Returns Future<bool>. |
onBackPressed() |
Simple helper for navigating to the previous step. |
onComplete() |
Called when the journey is complete (at the last step). |
onBeforeNext() |
Called before navigating to the next step. |
onAfterNext() |
Called after navigating to the next step. |
canContinue() |
Validation check before navigating to the next step. |
isFirstStep |
Returns true if this is the first step in the journey. |
isLastStep |
Returns true if this is the last step in the journey. |
currentStep |
Returns the current step index (0-based). |
totalSteps |
Returns the total number of steps. |
completionPercentage |
Returns the completion percentage (0.0 to 1.0). |
goToStep(int index) |
Jump to a specific step by index. |
goToNextStep() |
Jump to the next step (no validation). |
goToPreviousStep() |
Jump to the previous step (no validation). |
goToFirstStep() |
Jump to the first step. |
goToLastStep() |
Jump to the last step. |
exitJourney() |
Exit the journey by popping the root navigator. |
resetCurrentStep() |
Reset the current step's state. |
onJourneyComplete |
Callback when the journey completes (override in last step). |
buildJourneyPage() |
Build a full-screen journey page with Scaffold. |
nextStep
The nextStep method navigates to the next step with full validation. It runs through the lifecycle: canContinue() -> onBeforeNext() -> navigate or onComplete() -> onAfterNext().
You can pass force: true to bypass validation and directly navigate.
@override
Widget view(BuildContext context) {
return buildJourneyContent(
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...
],
),
nextButton: Button.primary(
text: isLastStep ? "Get Started" : "Continue",
onPressed: nextStep, // runs validation then navigates
),
);
}
To skip validation:
onPressed: () => nextStep(force: true),
previousStep
The previousStep method navigates to the previous step. Returns true if successful, false if already at the first step.
onPressed: () async {
bool success = await previousStep();
if (!success) {
// Already at first step
}
},
onBackPressed
The onBackPressed method is a simple helper that calls previousStep() internally.
@override
Widget view(BuildContext context) {
return buildJourneyContent(
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...
],
),
backButton: isFirstStep ? null : Button.textOnly(
text: "Back",
textColor: Colors.black87,
onPressed: onBackPressed,
),
);
}
onComplete
The onComplete method is called when nextStep() is triggered on the last step (after validation passes).
@override
Future<void> onComplete() async {
print("Journey completed");
}
onBeforeNext
The onBeforeNext method is called before navigating to the next step.
E.g. if you want to save data before navigating to the next step, you can do it here.
@override
Future<void> onBeforeNext() async {
// E.g. save data to session
// session('onboarding', {
// 'name': 'Anthony Gordon',
// 'occupation': 'Software Engineer',
// });
}
onAfterNext
The onAfterNext method is called after navigating to the next step.
@override
Future<void> onAfterNext() async {
// print('Navigated to the next step');
}
canContinue
The canContinue method is called when nextStep() is triggered. Return false to prevent navigation.
@override
Future<bool> canContinue() async {
// Perform your validation logic here
// Return true if the journey can continue, false otherwise
if (nameController.text.isEmpty) {
showToastSorry(description: "Please enter your name");
return false;
}
return true;
}
isFirstStep
The isFirstStep property returns true if this is the first step in the journey.
backButton: isFirstStep ? null : Button.textOnly(
text: "Back",
textColor: Colors.black87,
onPressed: onBackPressed,
),
isLastStep
The isLastStep property returns true if this is the last step in the journey.
nextButton: Button.primary(
text: isLastStep ? "Get Started" : "Continue",
onPressed: nextStep,
),
currentStep
The currentStep property returns the current step index (0-based).
Text("Step ${currentStep + 1} of $totalSteps"),
totalSteps
The totalSteps property returns the total number of steps in the journey.
completionPercentage
The completionPercentage property returns the completion percentage as a value from 0.0 to 1.0.
LinearProgressIndicator(value: completionPercentage),
goToStep
The goToStep method jumps directly to a specific step by index. This does not trigger validation.
nextButton: Button.primary(
text: "Skip to photos",
onPressed: () {
goToStep(2); // jump to step index 2
},
),
goToNextStep
The goToNextStep method jumps to the next step without validation. If already at the last step, it does nothing.
onPressed: () {
goToNextStep(); // skip validation and go to next step
},
goToPreviousStep
The goToPreviousStep method jumps to the previous step without validation. If already at the first step, it does nothing.
onPressed: () {
goToPreviousStep();
},
goToFirstStep
The goToFirstStep method jumps to the first step.
onPressed: () {
goToFirstStep();
},
goToLastStep
The goToLastStep method jumps to the last step.
onPressed: () {
goToLastStep();
},
exitJourney
The exitJourney method exits the journey by popping the root navigator.
onPressed: () {
exitJourney(); // pop the root navigator
},
resetCurrentStep
The resetCurrentStep method resets the current step's state.
onPressed: () {
resetCurrentStep();
},
onJourneyComplete
The onJourneyComplete getter can be overridden in the last step of your journey to define what happens when the user completes the flow.
class _CompleteStepState extends JourneyState<CompleteStep> {
_CompleteStepState() : super(
navigationHubState: OnboardingNavigationHub.path.stateName());
/// Callback when journey completes
@override
void Function()? get onJourneyComplete => () {
// Navigate to your home page or next destination
routeTo(HomePage.path);
};
@override
Widget view(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
...
Button.primary(
text: "Get Started",
onPressed: onJourneyComplete, // triggers the completion callback
),
],
),
);
}
}
buildJourneyPage
The buildJourneyPage method builds a full-screen journey page wrapped in a Scaffold with SafeArea.
@override
Widget view(BuildContext context) {
return buildJourneyPage(
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome', style: Theme.of(context).textTheme.headlineMedium),
],
),
nextButton: Button.primary(
text: "Continue",
onPressed: nextStep,
),
backgroundColor: Colors.white,
);
}
| Property | Type | Description |
|---|---|---|
content |
Widget |
The main content of the page. |
nextButton |
Widget? |
The next button widget. |
backButton |
Widget? |
The back button widget. |
contentPadding |
EdgeInsetsGeometry |
The padding for the content. |
header |
Widget? |
The header widget. |
footer |
Widget? |
The footer widget. |
backgroundColor |
Color? |
The background color of the Scaffold. |
appBar |
Widget? |
An optional AppBar widget. |
crossAxisAlignment |
CrossAxisAlignment |
The cross axis alignment of the content. |
Navigating to widgets within a tab
You can navigate to widgets within a tab by using the pushTo helper.
Inside your tab, you can use the pushTo helper to navigate to another widget.
_HomeTabState extends State<HomeTab> {
...
void _navigateToSettings() {
pushTo(SettingsPage());
}
...
}
You can also pass data to the widget you're navigating to.
_HomeTabState extends State<HomeTab> {
...
void _navigateToSettings() {
pushTo(SettingsPage(), data: {"name": "Anthony"});
}
...
}
Tabs
Tabs are the main building blocks of a Navigation Hub.
You can add tabs to a Navigation Hub using the NavigationTab class and its named constructors.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav();
...
_MyNavigationHubState() : super(() => {
0: NavigationTab.tab(
title: "Home",
page: HomeTab(),
icon: Icon(Icons.home),
activeIcon: Icon(Icons.home),
),
1: NavigationTab.tab(
title: "Settings",
page: SettingsTab(),
icon: Icon(Icons.settings),
activeIcon: Icon(Icons.settings),
),
});
In the above example, we've added two tabs to the Navigation Hub, Home and Settings.
You can use different kinds of tabs:
NavigationTab.tab()- A standard navigation tab.NavigationTab.badge()- A tab with a badge count.NavigationTab.alert()- A tab with an alert indicator.NavigationTab.journey()- A tab for journey navigation layouts.
Adding Badges to Tabs
We've made it easy to add badges to your tabs.
Badges are a great way to show users that there's something new in a tab.
Example, if you have a chat app, you can show the number of unread messages in the chat tab.
To add a badge to a tab, you can use the NavigationTab.badge constructor.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav();
...
_MyNavigationHubState() : super(() => {
0: NavigationTab.badge(
title: "Chats",
page: ChatTab(),
icon: Icon(Icons.message),
activeIcon: Icon(Icons.message),
initialCount: 10,
),
1: NavigationTab.tab(
title: "Settings",
page: SettingsTab(),
icon: Icon(Icons.settings),
activeIcon: Icon(Icons.settings),
),
});
In the above example, we've added a badge to the Chat tab with an initial count of 10.
You can also update the badge count programmatically.
/// Increment the badge count
BaseNavigationHub.stateActions.incrementBadgeCount(tab: 0);
/// Update the badge count
BaseNavigationHub.stateActions.updateBadgeCount(tab: 0, count: 5);
/// Clear the badge count
BaseNavigationHub.stateActions.clearBadgeCount(tab: 0);
By default, the badge count will be remembered. If you want to clear the badge count each session, you can set rememberCount to false.
0: NavigationTab.badge(
title: "Chats",
page: ChatTab(),
icon: Icon(Icons.message),
activeIcon: Icon(Icons.message),
initialCount: 10,
rememberCount: false,
),
Adding Alerts to Tabs
You can add alerts to your tabs.
Sometimes you might not want to show a badge count, but you want to show an alert indicator to the user.
To add an alert to a tab, you can use the NavigationTab.alert constructor.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
NavigationHubLayout? layout(BuildContext context) => NavigationHubLayout.bottomNav();
...
_MyNavigationHubState() : super(() => {
0: NavigationTab.alert(
title: "Chats",
page: ChatTab(),
icon: Icon(Icons.message),
activeIcon: Icon(Icons.message),
alertColor: Colors.red,
alertEnabled: true,
rememberAlert: false,
),
1: NavigationTab.tab(
title: "Settings",
page: SettingsTab(),
icon: Icon(Icons.settings),
activeIcon: Icon(Icons.settings),
),
});
This will add an alert to the Chat tab with a red color.
You can also update the alert programmatically.
/// Enable the alert
BaseNavigationHub.stateActions.alertEnableTab(tab: 0);
/// Disable the alert
BaseNavigationHub.stateActions.alertDisableTab(tab: 0);
Initial Index
By default, the Navigation Hub starts on the first tab (index 0). You can change this by overriding the initialIndex getter.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
int get initialIndex => 1; // Start on the second tab
...
}
Maintaining state
By default, the state of the Navigation Hub is maintained.
This means that when you navigate to a tab, the state of the tab is preserved.
If you want to clear the state of the tab each time you navigate to it, you can set maintainState to false.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
bool get maintainState => false;
...
}
onTap
You can override the onTap method to add custom logic when a tab is tapped.
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
@override
onTap(int index) {
// Add custom logic here
// E.g. track analytics, show confirmation, etc.
super.onTap(index); // Always call super to handle the tab switch
}
}
State Actions
State actions are a way to interact with the Navigation Hub from anywhere in your app.
Here are the state actions you can use:
/// Reset the tab at a given index
/// E.g. MyNavigationHub.stateActions.resetTabIndex(0);
resetTabIndex(int tabIndex);
/// Change the current tab programmatically
/// E.g. MyNavigationHub.stateActions.currentTabIndex(2);
currentTabIndex(int tabIndex);
/// Update the badge count
/// E.g. MyNavigationHub.stateActions.updateBadgeCount(tab: 0, count: 2);
updateBadgeCount({required int tab, required int count});
/// Increment the badge count
/// E.g. MyNavigationHub.stateActions.incrementBadgeCount(tab: 0);
incrementBadgeCount({required int tab});
/// Clear the badge count
/// E.g. MyNavigationHub.stateActions.clearBadgeCount(tab: 0);
clearBadgeCount({required int tab});
/// Enable the alert for a tab
/// E.g. MyNavigationHub.stateActions.alertEnableTab(tab: 0);
alertEnableTab({required int tab});
/// Disable the alert for a tab
/// E.g. MyNavigationHub.stateActions.alertDisableTab(tab: 0);
alertDisableTab({required int tab});
/// Navigate to the next page in a journey layout
/// E.g. await MyNavigationHub.stateActions.nextPage();
Future<bool> nextPage();
/// Navigate to the previous page in a journey layout
/// E.g. await MyNavigationHub.stateActions.previousPage();
Future<bool> previousPage();
To use a state action, you can do the following:
MyNavigationHub.stateActions.updateBadgeCount(tab: 0, count: 2);
MyNavigationHub.stateActions.resetTabIndex(0);
MyNavigationHub.stateActions.currentTabIndex(2); // Switch to tab 2
await MyNavigationHub.stateActions.nextPage(); // Journey: go to next page
Loading Style
Out the box, the Navigation Hub will show your default loading Widget (resources/widgets/loader_widget.dart) when the tab is loading.
You can customize the loadingStyle to update the loading style.
| Style | Description |
|---|---|
| normal | Default loading style |
| skeletonizer | Skeleton loading style |
| none | No loading style |
You can change the loading style like this:
@override
LoadingStyle get loadingStyle => LoadingStyle.normal();
// or
@override
LoadingStyle get loadingStyle => LoadingStyle.skeletonizer();
If you want to update the loading Widget in one of the styles, you can pass a child to the LoadingStyle.
@override
LoadingStyle get loadingStyle => LoadingStyle.normal(
child: Center(
child: Text("Loading..."),
),
);
Now, when the tab is loading, the text "Loading..." will be displayed.
Example below:
class _MyNavigationHubState extends NavigationHub<MyNavigationHub> {
...
_MyNavigationHubState() : super(() async {
await sleep(3); // simulate loading for 3 seconds
return {
0: NavigationTab.tab(
title: "Home",
page: HomeTab(),
),
1: NavigationTab.tab(
title: "Settings",
page: SettingsTab(),
),
};
});
@override
LoadingStyle get loadingStyle => LoadingStyle.normal(
child: Center(
child: Text("Loading..."),
),
);
...
}
Creating a Navigation Hub
To create a Navigation Hub, you can use Metro, use the below command.
metro make:navigation_hub base
The command will guide you through an interactive setup where you can choose the layout type and define your tabs or journey states.
This will create a base_navigation_hub.dart file in your resources/pages/navigation_hubs/base/ directory with child widgets organized in tabs/ or states/ subfolders.