Flutterでメールアプリや電話アプリを開く

先日、所属の会社でLT会があったときの発表内容です。

## やりたいこと
Flutterアプリからメールアプリや電話アプリに遷移したいです。
その際に任意のメールアドレスやタイトル、本文を設定すること、電話番号の場合も指定の電話番号を入力状態にしたいです。

このような画面でメールアプリと電話アプリをそれぞれ開くようなサンプルを作成しました。

![スクリーンショット 2020-10-30 1.24.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479957/4e76c768-edb6-e782-39ce-14b6b30bb2f4.png)

## 環境設定、Flutterのバージョン

Flutter 1.22.1

## ライブラリのインストール

今回url_launcherというライブラリを使用します。メールアプリや電話アプリを開くためだけのライブラリではなく、指定したURLを外部のブラウザアプリで開いたりすることも出来るようです。

https://pub.dev/packages/url_launcher/

pubspec.yamlに追加してインストールします。

```pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^5.7.8　# ←追加
（以下略）

```

pub getを実行しておきましょう。

```zsh
pub get
```

## 今回の使用するコード

```main.dart
import 'package:flutter/material.dart';
import 'package:open_mail_phone_sample/home_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomeScreen(),
    );
  }
}
```


```home_screen.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'mail and phone sample',
        ),
      ),
      body: Column(
        children: [
          RaisedButton(
            child: Text(
              'open phone app',
            ),
            onPressed: _openPhoneApp,
          ),
          RaisedButton(
            child: Text(
              'open mail app',
            ),
            onPressed: _openMailApp,
          ),
        ],
      ),
    );
  }

  void _openPhoneApp() {
    const tel = '06012345678';
    _launchURL(
      'tel:' + tel,
    );
  }

  void _openMailApp() async {
    final title = Uri.encodeComponent('タイトル');
    final body = Uri.encodeComponent('本文');
    const mailAddress = 'test@example.com';

    return _launchURL(
      'mailto:$mailAddress?subject=$title&body=$body',
    );
  }

  Future<void> _launchURL(String url) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      final Error error = ArgumentError('Could not launch $url');
      throw error;
    }
  }
}

```

## 実行時の注意点
エミュレーターでの実行では正常に動作しない場合があるので、実機で実行して下さい。
私の環境ではAndroidではシミュレーターでも電話アプリを開くことは確認出来ました。

## iOSでの注意点

上のコードでは、すでにiOSにも対応しているのですが、当初iOSの場合は上手く動かず困っていました。

注意点は2点です。

- 電話番号のリンクを作成するときにスペースを含めないこと
実際に私がやってしまっていたことですが、例えば以下のようにtel:の後にスペースを含めたりすると正常に動作しません。

```dart
'tel: ' + tel, // 動作しません
```

これに関してはurlなので、確かにスペースを含むべきではなかったです。。Androidではスペースがあっても開けました。

- メールアドレスのタイトルや本文に日本語を含める場合は、Uri.encodeComponentを使って文字列をエンコードしておくこと

こちらも対応しておかないとiOSで正常に動作しません。日本語を使用する場合はエンコードしましょう。
SwiftでURLに日本語が含まれているとnilが返るそうです。。

【Swift】日本語を含んだ文字列をURLやNSURLにするとnilになる
https://qiita.com/wadaaaan/items/0de9bc4ee40c8fbf38f1

日本語ではなく英語のみなどの場合は以下のようにするだけで問題なく動作します。

```dart
'mailto:$mailAddress?subject=SampleTitle&body=BodyText',
```

## 参考記事

Flutterでメーラーを起動する方法
https://qiita.com/sekitaka_1214/items/c7b3251e3d4ea1dd4f53