tutorial_coach_mark icon indicating copy to clipboard operation
tutorial_coach_mark copied to clipboard

Launching tutorial from service without a context

Open pitazzo opened this issue 2 years ago • 2 comments

I'm trying to decouple the logic of my app from the widget-side. In order to do so, I've created a TutorialService which launches the tutorial from the business logic-side. However, a BuildContext is needed in order to start the tutorial. In other situations like this (e.g. opening dialogs), getting a reference to the BuildContext using a navigator key and the navigatorKey.currentContext does the trick, bit it does not seem to work for this use case.

The problem is in the call to Overlay.of(...). I've tried following calls, and all of them return a null OverlayState:

Overlay.of(navigatorKey.currentContext, rootOverlay: false);
Overlay.of(navigatorKey.currentState!.context, rootOverlay: false);
Overlay.of(navigatorKey.currentState!.overlay!.context, rootOverlay: false);

Can this be done?

pitazzo avatar May 30 '22 16:05 pitazzo

Hi @Pitazzo ! Do you tried create the TutorialCoachMark with navigatorKey.currentContext ?

Something like this:

  TutorialCoachMark(navigatorKey.currentContext);

RafaelBarbosatec avatar Jun 20 '22 18:06 RafaelBarbosatec

hi @RafaelBarbosatec yes, that was exactly what I tried. That isn't working because of Overlay.of(...) being unable to get the OverlayState object

pitazzo avatar Jun 27 '22 10:06 pitazzo

Now you don't need create a Tutorial with context. Just need context to show. You can use the navigator context to do this.

RafaelBarbosatec avatar Sep 12 '22 21:09 RafaelBarbosatec

@pitazzo hey! Have you found a solution? I'm facing the same issue.

A quick workaround is to add Overlay to MaterialApp but it doesn't feel right

return MaterialApp(
  navigatorKey: navKey,
  builder: (context, child) =>
      Overlay(initialEntries: [OverlayEntry(builder: (context) => child!)]),
  ...
 )

Btw @RafaelBarbosatec yes indeed, we need the context just to show, but the navigator context throws the mentioned error No Overlay widget found.

Here's the full error
flutter: No Overlay widget found.
Some widgets require an Overlay widget ancestor for correct operation.
The most common way to add an Overlay to an application is to include a MaterialApp, CupertinoApp or Navigator widget in the runApp() call.
The context from which that widget was searching for an overlay was:
  Navigator-[LabeledGlobalKey<NavigatorState>#9d9d4 Key Created by default]
flutter: 
#0      Overlay.of.<anonymous closure> (package:flutter/src/widgets/overlay.dart:384:9)
#1      Overlay.of (package:flutter/src/widgets/overlay.dart:387:6)
#2      TutorialCoachMark.show.<anonymous closure> (package:tutorial_coach_mark/tutorial_coach_mark.dart:95:17)
#3      new Future.delayed.<anonymous closure> (dart:async/future.dart:424:39)
#4      _rootRun (dart:async/zone.dart:1390:47)
#5      _CustomZone.run (dart:async/zone.dart:1300:19)
#6      _CustomZone.runGuarded (dart:async/zone.dart:1208:7)
#7      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1248:23)
#8      _rootRun (dart:async/zone.dart:1398:13)
#9      _CustomZone.run (dart:async/zone.dart:1300:19)
#10     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1232:23)
#11     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#12     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
#13     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
#14     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:26)

dvagala avatar Apr 08 '23 10:04 dvagala

@RafaelBarbosatec

here is minimal reproducible example for this issue:

import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';

TargetFocus target = TargetFocus(
  identify: "Target 1",
  keyTarget: GlobalKey(),
  contents: [TargetContent()],
);

final navigatorKey = GlobalKey<NavigatorState>();

void main() {
  runApp(
    MaterialApp(
      navigatorKey: navigatorKey,
      home: const MyAppView(),
    ),
  );
}

class MyAppView extends StatelessWidget {
  const MyAppView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('tutorial_coach_mark_issue_demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'target',
              key: target.keyTarget,
            ),
            const SizedBox(height: 100),
            ElevatedButton(
              onPressed: () {
                // Doesn't work
                // Throws: No Overlay widget found.
                TutorialCoachMark(targets: [target]).show(context: navigatorKey.currentContext!);
              },
              child: const Text('show tutorial'),
            ),
          ],
        ),
      ),
    );
  }
}

I've made pull request #158 to fix this

dvagala avatar Apr 08 '23 11:04 dvagala