はじめに
モバイルアプリにおいてブラウザを使ってWebページを開かせたい時って結構あると思います。
つい最近の現場であったのが、設定画面で以下のウェブサイトに飛ばしたい時でした。
- そのサービスのホームページ
- プライバシーポリシーをのせたウェブページ
- 利用規約をのせたウェブページ
上記の例以外にもヘルプページやお問い合わせフォームなどウェブページを開かせたいことはよくあります。
そんな時に便利なパッケージがurl_launcherです。
今回はそのurl_launcherに渡すurlと、どんな方法でブラウザを開くかを決めるLaunchMode
をenum
を使って管理しやすくする方法を解説します。
記事の対象者
- 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だとわかりづらいですが、三つとも違った方法でブラウザを開いています。
- アプリ内ブラウザ
- アプリ外ブラウザ
- WebView
また、一番下のChange Flavorボタンを押すとボトムシートが立ち上がります。
そこで選択した結果によってFlavorが切り替わります。
changeLinkを開くボタンを押した場合、現在のFlavor
によって開くサイトが変わるようになっています。
このFlavor
によって開くサイトが変えるというのは、例えば開発環境と本番環境では接続するサーバー、APIを変える時によく使います。
ブラウザの開き方の違いについてはこちらの方が丁寧に解説されていましたので、興味がある方はご覧になってみてください。
ソースコード
補足
補足1
今回のFlavor
はサンプルのため、擬似的に定義しただけのものです。
プロジェクトのデバック実行はmain関数のrunを押して行っています。
補足2
iOSとAndroidのどちらでも実行できます。
補足3
url_launcherの導入方法の解説は省略しています。
恐らく初学者の方がつまりやすい、OSごとのブラウザ設定については
こちらの方が解説されていますので参考にしてみてください。
1. enum
にブラウザ起動モードをフィールドとして持たせる
以前書いたこの記事でも紹介していますがenum
にはフィールド持たせることができます。
まずはfinal LaunchMode mode;
というフィールドを作り、それぞれの列挙子にどのモードでブラウザを起動するかをコンストラクタで設定しています。
/// 外部リンク
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.qiita
とExternalLinks.zenn
の場合は大丈夫です。
ExternalLinks.qiita
とExternalLinks.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
です。
extension
でgetter
を定義する
/// 動的な値を持たせたい場合は、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を活用して実装した使用例
ブラウザを開くメソッドの定義
/// 引数のリンクによって開く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);
}
}
メソッドの呼び出し側
ElevatedButton(
child: const Text('Qiitaを開く'),
onPressed: () => openBrowser(ExternalLinks.qiita),
),
終わりに
過去のDartでは、enum
にはフィールドも定義できなかったため、extension
にてgetter
で定義していました。
現在のDartのバージョンではenumに直接フィールドを定義できるようになったのでextension
の必要がなくなったと思われがちです。(私はそう思っていました😇)
しかし、今回のように動的な値を扱う場合はextension
を活用する必要があります。
言語の仕様を深く理解し適切に活用することで、より管理しやすいコードを書くことができます。
この記事に関するご指摘やご意見がございましたら、ぜひコメントにてお知らせください。
この記事が誰かのお役に立てれば幸いです。