
A great app UI is one that minimizes the friction between a user and its functionality. An excellent way to reduce that friction is to highlight and showcase the parts of your app which are integral to its usability. This is particularly handy when a user launches your app for the first time or when you update the app with some new features.
Showcaseview is a customizable and simple to implement package you can use to show your users around the most important features of your Flutter app.
The Finished App
The finished app will look and behave like the one in the video below. When you run the app, the showcase will start instantly on the home page. It can also be started by tapping the info button in the app bar.
The showcase will highlight the most important features of our app. As the user taps on the screen, the widgets we have as part of the showcase will be presented in predefined order. This type of behavior also extends to the settings page which will have its own showcase.
When the showcase highlights the settings button in the home page, we will be able to tap on that button and be taken to the settings page where the second showcase will start. Once the showcase on the settings page ends, we can go back to the home page and continue the previous showcase from where we left off.
Getting Started
In this tutorial we are going to get to the nitty-gritty of the showcaseview package. We will get into all of the setup details, explore different customization options, learn to set up multi-page showcase functionality and more. Since we want to put our main focus on implementing the package, we are going to start with a base app UI. You can grab the starter project from the link below if you would like to follow along.
Let’s first start by adding the package dependency to our pubspec.yaml file. At the time of writing this tutorial the version is 1.1.0, it is migrated to null-safety, so you are all set to use it with the latest version of Flutter.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
showcaseview: ^1.1.0
Starter project overview
The starter project we will be working with is a UI for a water intake tracking app. The app consists of a HomePage
and a SettingsPage
.
The HomePage
displays an AppBar
with a leading help button and a settings action button that takes you to the SettingsPage
. The body has a Column
containing CupsNumberDisplay
and CupsGoal
widgets. Additionally, this page contains a FloatingActionButton
in the bottom right corner.
The SettingsPage
is simpler, featuring only a Column
with a SettingsControls
widget and some text. Since we are focusing solely on showcasing our UI elements, the functionality of the app is limited to routing from the HomePage
to the SettingsPage
.

