1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【Flutter】enumとurl_launcherでスマートなブラウザ起動方法を実現する

Last updated at Posted at 2024-06-15

はじめに

モバイルアプリにおいてブラウザを使ってWebページを開かせたい時って結構あると思います。
つい最近の現場であったのが、設定画面で以下のウェブサイトに飛ばしたい時でした。

  • そのサービスのホームページ
  • プライバシーポリシーをのせたウェブページ
  • 利用規約をのせたウェブページ

上記の例以外にもヘルプページやお問い合わせフォームなどウェブページを開かせたいことはよくあります。
そんな時に便利なパッケージがurl_launcherです。

今回はそのurl_launcherに渡すurlと、どんな方法でブラウザを開くかを決めるLaunchModeenumを使って管理しやすくする方法を解説します。

記事の対象者

  • url_launcherを使う際に便利に扱いたい方
  • enumで動的な値をフィールドに持たせたいと悩んでいる方

記事を執筆時点での筆者の環境

[✓] Flutter (Channel stable, 3.22.1, on macOS 14.3.1 23D60 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.90.1)

今回のサンプルプロジェクト

ブラウザを開くボタンが三つあり、それぞれに違ったウェブサイトへ遷移するようになっています。
Gifだとわかりづらいですが、三つとも違った方法でブラウザを開いています。

  1. アプリ内ブラウザ
  2. アプリ外ブラウザ
  3. WebView

また、一番下のChange Flavorボタンを押すとボトムシートが立ち上がります。
そこで選択した結果によってFlavorが切り替わります。
changeLinkを開くボタンを押した場合、現在のFlavorによって開くサイトが変わるようになっています。
このFlavorによって開くサイトが変えるというのは、例えば開発環境と本番環境では接続するサーバー、APIを変える時によく使います。

url_launcher_sample.gif

ブラウザの開き方の違いについてはこちらの方が丁寧に解説されていましたので、興味がある方はご覧になってみてください。

ソースコード

補足

補足1
今回のFlavorはサンプルのため、擬似的に定義しただけのものです。
プロジェクトのデバック実行はmain関数のrunを押して行っています。

補足2
iOSとAndroidのどちらでも実行できます。

補足3
url_launcherの導入方法の解説は省略しています。
恐らく初学者の方がつまりやすい、OSごとのブラウザ設定については
こちらの方が解説されていますので参考にしてみてください。

1. enumにブラウザ起動モードをフィールドとして持たせる

以前書いたこの記事でも紹介していますがenumにはフィールド持たせることができます。

まずはfinal LaunchMode mode;というフィールドを作り、それぞれの列挙子にどのモードでブラウザを起動するかをコンストラクタで設定しています。

lib/external_links.dart

/// 外部リンク
enum ExternalLinks {
  /// Qiitaへの接続で、[mode]はアプリ **外** ブラウザで開く
  qiita._(LaunchMode.externalApplication),

  /// Zennへの接続で、[mode]はアプリ **内** ブラウザで開く
  zenn._(LaunchMode.inAppBrowserView),

  /// Flavorによって接続先が変わり、[mode]はWebViewで開く
  changeLink._(LaunchMode.inAppWebView);

  const ExternalLinks._(this.mode);

  /// [LaunchMode]はurl_luncherに定義されたenumで、URLを起動するモードを指定する
  /// enumのフィールドはコンストな値(変更不可能な値)のみを持つことができる。
  final LaunchMode mode;
}

2. urlを動的な値で設定する

肝心なurlについて上記と同じようにfinal String url;としたいところです。
enumのフィールドは複数持つことも可能です。
しかし、今回の場合そうはいきません。

ExternalLinks.qiitaExternalLinks.zennの場合は大丈夫です。
ExternalLinks.qiitaExternalLinks.zennのurlは固定の値なので設定できます。

しかしExternalLinks.changeLinkはFlavorによって接続先が変わる仕様です。
つまり、固定値ではないのです。

エラーになってしまう書き方

試しに以下のように書いてみるとエラーになります。


enum ExternalLinks {
  /// Qiitaへの接続で、[mode]はアプリ **外** ブラウザで開く
  qiita._(LaunchMode.externalApplication, 'https://qiita.com/'),

