Your website is already working well, compatible with mobile device? Why recreating the wheel, and simply recreate your application around the WebView feature? The idea is simple, use the native Android (or iOS) browser inside your application. It will not fix everything though, but you will be able to reuse your website without rewriting everything from scratch.
Another purpose of WebView objects is to create an interface to a platform without public API. Indeed, with WebView you will have access to a real web browser capabilities, including executing requests on a remote service, storing cookies or executing Javascript script. on Dart/Flutter, the webview_flutter package is required.
$ flutter pub add webview_flutter
Resolving dependencies...
Downloading packages...
cupertino_icons 1.0.9 (2.0.0 available)
material_color_utilities 0.13.0 (0.13.1 available)
meta 1.18.3 (1.19.0 available)
+ plugin_platform_interface 2.1.8
test_api 0.7.12 (0.7.15 available)
vector_math 2.4.2 (2.4.3 available)
+ webview_flutter 4.14.1
+ webview_flutter_android 4.14.1
+ webview_flutter_platform_interface 2.15.1
+ webview_flutter_wkwebview 3.27.0
Changed 5 dependencies!
5 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
WebView objects are objects to display web content (usually website) inside an application. This kind of object can be found on Android and on iOS, both implementation offering similar (but not perfectly identical) features. When one WebView object is instantiated, it can be used like a browser where a part of its interfaces are exposed to the application.
The best way to demonstrate this is to create a WebViewController object. The one presented in the example should do the job for now (with few modification):
The
setJavaScriptModemethod is called, it can be set todisabledorunrestricted;-
The
setNavigationDelegatemethod is called, and aNavigationDelegateobject is created. This last one is used to accept or reject the navigation changes. The following callback functions can be configured:-
onNavigationRequest: executed to accept/deny navigation; -
onPageFinished: executed when the page is fully loaded; -
onPageStarted: executed when the page loading started; -
onProgress: executed during the page loading progression; -
onWebResourceError: executed when an error occurs.
-
The
cacheClearmethod is called: use the remove the cache from theWebView;The
clearLocalStoragemethod is called: used to clear the local storage from theWebView;The
loadRequestmethod is called: used to load an URL.
WebViewController controller({
String target = 'https://authenticationtest.com/simpleFormAuth/'
}) {
// parse an URL from a string
final url = Uri.parse(target);
// Return a WebViewController Object with custom callbacks
// and parameters
return WebViewController()
// set the javascript mode to unrestricted (javascript
// can be executed)
..setJavaScriptMode(JavaScriptMode.unrestricted)
// configure the authorized navigation pattern
..setNavigationDelegate(
NavigationDelegate(
// a progression gauge can be created if necessary
onProgress: (int progress) {
print("loading in progress ($progress)...");
},
// when the connection to the page start
onPageStarted: (String url) {
print("page start to be loaded ($url).");
},
// when the page is totally loaded
onPageFinished: (String url) {
print("page loaded successfully ($url).");
showCookies();
},
// what to do in case of error
onHttpError: (HttpResponseError error) {
},
// this callback is used to select what kind of request
// is allowed (to which website).
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
}))
// when a new object is created, clear up the cache
..clearCache()
// clear also the local storage
..clearLocalStorage()
// and finally, load the requested url
..loadRequest(url);
}
With this future object created, it is possible to include it in the flutter tree. To that, one can use a WebViewWidget object, with the previous controller passed as argument.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: .center,
children: [
Container(
child: Row(children: [])
),
Expanded(child: WebViewWidget(controller: _controller == null ? controller() : _controller!))
],
),
),
);
}
Some variable states can eventually store the reference of the current controller, initialized in the initState() method. This code was defined inside a StatefulWidget Widget.
WebViewController? _controller;
@override
initState() {
super.initState();
_controller = controller();
}
Here the log output during the loading. Yeah, the authenticationtest.com was used to retrieve some session cookies.
I/flutter (19018): page start to be loaded (https://authenticationtest.com/simpleFormAuth/).
I/flutter (19018): loading in progress (20)...
I/flutter (19018): loading in progress (20)...
I/flutter (19018): loading in progress (29)...
I/flutter (19018): loading in progress (70)...
I/flutter (19018): loading in progress (87)...
I/flutter (19018): loading in progress (100)...
I/flutter (19018): loading in progress (100)...
I/flutter (19018): loading in progress (100)...
I/flutter (19018): page loaded successfully (https://authenticationtest.com/simpleFormAuth/)
The cookies from the WebView can also be retrieved (and if needed stored in a local store). This can be achieved by created a new WebViewCookieManager object. After having been successfully instantiated, the cookies can be exported using the getCookies() method.
WebViewController? _controller;
@override
initState() {
super.initState();
_controller = controller();
_cookieManager = WebViewCookieManager();
}
Future<void> showCookies() async {
final currentUrl = await _controller?.currentUrl() ?? '';
final Uri? domain = Uri.tryParse(currentUrl);
final cookies = await _cookieManager?.getCookies(
domain: domain!
);
print(cookies);
}
Here the log output when some cookies have been received from the website. Those cookies are using the WebViewCookie object, containing 4 fields, name, value, domain and path. The other cookies attributes like expires are not available on Android (but it seems they can be found on iOS).
I/flutter (19018): [WebViewCookie{name: PHPSESSID, value: 8035d936660419d25f1969de60945682, domain: https://authenticationtest.com/simpleFormAuth/, path: /}]
Here a screenshot of the WebView from the Android emulator. Few buttons have been added to do tests, like extract the cookies and save same in a permanent store, or switch to another URL.
WebView is a really interesting feature to bypass some website limitations, especially the one with public API only. Collecting those cookies can then be reused afterward. Some of you would probably ask why I was playing with this package. Well, I needed to fetch some cookies from another website (not the one used here for test), on one of my project. Having access to the cookies will help me to query my target when authenticated. Anyway, here some interesting links if you want to know more:
webview_flutterpackage from pub.dev;The official
webview_flutterAPI documentation on pub.dev;The
webview_fluttersource code from github;The official
WebViewdocumentation from the Android developer website;The official
mkWebViewdocumentation from the Apple developer website;cURL HTTP cookies documentation where the cURL cookies format is explained;
The RFC6265 where the
set-cookieis explained, containing also the official cookies attributes with their specifications;Starting with Flutter: How to embed a webview by @theotherdevs;
Converting any website into an app: Flutter webview by @franciscodex;
Not Just a WebView: Building a Native Engine on Flutter to Convert Sites to Apps with SDUI by @__2ea5fee000c;
Flutter: Two-way communication between a WebView and a web page by @diamantidis;
Is there a way to show more cookies fields from webview_flutter's WebViewCookieManager on StackOverflow, where I asked a question regarding the extra cookies attributes.
As usual, have fun and happy hacking!
The stackoverflow user who answered me on my question found a bug in the parser (I missed it), when a value is set to base64, it returns an empty string. In fact, while I was reading the code, I did not see any limits on the size of the key or the value. This should also be tested, a malicious server could eventually overflow an application with badly crafted cookies. This protected could be implemented on the WebView (Kotlin) part though and not in the flutter interface.
Anyway, I will need a way to reproduce (at least) the following bug. Notes from Mozilla documentation:
A
<cookie-name>can contain any US-ASCII characters except for control characters (ASCII characters 0 up to 31 and ASCII character 127) or separator characters (space, tab and the characters:()<>@,;:\"/[]?={}A
<cookie-value>can optionally be wrapped in double quotes and include any US-ASCII character excluding control characters (ASCII characters 0 up to 31 and ASCII character 127), Whitespace, double quotes, commas, semicolons, and backslashes.Encoding: Many implementations perform percent-encoding on cookie values. However, this is not required by the RFC specification. The percent-encoding does help to satisfy the requirements of the characters allowed for
<cookie-value>.
- set a cookie with a base64 value (e.g.
dGVzdAo=) - fetch the cookie as
WebCookie, the value returned is""" instead of"dGVzdAo="
This issue is due to the current implementation of the parser used in the AndrdoiWebViewCookieManager implementation.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
// code exfiltred from official sources
poc(String cookies) {
List<dynamic> store = [];
for (final String cookie in cookies.split('; ')) {
final List<String> cookieValue = cookie.split('=');
store.add((cookieValue.first, cookieValue.last));
}
return store;
}
void main() {
test("poc", () {
print(poc("test=dGVzdAo"));
expect(poc("test=dGVzdAo"), ("test", "dGVzdAo"));
print(poc("test=dGVzdAo="));
expect(poc("testdGVzdAo="), ("test", "dGVzdAo="));
});
}
$ flutter test
...
00:00 +0: poc
[(test, dGVzdAo)]
00:01 +0 -1: poc [E]
Expected: (String, String):<(test, dGVzdAo)>
Actual: [(String, String):(test, dGVzdAo)]
package:matcher expect
package:flutter_test/src/widget_tester.dart 473:18 expect
test/widget_test.dart 25:5 main.<fn>
...
Here a quick nginx configuration:
events {
worker_connections 2000;
}
http {
server {
listen 4001;
add_header Set-Cookie "test=dGVzdAo=; Path=/";
return 200;
}
}
Thanks to Mahendra Dhakad](https://stackoverflow.com/a/80009898/6782635) on stackoverflow.
Cover Image by Paul Skorupskas on Unsplash

Top comments (0)