ShowCaseWidget
Before we implement the showcase for individual widgets, we need to wrap our page that will display the showcase with a ShowCaseWidget
. We must set the required builder
parameter, which will contain the Builder
widget returning our HomePage
. Let’s go to the main.dart file and implement that now in the home
parameter of the MaterialApp
.
main.dart
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Water Tracker',
home: ShowCaseWidget(
builder: Builder(
builder: (context) => const HomePage(),
),
),
);
ShowCaseWidget optional parameters
ShowCaseWidget
contains several optional parameters. Most notably you can provide callback functions to the onStart
, onFinish
and onComplete
parameters.
The onStart
function will execute on the start of your showcase and the onFinish
will execute when all the showcase ends. onComplete
will run every time you move on from one widget in your showcase to the next.
There is also the autoPlay
parameter that can be set to true
if you want to go through showcase automatically, without the user tapping on the screen. We won’t be configuring the above-mentioned parameters in this tutorial, but it is worth it to familiarize yourself with them.
Creating Global Keys
For the ShowCaseWidget
to know which widgets we want to be showcased, we need to create a key for every one of those widgets. In our app, there are three widgets we want to bring to the attention of our users when they reach the HomePage
. Let’s now create three GlobalKey
objects right above the build
method of our _HomePageState
.
home_page.dart
class _HomePageState extends State<HomePage> {
final _key1 = GlobalKey();
final _key2 = GlobalKey();
final _key3 = GlobalKey();
...
Showcase Widgets
It’s finally time to set up our widgets! The Showcase
widget, not to be confused with the ShowCaseWidget
wraps around every widget you want to be included in your showcase. Let’s start by adding one around our settings icon in the AppBar
.
home_page.dart
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
);
},
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
shapeBorder: const CircleBorder(),
showcaseBackgroundColor: Colors.indigo,
descTextStyle: const TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16,
),
overlayPadding: const EdgeInsets.all(8),
contentPadding: const EdgeInsets.all(20),
child: const Icon(Icons.settings),
),
),
],
...
There are a few things we should note about the Showcase
widget configuration. The key
, description
and child
parameters are required, so we must provide them as we do here. We use our previously initialized _key1
here because we want this to be the first widget that is displayed in the showcase. For styling, we are providing values to shapeBorder
(shape highlighting the child), showcaseBackgroundColor
(background color of the tooltip), descTextStyle
and setting the padding for the overlay and the tooltip content.
Creating the second Showcase widget
Before we get to see what this all looks like in action, let’s add one more Showcase
widget to our code. Go ahead and place it around the Column
located in the body
of our HomePage
. For this one, we will also add a title.
home_page.dart
body: Center(
child: SingleChildScrollView(
child: Showcase(
key: _key2,
title: 'Total & Goal Water Intake',
description: 'Track your total and goal water intake',
showArrow: false,
overlayPadding: const EdgeInsets.all(8),
showcaseBackgroundColor: Colors.indigo,
textColor: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupsNumberDisplay(
size: 200,
),
const SizedBox(
height: 60,
),
CupsGoal(),
],
),
),
),
),
...
Starting the Showcase
Our ShowCaseWidget
and two of the Showcase
widgets are fully configured, but when we run the app, nothing happens. That is because we need to tell the ShowCaseWidget
when to start the showcase. Let’s explore two of the possible ways this can be done - by tapping the info button in the AppBar
and on page build.
Go on over to the info IconButton
in the AppBar
and add the following code to the button’s onPressed
parameter.
home_page.dart
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => setState(() {
ShowCaseWidget.of(context)!.startShowCase([
_key1,
_key2,
_key3,
]);
}),
icon: const Icon(
Icons.help_rounded,
),
),
...
Here we are setting the state and calling the startShowCase
method on our previously configured ShowCaseWidget
. We need to pass in a List
of previously created global keys to the startShowCase
method. The keys need to be in the order that the corresponding widgets should show up during the showcase. We are providing all three of our keys here even though we only set up two widget. The third widget will be configured later on in this tutorial.
Starting the showcase on page build
For the showcase to start on page build, we will need to call the startShowCase
method inside of the initState
of the HomePage
. Note, however, that calling this method just the way we did in the button would produce an error. To prevent this from happening, we need to place this method call inside a callback function and provide it to WidgetsBinding.instance!.addPostFrameCallback()
. This will ensure that everything is executed correctly during the build. Now, if you run the app, our beautiful showcase will start as soon as the page builds. Tap through it until the showcase is finished. Then you can tap on the help button, and it will start all over again.
home_page.dart
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback(
(_) => ShowCaseWidget.of(context)!.startShowCase(
[
_key1,
_key2,
_key3,
],
),
);
}
Showcase.withWidget - Create Fully Custom Showcases
The showcaseview package makes it really easy to create Showcase
widgets, which display an elegant tooltip next to your target widget. We can also replace the tooltip with our very own, custom widget by using the Showcase.withWidget
constructor. Let’s do just that for our FloatingActionButton
which will be our final widget in the showcase for the HomePage
.
home_page.dart
floatingActionButton: Showcase.withWidget(
key: _key3,
height: 50,
width: 50,
container: Icon(
Icons.local_drink,
size: 50,
color: Colors.blue[200],
),
shapeBorder: const CircleBorder(),
overlayPadding: const EdgeInsets.all(8),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.indigo[900],
child: const Icon(Icons.add),
),
),
...
Showcase.withWidget
has a container
parameter that accepts a Widget
. For our project we are adding a simple icon, but you can get more creative. We are also required to add height
and width
properties that will affect how the container
widget is displayed.
onTargetClick
Let’s dive into some additional capabilities of the Showcase
widget. Presently, if our showcase is running and the active widget is tapped, nothing happens. This behavior can be modified by configuring the onTargetClick
parameter. When the settings button is tapped, we want to be taken to the SettingsPage
, even if the showcase is currently running.
When using onTargetClick
we are required to set the disposeOnTap
parameter on the Showcase
widget. It accepts a boolean
value that determines whether the current showcase is disposed or continues when the target widget is tapped. Since we want to be navigated to the SettingsPage
when the widget is tapped, we will set disposeOnTap
to true
. If set to false
, in this scenario, the showcase will continue even on the new route and that is definitely not something we want. Let’s go ahead and implement all of this now.
home_page.dart
...
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
disposeOnTap: true,
onTargetClick: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
);
},
...
SettingsPage Showcase
It’s time to head over to the SettingsPage
and implement the showcase for that route. First, let’s create the ShowCaseWidget
, returning the Scaffold
from the Builder
widget. There will only be one Showcase
on this page, so we can also create the GlobalKey
for it now.
settings_page.dart
@override
Widget build(BuildContext context) {
return ShowCaseWidget(
builder: Builder(
builder: (context) {
return Scaffold(...
The showcase should start on page build, similar to how it is set up in the HomePage
, but with one difference. To call the startShowCase
method, we need to access it through the ShowCaseWidget
that is located inside our SettingsPage
build
method. For this, we need to obtain the BuildContext
of that ShowCaseWidget
. To access this context we will create a nullable variable, myContext
. We will populate it with the BuildContext
of the ShowCaseWidget
from inside the builder
function of the Builder
widget. Once this is done, the startShowCase
method can be called from inside initState
, the same way as in the HomePage
. Just make sure to use the myContext
variable in place of context
.
settings_page.dart
class _SettingsPageState extends State<SettingsPage> {
final _key1 = GlobalKey();
BuildContext? myContext;
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
ShowCaseWidget.of(myContext!)!.startShowCase([_key1]);
});
}
@override
Widget build(BuildContext context) {
return ShowCaseWidget(
builder: Builder(
builder: (context) {
myContext = context;
return Scaffold(...
Adding the SettingsPage Showcase widget
Time to finish up the new showcase by wrapping the SettingsControls
widget located in the body
of our SettingsPage
with a Showcase
.
settings_page.dart
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Showcase(
key: _key1,
title: 'Change Your Water Goal',
description:
'Increase or decrease the number of cups for your goal',
showcaseBackgroundColor: Colors.indigo,
textColor: Colors.white,
child: SettingsControls(),
),
const SizedBox(
height: 30,
),
Text(
'Adjust your water intake goal',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey[700],
fontSize: 16,
),
),
],
),
...
Final Improvements

Both showcases in our app are set up and fully functioning. There are, however, a few things we can and should improve on.
When we tap on the settings button during the HomePage
showcase, we are routed to the SettingsPage
. However, when we go back to the HomePage
, the previous showcase doesn’t continue from where we left off. Also, when we go to the SettingsPage
the showcase starts while the routing animation is still happening, which looks a little odd. Let’s go ahead and fix these now.
Continuing the showcase
We want to continue the HomePage
showcase from where we left off after routing back from the SettingsPage
. To do this, we need to add a bit more code to the onTargetClick
function we set up earlier. Since Navigator.push
returns a Future
, we can register a callback on it using .then
. From this callback we will call startShowCase
inside of setState
, this time providing only the keys for the remaining widgets.
home_page.dart
...
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
disposeOnTap: true,
onTargetClick: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
).then(
(_) {
setState(
() {
ShowCaseWidget.of(context)!.startShowCase(
[
_key2,
_key3,
],
);
},
);
},
);
},
...
Improving the routing animation
Now there is only one thing left to do. That is to ensure the SettingsPage
showcase starts only after the routing animation has finished.
We can easily do this by placing the startShowCase
method call inside a Future.delayed
. By delaying the start of the showcase a bit everything will look much better. Let’s do just that and add a delay Duration
of 400 milliseconds. After you’ve done this, test it out and see how much better it looks!
settings_page.dart
class _SettingsPageState extends State<SettingsPage> {
final _key1 = GlobalKey();
BuildContext? myContext;
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 400), () {
ShowCaseWidget.of(myContext!)!.startShowCase([_key1]);
});
});
}
Conclusion
There you have it, a fully functioning showcase customized in various ways with the routing scenario covered! Take what you’ve learned here and apply it in your own projects. The showcaseview package is certain to make your app much easier for new users to learn and navigate.
pod marani pod mar
How do we write integration test for this? “pumpAndSettle” is getting blocked.