In this article, we're going to look at all the different ways to lay out and navigate between different features in your Flutter apps.
Flutter's pretty awesome for building apps that work everywhere – phones, computers, you name it. And a big part of making these apps user-friendly is getting navigation right.
This article won’t cover navigation between screens. Instead, we will cover options for how to design your UI to account for navigation in some common cases. So, let’s start by discussing some of the factors involved in making such a decision.
Intuition: It is very important for your app’s design to be as intuitive as possible. User’s not being able to understand your app is a big reason behind uninstalls. Onboarding and tutorial screens help, but that extra effort can be focussed elsewhere if your user already knows their way around the app.
A really simple way to make it easier for users to understand your app is by designing it in a way that’s consistent with the default apps that come with the operating system. Material components are a good way to build your app following Google’s design conventions. Similarly, Cupertino widgets are a great way to make your app look and feel like the built-in iOS apps. Having a custom design language to make your app consistent on all platforms is very tempting but may not always be the best solution.
User experience: Depending on your app, the layout you choose matters a lot when it comes to experience. If you have many screens in your app and you present the user with too many options, it can be overwhelming which results in confusion and eventually to them not using the app out of frustration. In the same way, offering users very few options up front, which results in them having to interact with nested choices, may lead to too many clicks or a lack of understanding, making your app feel either too cumbersome to use or too confusing.
Feature priority: If you have multiple features in your app, you may want users to interact with specific ones more than others; maybe these are paid features or just features that you feel add more value to the user. In cases like these, it is important to decide how to lay out your app so that the relevant items capture the user’s attention.
Let’s take a look at a couple of options and discuss when they can be useful:
This usually involves a number of tabs either on the bottom (common in iOS apps) or the top (more common in Android) of the screen that lets users switch between features easily. This pattern is best used when you have a small set of equally important features on the same screen that you want users to easily access. Bottom tab bar-based layouts are great for UX because the user’s thumb can easily reach the tabs.
In this example, we will be using a Cupertino-style tab bar. You can refer to the official Flutter documentation to know how to use the material tab bar.
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: const [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
),
]),
tabBuilder: (builderContext, position) {
return CupertinoTabView(
builder: (tabViewContext) {
return Center(
child: Text('Tab $position'),
);
},
);
},
);
}
}
This example would look like this:
Carousels are horizontal scroll-based layouts where the user swipes to switch between views. These are often used for features such as onboarding, photo galleries, etc. Carousels can be full screen or a percentage height; typically each individual view of a carousel is full width, but you can have them be slightly smaller to make it evident that there is a next item that can be scrolled to.
iOS typically has paged carousels, meaning you always fully scroll to the next item, even if the user only scrolls partially. Generally, if the user does only partially scroll to the next item, you should automatically scroll such that the item is fully in view.
When you have a few items in the carousel (for example, in your onboarding flow), it is a good idea to also add an indicator with your carousel that informs users about how many items there are and has a way to show what position they are currently at. If you have many items (let’s say a photo gallery), including an indicator will just add clutter. In this case, it’s better to add some sort of action (arrows, for example) that lets users trigger a scroll to the next item and then disable that action visually to indicate that they have reached the end.
Carousels are best used when you want to lay out more than 2-3 items (at least 3) horizontally. You should use a tab bar instead of a full-screen carousel if you have 2-3 items that are independent features.
While you can create a carousel from scratch in Flutter, you can also use a library created by someone else to implement some of these design patterns. For example, at the time of writing, carousel_slider was one of the more popular libraries on pub.dev for carousels in Flutter.
This pattern is something that is used mainly in iOS and other Apple frameworks. Segmented control involves separating your view into separate segments that the user can toggle between using some controls. This type of pattern is useful when you need to separate your layout, but the different segments all share the same context (for example, switching between missed and normal calls in the phone app).
While Flutter allows you to implement this in Android, it may not be a good idea if the rest of the app follows the normal material guidelines because it would feel out of place.
Common use cases of segment controls include filtering content, switching between different views for the same data (list and map views, for example), etc.
In this example, we use CupertinoSlidingSegmentedControl, but you can use SegmentedButton if you want a more material look for your app.
class _MyHomePageState extends State<MyHomePage> {
String selectedIndex = '1';
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
CupertinoSlidingSegmentedControl(
groupValue: selectedIndex,
children: const {
"1": Text("First"),
"2": Text("Second"),
},
onValueChanged: (value) {
if (value != null) {
setState(() {
selectedIndex = value;
});
}
},
)
],
),
),
),
);
}
}
This will render the following:
Stories is inspired by popular social media apps but have since become a popular design pattern. Stories are a popular way of showcasing content in a sequential and immersive way, useful for showing content such as tutorials, guides, product showcases, etc.
At the time of writing this article, story_view was one of the more popular libraries for adding stories like layout to Flutter apps.
There are plenty more options when it comes to design patterns for navigation UI; you can pick and choose or combine multiple ones to design really good-looking apps. The key to choosing between them is to maintain clarity and ease of use for your users while keeping the general usability of your app as intuitive as possible.