HomeX-It / open-mail-app-flutter

This library provides the ability to query the device for installed email apps and open those apps.
MIT License
36 stars 91 forks source link

Outdated example #41

Closed fennelhans closed 2 years ago

fennelhans commented 2 years ago

The showDialog function has changed in Flutter. (See https://api.flutter.dev/flutter/material/showDialog.html for an updated example).

When a mail app is installed (I've only been able to test on Android), it launches into it just fine. If there's no mail app, I get an error:

E/flutter ( 2814): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 4417 pos 12: '_debugIsStaticCallback(routeBuilder)': The provided routeBuilder must be a static function.
package:flutter/…/widgets/navigator.dart:4417
E/flutter ( 2814): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter ( 2814): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
E/flutter ( 2814): #2      NavigatorState.restorablePush
package:flutter/…/widgets/navigator.dart:4417
E/flutter ( 2814): #3      Screen._openMailApp
package:app/…/screens/xxx.dart:96
E/flutter ( 2814): <asynchronous suspension>
E/flutter ( 2814):

The code is as follows:

  Future<void> _openMailApp(BuildContext context) async {
    // Android: Will open mail app or show native picker.
    // iOS: Will open mail app if single mail app found.
    final result = await OpenMailApp.openMailApp();
    Route<Object?> _dialogBuilder(BuildContext context, Object? arguments) {
      return DialogRoute<void>(
        context: context,
        builder: (BuildContext context) => MailAppPickerDialog(
          mailApps: result.options,
        ),
      );
    }

    Route<Object?> _showNoMailAppsDialog(
        BuildContext context, Object? arguments) {
      return DialogRoute<void>(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text("Open Mail App"),
            content: Text("No mail apps installed"),
            actions: <Widget>[
              PrimaryButton(
                title: "OK",
                onPressed: () {
                  Navigator.pop(context);
                },
              )
            ],
          );
        },
      );
    }

    // If no mail apps found, show error
    if (!result.didOpen && !result.canOpen) {
      Navigator.of(context).restorablePush(_showNoMailAppsDialog);

      // iOS: if multiple mail apps found, show dialog to select.
      // There is no native intent/default app system in iOS so
      // you have to do it yourself.
    } else if (!result.didOpen && result.canOpen) {
      Navigator.of(context).restorablePush(_dialogBuilder);
    }
  }

Can you provide updated instructions?

Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel stable, 2.10.0, on Microsoft Windows [Version 10.0.22000.493], locale en-US) [√] Android toolchain - develop for Android devices (Android SDK version 31.0.0) [√] Chrome - develop for the web [√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.0.6) [√] Android Studio (version 2020.3) [√] VS Code, 64-bit edition (version 1.64.1) [√] Connected device (4 available) [√] HTTP Host Availability

MisterJimson commented 2 years ago

Hello! Does the example app work for you? Your code looks different compared to the example app. I'm not able to reproduce the issue in the example app.

I'm not familiar with Navigator.of(context).restorablePush or DialogRoute. From the error message it seems the problem may be with the usage of those.

fennelhans commented 2 years ago

I'll give it another pass, but I was basing this code off what is now present in the Flutter API docs.

fennelhans commented 2 years ago

image

fennelhans commented 2 years ago

Ok, I've got it working (at least on Android... I don't have a Mac to test it on iOS.) I've tested with no mail app, one mail app, and two mail apps - all three cases seem to work.

  Future<void> _openMailApp(BuildContext context) async {
    // Android: Will open mail app or show native picker.
    // iOS: Will open mail app if single mail app found.
    final result = await OpenMailApp.openMailApp();
    Route<Object?> _dialogBuilder(BuildContext context, Object? arguments) {
      return DialogRoute<void>(
        context: context,
        builder: (BuildContext context) => MailAppPickerDialog(
          mailApps: result.options,
        ),
      );
    }

    // If no mail apps found, show error
    if (!result.didOpen && !result.canOpen) {
      Navigator.of(context).restorablePush(_showNoMailAppsDialog);

      // iOS: if multiple mail apps found, show dialog to select.
      // There is no native intent/default app system in iOS so
      // you have to do it yourself.
    } else if (!result.didOpen && result.canOpen) {
      Navigator.of(context).restorablePush(_dialogBuilder);
    }
  }

  static Route<Object?> _showNoMailAppsDialog(
      BuildContext context, Object? arguments) {
    return DialogRoute<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text("Open Mail App"),
          content: Text("No mail apps installed"),
          actions: <Widget>[
            PrimaryButton(
              title: "OK",
              onPressed: () {
                Navigator.pop(context);
              },
            )
          ],
        );
      },
    );
  }

Invoking it looks like this:

Button(
  onPressed: () => {_openMailApp(context)},
),
fennelhans commented 2 years ago

I also managed to figure out how do to fix the example provided, without using my example. Notice the type next to the function, as well as the updated button.

  void showNoMailAppsDialog(BuildContext context) {
    showDialog<Dialog>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text("Open Mail App"),
          content: Text("No mail apps installed"),
          actions: <Widget>[
            TextButton(
              child: Text("OK"),
              onPressed: () {
                Navigator.pop(context);
              },
            )
          ],
        );
      },
    );
  }