  /// Zennへの接続で、[mode]はアプリ **内** ブラウザで開く
  zenn._(LaunchMode.inAppBrowserView, 'https://zenn.dev/'),

  /// Flavorによって接続先が変わり、[mode]はWebViewで開く
  changeLink._(
    LaunchMode.inAppWebView,
    _changeLinksUrl, // <= 🔥 ここでエラーになる 🔥
  );

  const ExternalLinks._(this.mode, this.url);

  final LaunchMode mode;
  final String url;

  /// Flavorによって変わるurlを返却する
  String get _changeLinksUrl {
    switch (FlavorConfig.appFlavor) {
      case Flavor.dev:
        return 'https://www.apple.com/jp/';
      case Flavor.stg:
        return 'https://developer.android.com/?hl=ja';
      case Flavor.prod:
        return 'https://www.microsoft.com/ja-jp/';
    }
  }
}

エラー文をみると

// エラー原文
Arguments of a constant creation must be constant expressions.
Try making the argument a valid constant, or use 'new' to call the constructor.

// 和訳
定数生成の引数は定数式でなければならない。
引数を有効な定数にするか、コンストラクタの呼び出しに 'new' を使用してください。

つまり、変動する値を入れることはできないのです。そこで出てくるのがextensionです。

extensiongetterを定義する

lib/external_links.dart

/// 動的な値を持たせたい場合は、extensionを使ってゲッターで受け取る。
extension ExternalLinksExtension on ExternalLinks {
  /// 接続先のurlを返す
  String get url {
    switch (this) {
      case ExternalLinks.qiita:
        return 'https://qiita.com/';
      case ExternalLinks.zenn:
        return 'https://zenn.dev/';
      // 直接分岐を書いても良いが、わかりづらいので今回はメソッドの戻り値で受け取る
      case ExternalLinks.changeLink:
        return _getChangeLinksUrl();
    }
  }

  /// Flavorによって変わるurlを返却する
  String _getChangeLinksUrl() {
    switch (FlavorConfig.appFlavor) {
      case Flavor.dev:
        return 'https://www.apple.com/jp/';
      case Flavor.stg:
        return 'https://developer.android.com/?hl=ja';
      case Flavor.prod:
        return 'https://www.microsoft.com/ja-jp/';
    }
  }
}

コメントにもある通り、動的な値を設定するにはextensionする必要があります。
本当は動的な値を持つことができないenum拡張して、扱えるように改造しているのです。
少々紛らわしいですが上記の通りにすることで、Flavorによって返す値を変える値を受け取ることができます。

3. enumを活用して実装した使用例

ブラウザを開くメソッドの定義

lib/main.dart

  /// 引数のリンクによって開くWebサイト、ブラウザのモードを切り替える
  /// ただ、切り替えるのはあくまでExternalLinksで切り替えるようにし
  /// このメソッド内では指定されたものを使うだけにする
  Future<void> openBrowser(ExternalLinks link) async {
    final url = Uri.parse(link.url);
    try {
      if (await canLaunchUrl(url)) {
        await launchUrl(url, mode: link.mode);
      } else {
        logger.d('canLaunchUrlがfalseです');
      }
    } catch (e) {
      logger.e('エラーです', error: e);
    }
  }
  

メソッドの呼び出し側

lib/main.dart

 ElevatedButton(
              child: const Text('Qiitaを開く'),
              onPressed: () => openBrowser(ExternalLinks.qiita),
            ),

終わりに

過去のDartでは、enumにはフィールドも定義できなかったため、extensionにてgetterで定義していました。
現在のDartのバージョンではenumに直接フィールドを定義できるようになったのでextensionの必要がなくなったと思われがちです。(私はそう思っていました😇)
しかし、今回のように動的な値を扱う場合はextensionを活用する必要があります。
言語の仕様を深く理解し適切に活用することで、より管理しやすいコードを書くことができます。

この記事に関するご指摘やご意見がございましたら、ぜひコメントにてお知らせください。
この記事が誰かのお役に立てれば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?