※今のところAndroidアプリだけでしか実現できていません
9/19に開催された
Dart/Flutter入門者向けハンズオン + もくもく会
に行ってからちょっとだけDart/Flutter熱が上がったので記事書いてみました。
がっつりネイティブモバイルアプリを作ったことがなかったので、
ManifestファイルとかAndroidのライフサイクルで結構ハマりました。
そしてGradleについては謎のまま。
要学習・・・
やりたかったこと
掲題の通りです。
「Flutterで作ったアプリでChromeブラウザの「共有」から表示中のWebサイトの「ページ名」と「URL」を取得して表示させる」
です。
作ったもの
こんな感じです。
やったこと
環境構築とかその辺は省略してます
基本的に公式の What is the equivalent of an Intent in Flutter? の沿ったことをやっています。
AndroidManifest.xmlを編集
Chromeブラウザの「共有」に自分のアプリが表示されるようにする
<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が複数起動しないようにする
<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をこんな感じです。
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)からデータをもらう
- 受け取ったデータを画面に表示
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に公開すると思います。