[Flutter] Develop webview_flutter App
0. Prerequisite
- Install VSCode
- Install XCode
- Install Flutter
0.1. Introduction
User Story
- User can write URL in Top page.
- User can see the URL's page after pushing the button in Top page.
In this artile, you will build the following Flutter application:
FYI
0.2. PC Info
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. Development Environment
Editor: VSCode
Simulator: XCode(iOS)
1. Set up your Flutter environment
1.1. Create a project
You can see how to create a project from following article.
1.2. Install Flutter package
You can see how to install flutter package from following article.
You can install the package to write package name in 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
While installing the library, you will see the following result.
[flutter_webview] flutter pub get
Running "flutter pub get" in flutter_webview...
Resolving dependencies...
...
Changed 16 dependencies!
exit code 0
2. Development of the web_view App
The final application can be found on my following GitHub.
Create and develop a dart file in lib/
directory.
2.1. Directory structure
The final directory structure will look like this.
.
├── .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
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(),
);
}
}
- How to create the design: Combine widget(MaterialApp, Scaffold, Padding, etc...)
-
Application setting info(default color, top page file, etc...): In
MaterialApp()
widget
2.3. Manage the global state | provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
// the initial value of the state is empty
final provider = StateProvider((ref) => '');
- The Global State: the input form value(URL)
How to work the global state
Set up ProviderScope()
in main.dart as an entry point argument like this.
...
// entry point of the application
void main() {
runApp(const ProviderScope(child: MyApp()));
}
...
2.4. Top page | 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: 'Fill out URL',
);
} else {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const WebPage()));
}
},
child: const Text('OPEN WEB'),
),
],
),
),
);
}
}
2.4. WebView | 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. Run the application | iOS environment
3.1. Run iOS simulator
Run the following command to run the iOS simulator.
% open -a Simulator
3.2. Run the application
Run the following command to run the application.
This time, as it's done in the iOS Simulator, the following display is shown.
% 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=/
It's done. Thank you.
Discussion