✨
[Flutter] webview_flutter App 作成
0. 前提条件
- VSCodeのinstall
- XCodeのinstall
- Flutterのinstall
0.1. Appの完成系
User Storyは以下の通りです。
- Userは任意のURLをHOMEで書き込める。
- HOMEのButtonを押すことで任意のURLのPageを閲覧できる。
実際に完成するAppが以下の画像の通りです。
参考にさせていただいた記事は以下の記事です。
0.2. PCの情報
Software:
System Software Overview:
System Version: macOS 13.2.1 (22D68)
Kernel Version: Darwin 22.3.0
Hardware:
Hardware Overview:
Model Name: MacBook Pro
Chip: Apple M2 Pro
Memory: 32 GB
0.3. Flutterのversion
% flutter --version
Flutter 3.7.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 12cb4eb7a0 (7 days ago) • 2023-03-01 10:29:26 -0800
Engine • revision ada363ee93
Tools • Dart 2.19.3 • DevTools 2.20.1
0.4. 実行環境
Editor: VSCode
Simulator: XCode(iOS)
1. 事前準備
1.1. project setup
以下の記事を参考にしてflutter projectの作成を行います。
1.2. packageのinstall
packageのinstall手順は以下の記事を参考にしています。
pubspec.yaml
name: flutter_webview
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=2.19.3 <3.0.0'
dependencies:
adaptive_dialog: ^1.5.0
flutter:
sdk: flutter
flutter_riverpod: ^1.0.3
webview_flutter: ^3.0.1
dev_dependencies:
flutter_lints: ^1.0.0
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
libraryのinstall中には以下のような結果が出力されます。
[flutter_webview] flutter pub get
Running "flutter pub get" in flutter_webview...
Resolving dependencies...
...
Changed 16 dependencies!
exit code 0
2. Coding
最終的な完成系は以下のGitHubに載せてあります。
lib directory内にあるmain.dart
というDart fileがentry pointとなっています。
以降は、lib/
内でdart fileを作成し、開発します。
2.1. Directory構成
最終的なDirectory構成はこのようになっています。
.
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ ├── build.gradle
│ ├── gradle
│ │ └── wrapper
│ ├── gradle.properties
│ └── settings.gradle
├── ios
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── Base.lproj
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ └── xcshareddata
│ └── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
├── lib
│ ├── main.dart
│ ├── provider.dart
│ └── view
│ ├── input_page.dart
│ └── web_page.dart
├── linux
│ ├── CMakeLists.txt
│ ├── flutter
│ │ ├── CMakeLists.txt
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ ├── main.cc
│ ├── my_application.cc
│ └── my_application.h
├── macos
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── Base.lproj
│ │ ├── Configs
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ └── xcshareddata
│ └── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
├── pubspec.lock
├── pubspec.yaml
├── test
│ └── widget_test.dart
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
└── windows
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
37 directories, 67 files
2.2. Entry Point(main.dart)
main.dart
import 'package:flutter/material.dart'; // contains widgets implementing the Material Design guidelines
import 'package:flutter_riverpod/flutter_riverpod.dart'; // state management library for Flutter
import 'package:flutter_webview/view/input_page.dart'; // 'name in pubspec.yaml'/view/input_page.dart
// entry point of the application
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// It overrides the build() method to create the MaterialApp widget
Widget build(BuildContext context) {
// MaterialApp let us use material design
return MaterialApp(
// hides the debug banner in the top-right corner of the screen
debugShowCheckedModeBanner: false,
// the app title
title: 'WebView',
// Set default data in this app
theme: ThemeData(
primarySwatch: Colors.blue,
),
// top page content
home: const InputPage(),
);
}
}
- flutterの見た目の作成方法: widget(MaterialApp, Scaffold, Padding等)の組み合わせ
-
Appの基本情報設定(default color, top pageのfile等):
MaterialApp()
内
2.3. Global stateの管理(provider.dart)
provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
// the initial value of the state is empty
final provider = StateProvider((ref) => '');
- 管理しているState: Formで入力されるURL
Global stateの適用方法
main.dartで以下のようにProviderScope()
をentry pointのargumentとして設定
main.dart
...
// entry point of the application
void main() {
runApp(const ProviderScope(child: MyApp()));
}
...
2.4. Top page(input_page.dart)
input_page.dart
import 'package:adaptive_dialog/adaptive_dialog.dart'; // pre-built dialogs for different platforms
import 'package:flutter/material.dart'; // contains widgets implementing the Material Design guidelines
import 'package:flutter_riverpod/flutter_riverpod.dart'; // state management library for Flutter
import 'package:flutter_webview/provider.dart'; // 'name in pubspec.yaml'/provider.dart
import 'package:flutter_webview/view/web_page.dart'; // 'name in pubspec.yaml'/view/web_page.dart
class InputPage extends ConsumerWidget {
const InputPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
// manage text field widget
final _controller = TextEditingController();
// ref.watch: listen to changes in the state and rebuild the widget tree.
// provider.notifier: access the notifier property of the provider object.
final _provider = ref.watch(provider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text('Input Page'),
actions: [
IconButton(
onPressed: () {
_controller.text = "";
},
icon: const Icon(Icons.autorenew),
),
],
),
body: Padding(
// Set padding in body
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
TextField(
controller: _controller,
// change textfield state
onChanged: (value) {
_provider.state = value;
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
if (_provider.state == "") {
// showOkAlertDialog is in adaptive_dialog package.
showOkAlertDialog(
// context provides the context of the current widget
context: context,
title: 'URLを入力してください',
);
} else {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const WebPage()));
}
},
child: const Text('OPEN WEB'),
),
],
),
),
);
}
}
2.5. webview page(web_page.dart)
web_page.dart
import 'package:flutter/material.dart'; // contains widgets implementing the Material Design guidelines
import 'package:flutter_riverpod/flutter_riverpod.dart'; // state management library for Flutter
import 'package:flutter_webview/provider.dart'; // 'name in pubspec.yaml'/provider.dart
import 'package:webview_flutter/webview_flutter.dart'; // display web content within a Flutter app
class WebPage extends StatefulWidget {
const WebPage({Key? key}) : super(key: key);
// Manage web page state
State<WebPage> createState() => _WebPageState();
}
class _WebPageState extends State<WebPage> {
late WebViewController controller; // manage webview controller
double progress = 0; // progress of loading the web page
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('WebPage'),
actions: [
IconButton(
onPressed: () async {
if (await controller.canGoBack()) {
controller.goBack(); // go back to former page
}
},
icon: const Icon(Icons.arrow_back),
),
IconButton(
onPressed: () {
controller.reload(); // reload the web page
},
icon: const Icon(Icons.refresh),
),
],
),
body: Column(
children: [
// Setting about progress bar
LinearProgressIndicator(
value: progress,
color: Colors.red,
backgroundColor: Colors.grey,
),
Expanded(
child: Consumer(
builder: ((context, ref, child) {
// Access provider to get the web url
final _provider = ref.watch(provider.notifier);
return WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: _provider.state, // the web url in provider state
onWebViewCreated: (controller) {
this.controller = controller; // Manage the web view
},
// When the web page is loading, showing a progress bar
// that is LinearProgressIndicator
onProgress: (progress) {
setState(() {
this.progress = progress / 100;
});
},
);
}),
),
),
],
),
);
}
}
3. Appの動作確認(iOS環境)
3.1. Simulatorの起動
iOS環境のSimulatorの起動は以下のCommandを実行します。
% open -a Simulator
3.2. Appの起動
Appの起動はterminal上で以下のcommandの実行によってできます。
今回はiOSのSimulatorで行なっているため、以下の表示になります。
% flutter run
Launching lib/main.dart on iPhone 14 Pro Max in debug mode...
Running Xcode build...
└─Compiling, linking and signing... 2,712ms
Xcode build done. 5.7s
Syncing files to device iPhone 14 Pro Max... 37ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on iPhone 14 Pro Max is available at: http://127.0.0.1:64681/bFsmeUts5gE=/
The Flutter DevTools debugger and profiler on iPhone 14 Pro Max is available at: http://127.0.0.1:9101?uri=http://127.0.0.1:64681/bFsmeUts5gE=/
これで動作確認が完了です。
Discussion