【Flutter】Chromeブラウザの「共有」から表示している「ページ名」と「URL」を取得する

**※今のところAndroidアプリだけでしか実現できていません**

9/19に開催された
[Dart/Flutter入門者向けハンズオン + もくもく会](https://dartisans-jp.connpass.com/event/92190/)
に行ってからちょっとだけDart/Flutter熱が上がったので記事書いてみました。

がっつりネイティブモバイルアプリを作ったことがなかったので、
ManifestファイルとかAndroidのライフサイクルで結構ハマりました。
そしてGradleについては謎のまま。
要学習・・・

## やりたかったこと

掲題の通りです。
**「Flutterで作ったアプリでChromeブラウザの「共有」から表示中のWebサイトの「ページ名」と「URL」を取得して表示させる」**
です。

![Screenshot_1538928516.png](https://qiita-image-store.s3.amazonaws.com/0/164245/b577f037-789e-1f31-26d2-8d7b26e8aaca.png)


## 作ったもの

こんな感じです。

![share_reciver.gif](https://qiita-image-store.s3.amazonaws.com/0/164245/1adbf9e3-988d-f12b-0665-ca77d9b90d03.gif)


## やったこと

**環境構築とかその辺は省略してます**

基本的に公式の [What is the equivalent of an Intent in Flutter?](https://flutter.io/flutter-for-android/#what-is-the-equivalent-of-an-intent-in-flutter) の沿ったことをやっています。

### AndroidManifest.xmlを編集

#### Chromeブラウザの「共有」に自分のアプリが表示されるようにする

```xml
<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
</intent-filter>
```

上記をMainActivityの設定に追加します。
Chromeから「共有」すると INTENT.ACTION_SEND なるものが発行されるので、
それを受け取れるようになります。

#### Activityが複数起動しないようにする

```xml
<activity            
    android:launchMode="singleTask"
    android:name=".MainActivity"/>
```

上記のようにActivityの設定に android:launchMode="singleTask" を追記しました。
インテントを受け取るたびにonCreateが実行されてしまうとActivityがその分だけ作られてしまっていて困っていたのですが、ひとまずこれで解決できました。
ただ、調べていると「**SingleTask 非推奨**」なるキーワードに遭遇しておりビビっているので、これでいいのかは調査が必要。

### MainActivity.javaを編集

#### Chromeから発行された INTENT.ACTION_SEND を受け取ってDart側に渡す準備

必要なことは以下のなります。

* URLとタイトルを保持するメンバ変数を用意
* DartコードとやりとりするためのMethodChannelを用意
* Intentを受け取り、中身（ページ名、URL）を取り出す処理を用意


これらを全部盛りするとMainActivity.javaをこんな感じです。

```java
import android.content.Intent;
import android.os.Bundle;

import java.util.HashMap;
import java.util.Map;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    // URLとタイトルを保持するメンバ変数を用意
    private String url;
    private String title;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);

    // DartコードとやりとりするためのMethodChannelを用意
    new MethodChannel(getFlutterView(), "app.channel.webpage.info").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
            if (methodCall.method.contentEquals("getWebpageInfo")) {

                Map<String, String> webpage = new HashMap<>();
                webpage.put("url", url);
                webpage.put("title", title);

                result.success(webpage);

                url = null;
                title = null;
            }
        }
    });
  }

  @Override
  protected void onNewIntent(Intent intent) {
      super.onNewIntent(intent);
      intentHandler(intent);
  }

  // Intentを受け取り、中身（ページ名、URL）を取り出す処理を用意
  private void intentHandler(Intent intent) {
      String action = intent.getAction();
      String type = intent.getType();
      if (Intent.ACTION_SEND.equals(action) && type != null) {
          if ("text/plain".equals(type)) {
            Bundle bundle = intent.getExtras();
            this.url = intent.getStringExtra(Intent.EXTRA_TEXT);
            this.title = intent.getStringExtra(Intent.EXTRA_SUBJECT);
          }
      }
  }

}

```

### main.dartの編集

必要なことは以下になります

* Activityのライフサイクルで指定処理を実行できるようにする
* Java(MainActivity.java)からデータをもらう
* 受け取ったデータを画面に表示

```dart

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

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}


class _MyAppState extends State<MyApp>
    // Activityのライフサイクルで指定処理を実行できるようにする①
    with WidgetsBindingObserver {

  // Java(MainActivity.java)からデータをもらう①
  static const channel = const MethodChannel('app.channel.webpage.info');

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _urlController = new TextEditingController();
  final TextEditingController _titleController = new TextEditingController();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // Activityのライフサイクルで指定処理を実行できるようにする②
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      getWebpageInfo();
    }
  }

  void getWebpageInfo() async {

    // Java(MainActivity.java)からデータをもらう②
    var message = await  channel.invokeMethod('getWebpageInfo');
    var webpageInfo = WebpageInfo.fromMap(message);

    // 受け取った値を画面に表示
    setState(() {
      _urlController.value = TextEditingValue(text: webpageInfo.url);
      _titleController.value = TextEditingValue(text: webpageInfo.title);
    });
  }

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('「共有」から❤️'),
        ),
        body: Center(
          child: Container(
            padding: EdgeInsets.all(20.0),
            child: Form(
              key: this._formKey,
                child: ListView(
                children: <Widget>[
                  TextFormField(
                      controller: _urlController,
                      keyboardType: TextInputType
                          .url, // Use email input type for emails.
                      decoration: InputDecoration(
                          hintText: 'url', labelText: 'Share URL')),
                  TextFormField(
                      controller: _titleController,
                      keyboardType: TextInputType.text,
                      decoration: InputDecoration(
                          hintText: 'page title', labelText: 'Title')),
                  Container(
                    child: new RaisedButton(
                      child: new Text(
                        'Save',
                        style: new TextStyle(color: Colors.white),
                      ),
                      onPressed: () => null,
                      color: Colors.blue,
                    ),
                    margin: new EdgeInsets.only(top: 20.0),
                  )
                ]
              )
            ),
          )
        ),
      ),
    );
  }
}


// WebpageInfoモデル
class WebpageInfo {

  WebpageInfo._({
    this.url,
    this.title
  });

  String url = '';
  String title = '';

  static WebpageInfo fromMap(dynamic message) {
    final Map<dynamic, dynamic> map = message;
    return WebpageInfo._(
      url: map['url'],
      title: map['title']
    );
  }
}
```

こんな感じでした。

### 感想

プラグイン化してみたいな〜。
iOSは何をどうすればいいかわからないな〜。

Androidもよくわかってないしな〜。

そのうちGitHubに公開すると思います。