I am trying to have a Showcase in my CupertinoNavigationBar, but when I navigate to the Detail page, I get an exception
Please provide ShowCaseView context
I have saved the context from the ShowCaseWidget and applied it, but I can't get it to work at all. As soon as you click on the top item to pull up the Detail page, the error happens. If you change the DEBUG_showShowcase to false so that there is no showcase in the navigationBar everything works fine. Not sure if I am doing something wrong with the CupertinoNavigationBar or ShowCaseWidget.
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:showcaseview/showcaseview.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter ShowCase',
theme: ThemeData(
primaryColor: const Color(0xffEE5366),
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: MailPage(),
),
);
}
}
class MailPage extends StatefulWidget {
const MailPage({Key? key}) : super(key: key);
@override
_MailPageState createState() => _MailPageState();
}
class _MailPageState extends State<MailPage> {
final GlobalKey _one = GlobalKey();
final GlobalKey _two = GlobalKey();
final GlobalKey _three = GlobalKey();
final GlobalKey _four = GlobalKey();
final GlobalKey _five = GlobalKey();
final GlobalKey _nav = GlobalKey();
List<Mail> mails = [];
final scrollController = ScrollController();
BuildContext? showContext;
@override
void initState() {
super.initState();
//Start showcase view after current widget frames are drawn.
//NOTE: remove ambiguate function if you are using
//flutter version greater than 3.x and direct use WidgetsBinding.instance
WidgetsBinding.instance.addPostFrameCallback(
(_) => ShowCaseWidget.of(showContext!).startShowCase([_three, _four, _five, _one]),
);
mails = [
Mail(
sender: 'Medium',
sub: 'Showcase View',
msg: 'Check new showcase View',
date: '1 May',
isUnread: false,
),
Mail(
sender: 'Quora',
sub: 'New Question for you',
msg: 'Hi, There is new question for you',
date: '2 May',
isUnread: true,
),
Mail(
sender: 'Google',
sub: 'Flutter 1.5',
msg: 'We have launched Flutter 1.5',
date: '3 May',
isUnread: false,
),
Mail(
sender: 'Github',
sub: 'Showcase View',
msg: 'New star on your showcase view.',
date: '4 May ',
isUnread: true,
),
Mail(
sender: 'Simform',
sub: 'Credit card Plugin',
msg: 'Check out our credit card plugin',
date: '5 May',
isUnread: false,
),
Mail(
sender: 'Flutter',
sub: 'Flutter is Future',
msg: 'Flutter launched for Web',
date: '6 May',
isUnread: true,
),
Mail(
sender: 'Medium',
sub: 'Showcase View',
msg: 'Check new showcase View',
date: '7 May ',
isUnread: false,
),
Mail(
sender: 'Simform',
sub: 'Credit card Plugin',
msg: 'Check out our credit card plugin',
date: '8 May',
isUnread: true,
),
Mail(
sender: 'Flutter',
sub: 'Flutter is Future',
msg: 'Flutter launched for Web',
date: '9 May',
isUnread: false,
),
];
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// I read that I needed to save off the context, not sure this is right
//flag to switch between using the showcase on the navigation bar.
bool DEBUG_showShowcase = true;
return ShowCaseWidget(
onStart: (index, key) {
log('onStart: $index, $key');
},
onComplete: (index, key) {
log('onComplete: $index, $key');
if (index == 4) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle.light.copyWith(
statusBarIconBrightness: Brightness.dark,
statusBarColor: Colors.white,
),
);
}
},
blurValue: 1,
// autoPlayDelay: const Duration(seconds: 1),
builder: Builder(
builder: (context) {
showContext = context;
return Stack(children: [
CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
middle: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
//uses flag to determine if we should add the showcase
if (DEBUG_showShowcase)
Showcase(
key: _one,
description: 'Tap to see menu options',
disableDefaultTargetGestures: true,
onBarrierClick: () => debugPrint('Barrier clicked'),
child: GestureDetector(
onTap: () => debugPrint('menu button clicked'),
child: Icon(
Icons.menu,
color: Theme.of(context).primaryColor,
),
),
),
// if we don't use the showcase, just display the icon
if (!DEBUG_showShowcase)
Icon(Icons.menu, color: Theme.of(context).primaryColor),
const SizedBox(
width: 10,
),
const Text(
'Search email',
style: TextStyle(
color: Colors.black45,
fontSize: 16,
letterSpacing: 0.4,
),
),
const Spacer(),
const Icon(
Icons.search,
color: Color(0xffADADAD),
),
],
),
),
],
),
),
child: Column(children: [
Container(
padding: const EdgeInsets.only(left: 16, top: 8),
child: const Text(
'PRIMARY',
style: TextStyle(
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
const Padding(padding: EdgeInsets.only(top: 8)),
Expanded(
child: ListView.builder(
controller: scrollController,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
if (index == 0) {
return showcaseMailTile(_three, true, context, mails.first);
}
return MailTile(
mail: mails[index % mails.length],
);
},
),
),
]),
),
Positioned(
bottom: 16,
right: 16,
child: Showcase(
key: _five,
title: 'Compose Mail',
description: 'Click here to compose mail',
targetShapeBorder: const CircleBorder(),
child: FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor,
onPressed: () {
setState(() {
/* reset ListView to ensure that the showcased widgets are
* currently rendered so the showcased keys are available in the
* render tree. */
scrollController.jumpTo(0);
ShowCaseWidget.of(showContext!)
// .startShowCase([_one, _two, _three, _four, _five]);
.startShowCase([_three, _four, _five, _one]);
});
},
child: const Icon(
Icons.add,
),
),
),
)
]);
}
),
);
}
GestureDetector showcaseMailTile(GlobalKey<State<StatefulWidget>> key,
bool showCaseDetail, BuildContext context, Mail mail) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) => const Detail(),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Showcase(
key: key,
description: 'Tap to check mail',
tooltipPosition: TooltipPosition.top,
disposeOnTap: true,
onTargetClick: () {
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (_) => const Detail(),
),
).then((_) {
setState(() {
ShowCaseWidget.of(context).startShowCase([_four, _five]);
});
});
},
child: MailTile(
mail: mail,
showCaseKey: _four,
showCaseDetail: showCaseDetail,
)),
),
);
}
}
class SAvatarExampleChild extends StatelessWidget {
const SAvatarExampleChild({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
child: Container(
width: 45,
height: 45,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffFCD8DC),
),
child: Center(
child: Text(
'S',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
);
}
}
class Mail {
Mail({
required this.sender,
required this.sub,
required this.msg,
required this.date,
required this.isUnread,
});
String sender;
String sub;
String msg;
String date;
bool isUnread;
}
class MailTile extends StatelessWidget {
const MailTile(
{required this.mail,
this.showCaseDetail = false,
this.showCaseKey,
Key? key})
: super(key: key);
final bool showCaseDetail;
final GlobalKey<State<StatefulWidget>>? showCaseKey;
final Mail mail;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 6, right: 16, top: 8, bottom: 8),
color: mail.isUnread ? const Color(0xffFFF6F7) : Colors.white,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (showCaseDetail)
Showcase.withWidget(
key: showCaseKey!,
height: 50,
width: 140,
targetShapeBorder: const CircleBorder(),
targetBorderRadius: const BorderRadius.all(
Radius.circular(150),
),
container: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 45,
height: 45,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffFCD8DC),
),
child: Center(
child: Text(
'S',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
const SizedBox(
height: 10,
),
const Text(
"Your sender's profile ",
style: TextStyle(color: Colors.white),
)
],
),
child: const SAvatarExampleChild(),
)
else
const SAvatarExampleChild(),
const Padding(padding: EdgeInsets.only(left: 8)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
mail.sender,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: mail.isUnread
? FontWeight.bold
: FontWeight.normal,
fontSize: 17,
),
),
Text(
mail.sub,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 16,
),
),
Text(
mail.msg,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.normal,
color: mail.isUnread
? Theme.of(context).primaryColor
: Colors.black,
fontSize: 15,
),
),
],
),
)
],
),
),
SizedBox(
width: 50,
child: Column(
children: <Widget>[
const SizedBox(
height: 5,
),
Text(
mail.date,
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(
height: 10,
),
Icon(
mail.isUnread ? Icons.star : Icons.star_border,
color: mail.isUnread ? const Color(0xffFBC800) : Colors.grey,
),
],
),
),
],
),
);
}
}
class Detail extends StatefulWidget {
const Detail({Key? key}) : super(key: key);
@override
_DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> {
final GlobalKey _one = GlobalKey();
BuildContext? myContext;
@override
void initState() {
super.initState();
//NOTE: remove ambiguate function if you are using
//flutter version greater than 3.x and direct use WidgetsBinding.instance
WidgetsBinding.instance.addPostFrameCallback(
(_) => Future.delayed(const Duration(milliseconds: 200), () {
ShowCaseWidget.of(myContext!).startShowCase([_one]);
}),
);
}
@override
Widget build(BuildContext context) {
return ShowCaseWidget(
builder: Builder(
builder: (context) {
// store the ShowCaseWidget context
myContext = context;
return CupertinoPageScaffold(
backgroundColor: Theme.of(context).colorScheme.background,
navigationBar: CupertinoNavigationBar(
// leading: const Icon(CupertinoIcons.left_chevron),
middle: Text('Test Page',
style: Theme.of(context).textTheme.headlineSmall,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.fade),
),
/*
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.black,
),
onPressed: () {
Navigator.pop(context);
},
),
),
*/
child: Material(
child: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: <Widget>[
Showcase(
key: _one,
title: 'Title',
description: 'Desc',
child: InkWell(
onTap: () {},
child: const Text(
'Flutter Notification',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(
height: 16,
),
const Text(
'Hi, you have new Notification from flutter group, open '
'slack and check it out',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
),
const SizedBox(
height: 16,
),
RichText(
text: const TextSpan(
style: TextStyle(
fontWeight: FontWeight.w400,
color: Colors.black,
),
children: [
TextSpan(text: 'Hi team,\n\n'),
TextSpan(
text:
'As some of you know, we’re moving to Slack for '
'our internal team communications. Slack is a '
'messaging app where we can talk, share files, '
'and work together. It also connects with tools '
'we already use, like [add your examples here], '
'plus 900+ other apps.\n\n',
),
TextSpan(
text: 'Why are we moving to Slack?\n\n',
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
TextSpan(
text:
'We want to use the best communication tools to '
'make our lives easier and be more productive. '
'Having everything in one place will help us '
'work together better and faster, rather than '
'jumping around between emails, IMs, texts and '
'a bunch of other programs. Everything you share '
'in Slack is automatically indexed and archived, '
'creating a searchable archive of all our work.',
),
],
),
),
],
),
),
),
);
},
),
);
}
}