概要
FlutterでInAppWebviewを利用したアプリのサンプルソースコードを紹介します。InAppWebviewは以下のpub.devで公開されているライブラリでヘッドレスWebviewなども提供してくれています。
インストール
pubspec.yamlのdependencies内に以下を追加しましょう。(最新バージョンは適宜pub.devを参照ください)
flutter_inappwebview: ^5.4.3+7
Webview上のカメラ機能などを利用する方は、以下も追加しておきましょう。
permission_handler: ^10.0.0
実装
今回はStatelessWidgetで実装していきたいと思います。(筆者がStatefulはあまり好んでいないため、、、)
まずmain内に以下を記述します。
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
await Permission.camera.request();
await Permission.microphone.request();
var swAvailable =
await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(
AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);
if (swAvailable && swInterceptAvailable) {
AndroidServiceWorkerController serviceWorkerController = AndroidServiceWorkerController.instance();
await serviceWorkerController.setServiceWorkerClient(AndroidServiceWorkerClient(
shouldInterceptRequest: (request) async {
print(request);
return null;
},
));
}
}
続いて、WebviewSampleというクラスを作成し、StatelessWidgetを継承させ以下を記述します。
大まかな概要としては画面上部に検索用のTextFiledを用意し、下にWebviewを表示させるようにしています。また、フッター部分にはBackボタンやReloadボタンなどを用意することでWebviewの画面遷移をできるようにしています。
ちなみに大部分は公式ページから取ってきているので、細かい説明は公式から読むと良いと思います。
class WebviewSample extends StatelessWidget {
WebviewSample({Key? key}) : super(key: key);
final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webViewController;
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
),
);
late PullToRefreshController pullToRefreshController;
String url = "";
double progress = 0;
final urlController = TextEditingController();
@override
Widget build(BuildContext context) {
pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.blue,
),
onRefresh: () async {
if (Platform.isAndroid) {
webViewController?.reload();
} else if (Platform.isIOS) {
webViewController?.loadUrl(urlRequest: URLRequest(url: await webViewController?.getUrl()));
}
},
);
return Scaffold(
appBar: AppBar(
title: const Text("WebViewSample"),
),
body: SafeArea(
child: Column(
children: [
TextField(
decoration: const InputDecoration(
prefixIcon: Icon(Icons.search),
),
controller: urlController,
keyboardType: TextInputType.url,
onSubmitted: (value) {
var url = Uri.parse(value);
if (url.scheme.isEmpty) {
url = Uri.parse("https://www.google.com/search?q=$value");
}
webViewController?.loadUrl(urlRequest: URLRequest(url: url));
},
),
Expanded(
child: Stack(
children: [
InAppWebView(
key: webViewKey,
initialUrlRequest: URLRequest(
url: Uri.parse("https://inappwebview.dev/"),
),
initialOptions: options,
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) {
webViewController = controller;
},
onLoadStart: (controller, url) {
this.url = url.toString();
urlController.text = this.url;
},
androidOnPermissionRequest: (controller, origin, resources) async {
return PermissionRequestResponse(
resources: resources, action: PermissionRequestResponseAction.GRANT);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (!["http", "https", "file", "chrome", "data", "javascript", "about"]
.contains(uri.scheme)) {
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async {
pullToRefreshController.endRefreshing();
this.url = url.toString();
urlController.text = this.url;
},
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();
},
onProgressChanged: (controller, progress) {
if (progress == 100) {
pullToRefreshController.endRefreshing();
}
this.progress = progress / 100;
urlController.text = url;
},
onUpdateVisitedHistory: (controller, url, androidIsReload) {
this.url = url.toString();
urlController.text = this.url;
},
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
},
),
progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(),
],
),
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
child: const Icon(Icons.arrow_back),
onPressed: () {
webViewController?.goBack();
},
),
ElevatedButton(
child: const Icon(Icons.arrow_forward),
onPressed: () {
webViewController?.goForward();
},
),
ElevatedButton(
child: const Icon(Icons.refresh),
onPressed: () {
webViewController?.reload();
},
),
],
),
],
),
),
);
}
}
カメラなどを使う場合
Webアプリ側でカメラ機能などを利用する場合は、上記に加えて各プラットフォーム上での権限設定などが必要になります。まずmainの方に以下を追加します。
await Permission.camera.request();
await Permission.microphone.request(); // マイクを使う場合のみ
続けてAndroidの権限を設定するために、AndroidManifestに以下の権限を追加します。<provider>タグの方は<applicatoin>タグ内に記述してください。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<provider
android:name="com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider"
android:authorities="${applicationId}.flutter_inappwebview.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
続けてiOSの方はInfo.plistファイルに以下を記述します。
<key>NSMicrophoneUsageDescription</key>
<string>Flutter requires acess to microphone.</string>
<key>NSCameraUsageDescription</key>
<string>Flutter requires acess to camera.</string>
string内は説明文なので適宜変えても問題ありません。
筆者は動作確認に以下のReactのDemoカメラページを利用させて頂きました。
最後に
今回はFlutterでWebviewを利用してみました。非常に使いやすいライブラリで、ますますFlutterのエコシステムが便利なことを痛感させられます。もう純粋なAndroid/Kotlinには戻れなそう。。。
以上になります。
コメント