ShowCaseWidget in CupertinoNavigationBar causing exception 'Please provide ShowCaseView context'

288 views Asked by At

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.',
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}
0

There are 0 answers