FlutterでAndroidのIntentを受信する方法

ライブラリを使った方法

pubspec.yamlに以下を追加して

1
2
dependencies:
  receive_sharing_intent: ^1.3.1+1

インストール

1
$ flutter pub get

AndroidManifest.xmlを以下のように変更(受け取るものに応じて要変更。使い方はこちら)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- ↓ こんな感じでintent filterを追加する -->
            <intent-filter>
               <action android:name="android.intent.action.SEND" />
               <category android:name="android.intent.category.DEFAULT" />
               <data android:mimeType="text/*" />
            </intent-filter>
            <!-- ↑ -->
      </activity>

Flutter内は下記のように書くことで取得できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    // アプリがメモリ内の時
    _intentDataStreamSubscription =
        ReceiveSharingIntent.getTextStream().listen((String value) {
      setState(() {
        // valueがintentで受け取ったもの
        _sharedText = value;
      });
    }, onError: (err) {
      print("getLinkStream error: $err");
    });

    // アプリが閉じられている時
    ReceiveSharingIntent.getInitialText().then((String value) {
      setState(() {
        // 上と同じくvalueがintentで受け取ったもの
        _sharedText = value;
      });
    });

自分で実装する方法

プラットフォーム固有の処理を行う方法を知っておくために軽く見ておくと良いと思います。

* text/plainのみに対応させる形で実装してあるものです。画像等の処理は追加で記述する必要があります。

AndroidManifest.xmlはライブラリ使う時と同様の変更を行う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">

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

MainActivity.ktを下記のように変更する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.example.shared;

import android.content.Intent
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity : FlutterActivity() {

  private var sharedText: String? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
    val intent = intent
    val action = intent.action
    val type = intent.type

    // intentとmime typeの判定
    if (Intent.ACTION_SEND == action && type != null) {
      if ("text/plain" == type) {
        handleSendText(intent)
      }
    }

    MethodChannel(flutterView, "app.channel.shared.data").setMethodCallHandler { call, result ->
      if (call.method.contentEquals("getSharedText")) {
        result.success(sharedText)
        sharedText = null
      }
    }
  }

  private fun handleSendText(intent: Intent) {
    // 文字列を取り出してインスタンス変数に格納
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
  }
}

action_sendインテントを受け取り、typeがtext/plainの際に、インスタンス変数に受け取った文字列を保存できるようになっています。 MethodChannelを用いてDart側でgetSharedTextメソッドを呼び出すことで、インスタンス変数に格納した文字列を取得できるような作りです。

Flutter側の実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // 先程Kotlinで記述したメソッドを呼び出す準備
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }

  getSharedText() async {
    // 先程Kotlinで記述したメソッドの呼び出し
    // MainActivityのインスタンス変数に格納されている文字列を取り出している。
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

終わりに

Webの人なのでAndroid全然わからないのだが、PocketやInstapaperみたいに、保存時に画面遷移なくて通知(ToastやSnackbarみたいなもの)を表示する方法を知りたい。ServiceかIntentServiceを使えばできるのか?

Built with Hugo
テーマ StackJimmy によって設計されています。