Showcase and highlight your widgets using ShowCaseView
Make your Flutter application more user-friendly by using the showcaseview package. The package enables you to highlight essential widgets and functionalities. This post shows you how to install and use the package. We will go over different implementations to start the showcase automatically or with a simple tap. The showcase we will be building will be for an authentication page.

Installing
To get started, we need to install the showcaseview package into our project. The installation process is simple. Just execute the following command: flutter pub add showcaseview
.
Once the command is executed, make sure to check your pubspec.yaml
file for the added dependencies. You should see the showcaseview package included in the dependencies section, like this:
dependencies:
flutter:
sdk: flutter
showcaseview: ^2.0.3
Creating the authentication screen
In this post, we will be creating a showcase for an authentication screen. This authentication screen is a result of previous work discussed in another post. If you are interested in learning more, you can check out that related post:

However, we are also going to cover everything you need right here, including the code.
Folder structure
First, take a look at the folder structure for the authentication screen:
|-- lib/
|-- screens/
|-- authentication_screen.dart
|-- widgets/
|-- authentication_text_form_field.dart
|-- wave.dart
|-- main.dart
Code
Once you created the above folder structure including the files, you can copy the code below into each file.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/screens/authentication_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Authentication Screen',
theme: ThemeData(
primaryColor: Colors.blueAccent,
colorScheme: ThemeData().colorScheme.copyWith(
primary: Colors.blueAccent,
),
),
home: const AuthenticationScreen(),
);
}
}
authentication_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';
import 'package:flutter_authentication_screen/widgets/wave.dart';
class AuthenticationScreen extends StatefulWidget {
const AuthenticationScreen({Key? key}) : super(key: key);
@override
State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}
class _AuthenticationScreenState extends State<AuthenticationScreen> {
final GlobalKey<FormState> _formKey = GlobalKey();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final passwordConfirmationController = TextEditingController();
bool register = true;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
const Wave(),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
child: Column(
children: [
const SizedBox(height: 25),
AuthenticationTextFormField(
icon: Icons.email,
label: 'Email',
textEditingController: emailController,
),
AuthenticationTextFormField(
icon: Icons.vpn_key,
label: 'Password',
textEditingController: passwordController,
),
if (register == true)
AuthenticationTextFormField(
icon: Icons.password,
label: 'Password Confirmation',
textEditingController: passwordConfirmationController,
),
const SizedBox(height: 25),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: const StadiumBorder(),
),
onPressed: () => {},
child: Text(
register == true ? 'Register' : 'Login',
style: const TextStyle(fontSize: 17.5),
),
),
const SizedBox(height: 20),
InkWell(
onTap: () => setState(() {
register = !register;
_formKey.currentState?.reset();
}),
child: Text(
register == true ? 'Login instead' : 'Register instead',
),
)
],
),
),
)
],
),
);
}
}
authentication_text_form_field.dart
import 'package:flutter/material.dart';
class AuthenticationTextFormField extends StatelessWidget {
const AuthenticationTextFormField({
Key? key,
required this.icon,
required this.label,
required this.textEditingController,
}) : super(key: key);
final IconData icon;
final String label;
final TextEditingController textEditingController;
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textEditingController,
obscureText: label.toLowerCase().contains('password'),
decoration: InputDecoration(
floatingLabelStyle: const TextStyle(fontSize: 20),
icon: Icon(
icon,
color: Theme.of(context).primaryColor,
),
labelText: label,
),
);
}
}
wave.dart
import 'package:flutter/material.dart';
class Wave extends StatelessWidget {
const Wave({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ClipPath(
clipper: WaveClipper(),
child: Container(
color: Theme.of(context).primaryColor,
height: 200,
),
);
}
}
class WaveClipper extends CustomClipper<Path> {
@override
getClip(Size size) {
var path = Path();
path.lineTo(0, 175);
path.quadraticBezierTo(size.width * 0.25, size.height * 0.50 - 25,
size.width * 0.50, size.height * 0.75);
path.quadraticBezierTo(
size.width * 0.75, size.height + 25, size.width, size.height * 0.75);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
Once you have followed these steps and built your application, you should see the resulting screen. In this case, this will be the starting point of the showcase implementation.

Creating the showcase
To start with the implementation we need to wrap every widget we want to showcase with the Showcase widget, see the code below:
authentication_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';
import 'package:flutter_authentication_screen/widgets/wave.dart';
import 'package:showcaseview/showcaseview.dart';
class AuthenticationScreen extends StatefulWidget {
const AuthenticationScreen({Key? key}) : super(key: key);
@override
State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}
class _AuthenticationScreenState extends State<AuthenticationScreen> {
final GlobalKey<FormState> _formKey = GlobalKey();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final passwordConfirmationController = TextEditingController();
bool register = true;
final GlobalKey _emailKey = GlobalKey();
final GlobalKey _passwordKey = GlobalKey();
final GlobalKey _submitKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) =>
ShowCaseWidget.of(context).startShowCase([_emailKey, _passwordKey, _submitKey])
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
const Wave(),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
child: Column(
children: [
const SizedBox(height: 25),
Showcase(
key: _emailKey,
title: 'Email Showcase',
description: 'This is where you enter your email.',
child: AuthenticationTextFormField(
key: const Key('email'),
icon: Icons.email,
label: 'Email',
textEditingController: emailController,
),
),
Showcase(
key: _passwordKey,
title: 'Password Showcase',
description: 'This is where you enter your password.',
child: AuthenticationTextFormField(
key: const Key('password'),
icon: Icons.vpn_key,
label: 'Password',
textEditingController: passwordController,
),
),
if (register == true)
AuthenticationTextFormField(
key: const Key('password_confirmation'),
icon: Icons.password,
label: 'Password Confirmation',
textEditingController: passwordConfirmationController,
),
const SizedBox(height: 25),
Showcase(
key: _submitKey,
title: 'Authentication Button',
description: 'Tap here to register or login.',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: const StadiumBorder(),
),
onPressed: () => {},
child: Text(
register == true ? 'Register' : 'Login',
style: const TextStyle(fontSize: 17.5),
),
),
),
const SizedBox(height: 20),
InkWell(
onTap: () => setState(() {
register = !register;
}),
child: Text(
register == true ? 'Login instead' : 'Register instead',
),
)
],
),
),
)
],
),
),
);
}
}
In this code snippet, we updated the AuthenticationScreen
widget to use the showcaseview package to provide a showcase of three widgets of the authentication screen. The following changes have been made:
1. Added showcaseview import: This import is necessary to use the showcaseview package.
import 'package:showcaseview/showcaseview.dart';
2. Added key variables: _emailKey
, _passwordKey
, _submitKey
: These keys are used to identify the widgets that will be showcased. Each key corresponds to a specific widget on the screen.
final GlobalKey _emailKey = GlobalKey();
final GlobalKey _passwordKey = GlobalKey();
final GlobalKey _submitKey = GlobalKey();
3. Added initstate method: The initState
method has been added to the _AuthenticationScreenState
class. It is responsible for starting the showcase after the screen has been built. It uses the ShowCaseWidget.of(context).startShowCase
method to initiate the showcase and pass in the keys of the widgets to be showcased.
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) =>
ShowCaseWidget.of(context).startShowCase([_emailKey, _passwordKey, _submitKey])
);
}
4. Wrapped widgets with Showcase widget: The Showcase
widget has been wrapped around certain widgets, such as the email field, password field, and authentication button. This wraps these widgets with the showcase effect, and the package will highlight and provide information about them. Each Showcase
widget includes a key
, title
and description
property. The key
is needed to start the Showcase
widget and the title
and description
will be displayed during the showcase, explaining the purpose of the widget to the user.
Email and Password field
Showcase(
key: _emailKey,
title: 'Email Showcase',
description: 'This is where you enter your email.',
child: AuthenticationTextFormField(
key: const Key('email'),
icon: Icons.email,
label: 'Email',
textEditingController: emailController,
),
),
Showcase(
key: _passwordKey,
title: 'Password Showcase',
description: 'This is where you enter your password.',
child: AuthenticationTextFormField(
key: const Key('password'),
icon: Icons.vpn_key,
label: 'Password',
textEditingController: passwordController,
),
),
Authentication button
Showcase(
key: _submitKey,
title: 'Authentication Button',
description: 'Tap here to register or login.',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: const StadiumBorder(),
),
onPressed: () => {},
child: Text(
register == true ? 'Register' : 'Login',
style: const TextStyle(fontSize: 17.5),
),
),
),
This concludes the changes to the AuthenticationScreen
, however, we still need to make some changes to the main.dart
file.
main.dart
In the main.dart
file we have to make sure that our AuthenticationScreen
widget has access to the context of the ShowCaseWidget
, see the code below:
import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/screens/authentication_screen.dart';
import 'package:showcaseview/showcaseview.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Authentication Screen',
theme: ThemeData(
primaryColor: Colors.blueAccent,
colorScheme: ThemeData().colorScheme.copyWith(
primary: Colors.blueAccent,
error: Colors.redAccent,
),
),
home: ShowCaseWidget(
builder: Builder(
builder: (BuildContext buildContext) => const AuthenticationScreen()
),
),
);
}
}
In this code snippet, we wrap our AuthenticationScreen
widget with the ShowCaseWidget
. The ShowCaseWidget
takes a builder
. On this builder
property we assigned a Builder
and build our AuthenticationScreen
widget.

Starting the showcase manually
So far we have created an implementation that will automatically start the showcase. It is also possible to start the showcase manually:
Align(
alignment: Alignment.topRight,
child: InkWell(
onTap: () => ShowCaseWidget.of(context).startShowCase([_emailKey, _passwordKey, _submitKey]),
child: Icon(
Icons.question_mark,
color: Theme.of(context).primaryColor,
),
),
),
In this code snippet, we created a small clickable Icon
widget at the top-right corner. When you tap the icon it will start the showcase using the ShowCaseWidget.of(context).startShowCase
function.

Conclusion
In this post, we went over the showcaseview package to make our Flutter application more user-friendly. It helps to highlight important parts of your application for users. We covered how to install it and how to use it in different ways. We focused on an example where we improved an authentication screen.
We talked about the steps to set up the screen and the code needed for that. We explored how to make the showcase for the email field, password field, and login button. Finally, we discussed how to start the showcase either automatically or when a user taps an icon.