[webview_flutter] Android implementation of `loadFlutterAsset` method. (#4581)
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index caee5f7..e8d9e63 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.6.0
+
+* Adds implementation of the `loadFlutterAsset` method from the platform interface.
+
## 2.5.0
* Adds an option to set the background color of the webview.
diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle
index e70d4e6..37954b3 100644
--- a/packages/webview_flutter/webview_flutter_android/android/build.gradle
+++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle
@@ -58,8 +58,4 @@
}
}
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java
new file mode 100644
index 0000000..1d484d8
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java
@@ -0,0 +1,108 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import android.content.res.AssetManager;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.PluginRegistry;
+import java.io.IOException;
+
+/** Provides access to the assets registered as part of the App bundle. */
+abstract class FlutterAssetManager {
+ final AssetManager assetManager;
+
+ /**
+ * Constructs a new instance of the {@link FlutterAssetManager}.
+ *
+ * @param assetManager Instance of Android's {@link AssetManager} used to access assets within the
+ * App bundle.
+ */
+ public FlutterAssetManager(AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ /**
+ * Gets the relative file path to the Flutter asset with the given name, including the file's
+ * extension, e.g., "myImage.jpg".
+ *
+ * <p>The returned file path is relative to the Android app's standard asset's directory.
+ * Therefore, the returned path is appropriate to pass to Android's AssetManager, but the path is
+ * not appropriate to load as an absolute path.
+ */
+ abstract String getAssetFilePathByName(String name);
+
+ /**
+ * Returns a String array of all the assets at the given path.
+ *
+ * @param path A relative path within the assets, i.e., "docs/home.html". This value cannot be
+ * null.
+ * @return String[] Array of strings, one for each asset. These file names are relative to 'path'.
+ * This value may be null.
+ * @throws IOException Throws an IOException in case I/O operations were interrupted.
+ */
+ public String[] list(@NonNull String path) throws IOException {
+ return assetManager.list(path);
+ }
+
+ /**
+ * Provides access to assets using the {@link PluginRegistry.Registrar} for looking up file paths
+ * to Flutter assets.
+ *
+ * @deprecated The {@link RegistrarFlutterAssetManager} is for Flutter's v1 embedding. For
+ * instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit
+ * http://flutter.dev/go/android-plugin-migration
+ */
+ @Deprecated
+ static class RegistrarFlutterAssetManager extends FlutterAssetManager {
+ final PluginRegistry.Registrar registrar;
+
+ /**
+ * Constructs a new instance of the {@link RegistrarFlutterAssetManager}.
+ *
+ * @param assetManager Instance of Android's {@link AssetManager} used to access assets within
+ * the App bundle.
+ * @param registrar Instance of {@link io.flutter.plugin.common.PluginRegistry.Registrar} used
+ * to look up file paths to assets registered by Flutter.
+ */
+ RegistrarFlutterAssetManager(AssetManager assetManager, PluginRegistry.Registrar registrar) {
+ super(assetManager);
+ this.registrar = registrar;
+ }
+
+ @Override
+ public String getAssetFilePathByName(String name) {
+ return registrar.lookupKeyForAsset(name);
+ }
+ }
+
+ /**
+ * Provides access to assets using the {@link FlutterPlugin.FlutterAssets} for looking up file
+ * paths to Flutter assets.
+ */
+ static class PluginBindingFlutterAssetManager extends FlutterAssetManager {
+ final FlutterPlugin.FlutterAssets flutterAssets;
+
+ /**
+ * Constructs a new instance of the {@link PluginBindingFlutterAssetManager}.
+ *
+ * @param assetManager Instance of Android's {@link AssetManager} used to access assets within
+ * the App bundle.
+ * @param flutterAssets Instance of {@link
+ * io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets} used to look up file
+ * paths to assets registered by Flutter.
+ */
+ PluginBindingFlutterAssetManager(
+ AssetManager assetManager, FlutterPlugin.FlutterAssets flutterAssets) {
+ super(assetManager);
+ this.flutterAssets = flutterAssets;
+ }
+
+ @Override
+ public String getAssetFilePathByName(String name) {
+ return flutterAssets.getAssetFilePathByName(name);
+ }
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java
new file mode 100644
index 0000000..791912a
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import android.webkit.WebView;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Host api implementation for {@link WebView}.
+ *
+ * <p>Handles creating {@link WebView}s that intercommunicate with a paired Dart object.
+ */
+public class FlutterAssetManagerHostApiImpl implements FlutterAssetManagerHostApi {
+ final FlutterAssetManager flutterAssetManager;
+
+ /** Constructs a new instance of {@link FlutterAssetManagerHostApiImpl}. */
+ public FlutterAssetManagerHostApiImpl(FlutterAssetManager flutterAssetManager) {
+ this.flutterAssetManager = flutterAssetManager;
+ }
+
+ @Override
+ public List<String> list(String path) {
+ try {
+ String[] paths = flutterAssetManager.list(path);
+
+ if (paths == null) {
+ return new ArrayList<>();
+ }
+
+ return Arrays.asList(paths);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex.getMessage());
+ }
+ }
+
+ @Override
+ public String getAssetFilePathByName(String name) {
+ return flutterAssetManager.getAssetFilePathByName(name);
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index a5632d3..0123790 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/** Generated class from Pigeon. */
@@ -1915,6 +1916,84 @@
}
}
+ private static class FlutterAssetManagerHostApiCodec extends StandardMessageCodec {
+ public static final FlutterAssetManagerHostApiCodec INSTANCE =
+ new FlutterAssetManagerHostApiCodec();
+
+ private FlutterAssetManagerHostApiCodec() {}
+ }
+
+ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+ public interface FlutterAssetManagerHostApi {
+ List<String> list(String path);
+
+ String getAssetFilePathByName(String name);
+
+ /** The codec used by FlutterAssetManagerHostApi. */
+ static MessageCodec<Object> getCodec() {
+ return FlutterAssetManagerHostApiCodec.INSTANCE;
+ }
+
+ /**
+ * Sets up an instance of `FlutterAssetManagerHostApi` to handle messages through the
+ * `binaryMessenger`.
+ */
+ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi api) {
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger, "dev.flutter.pigeon.FlutterAssetManagerHostApi.list", getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map<String, Object> wrapped = new HashMap<>();
+ try {
+ ArrayList<Object> args = (ArrayList<Object>) message;
+ String pathArg = (String) args.get(0);
+ if (pathArg == null) {
+ throw new NullPointerException("pathArg unexpectedly null.");
+ }
+ List<String> output = api.list(pathArg);
+ wrapped.put("result", output);
+ } catch (Error | RuntimeException exception) {
+ wrapped.put("error", wrapError(exception));
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map<String, Object> wrapped = new HashMap<>();
+ try {
+ ArrayList<Object> args = (ArrayList<Object>) message;
+ String nameArg = (String) args.get(0);
+ if (nameArg == null) {
+ throw new NullPointerException("nameArg unexpectedly null.");
+ }
+ String output = api.getAssetFilePathByName(nameArg);
+ wrapped.put("result", output);
+ } catch (Error | RuntimeException exception) {
+ wrapped.put("error", wrapError(exception));
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ }
+ }
+
private static class WebChromeClientFlutterApiCodec extends StandardMessageCodec {
public static final WebChromeClientFlutterApiCodec INSTANCE =
new WebChromeClientFlutterApiCodec();
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
index 2b174ff..cbeda8d 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -14,6 +14,7 @@
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi;
@@ -61,7 +62,9 @@
registrar.messenger(),
registrar.platformViewRegistry(),
registrar.activity(),
- registrar.view());
+ registrar.view(),
+ new FlutterAssetManager.RegistrarFlutterAssetManager(
+ registrar.context().getAssets(), registrar));
new FlutterCookieManager(registrar.messenger());
}
@@ -69,7 +72,8 @@
BinaryMessenger binaryMessenger,
PlatformViewRegistry viewRegistry,
Context context,
- View containerView) {
+ View containerView,
+ FlutterAssetManager flutterAssetManager) {
new FlutterCookieManager(binaryMessenger);
InstanceManager instanceManager = new InstanceManager();
@@ -111,6 +115,8 @@
binaryMessenger,
new WebSettingsHostApiImpl(
instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator()));
+ FlutterAssetManagerHostApi.setup(
+ binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager));
}
@Override
@@ -120,7 +126,9 @@
binding.getBinaryMessenger(),
binding.getPlatformViewRegistry(),
binding.getApplicationContext(),
- null);
+ null,
+ new FlutterAssetManager.PluginBindingFlutterAssetManager(
+ binding.getApplicationContext().getAssets(), binding.getFlutterAssets()));
}
@Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java
new file mode 100644
index 0000000..f530365
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java
@@ -0,0 +1,76 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class FlutterAssetManagerHostApiImplTest {
+ @Mock FlutterAssetManager mockFlutterAssetManager;
+
+ FlutterAssetManagerHostApiImpl testFlutterAssetManagerHostApiImpl;
+
+ @Before
+ public void setUp() {
+ mockFlutterAssetManager = mock(FlutterAssetManager.class);
+
+ testFlutterAssetManagerHostApiImpl =
+ new FlutterAssetManagerHostApiImpl(mockFlutterAssetManager);
+ }
+
+ @Test
+ public void list() {
+ try {
+ when(mockFlutterAssetManager.list("test/path"))
+ .thenReturn(new String[] {"index.html", "styles.css"});
+ List<String> actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path");
+ verify(mockFlutterAssetManager).list("test/path");
+ assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths.toArray());
+ } catch (IOException ex) {
+ fail();
+ }
+ }
+
+ @Test
+ public void list_returns_empty_list_when_no_results() {
+ try {
+ when(mockFlutterAssetManager.list("test/path")).thenReturn(null);
+ List<String> actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path");
+ verify(mockFlutterAssetManager).list("test/path");
+ assertArrayEquals(new String[] {}, actualFilePaths.toArray());
+ } catch (IOException ex) {
+ fail();
+ }
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void list_should_convert_io_exception_to_runtime_exception() {
+ try {
+ when(mockFlutterAssetManager.list("test/path")).thenThrow(new IOException());
+ testFlutterAssetManagerHostApiImpl.list("test/path");
+ } catch (IOException ex) {
+ fail();
+ }
+ }
+
+ @Test
+ public void getAssetFilePathByName() {
+ when(mockFlutterAssetManager.getAssetFilePathByName("index.html"))
+ .thenReturn("flutter_assets/index.html");
+ String filePath = testFlutterAssetManagerHostApiImpl.getAssetFilePathByName("index.html");
+ verify(mockFlutterAssetManager).getAssetFilePathByName("index.html");
+ assertEquals("flutter_assets/index.html", filePath);
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java
new file mode 100644
index 0000000..1f556b7
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java
@@ -0,0 +1,54 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.AssetManager;
+import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets;
+import io.flutter.plugins.webviewflutter.FlutterAssetManager.PluginBindingFlutterAssetManager;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class PluginBindingFlutterAssetManagerTest {
+ @Mock AssetManager mockAssetManager;
+ @Mock FlutterAssets mockFlutterAssets;
+
+ PluginBindingFlutterAssetManager tesPluginBindingFlutterAssetManager;
+
+ @Before
+ public void setUp() {
+ mockAssetManager = mock(AssetManager.class);
+ mockFlutterAssets = mock(FlutterAssets.class);
+
+ tesPluginBindingFlutterAssetManager =
+ new PluginBindingFlutterAssetManager(mockAssetManager, mockFlutterAssets);
+ }
+
+ @Test
+ public void list() {
+ try {
+ when(mockAssetManager.list("test/path"))
+ .thenReturn(new String[] {"index.html", "styles.css"});
+ String[] actualFilePaths = tesPluginBindingFlutterAssetManager.list("test/path");
+ verify(mockAssetManager).list("test/path");
+ assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths);
+ } catch (IOException ex) {
+ fail();
+ }
+ }
+
+ @Test
+ public void registrar_getAssetFilePathByName() {
+ tesPluginBindingFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4");
+ verify(mockFlutterAssets).getAssetFilePathByName("sample_movie.mp4");
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java
new file mode 100644
index 0000000..86b0fb5
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java
@@ -0,0 +1,55 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.AssetManager;
+import io.flutter.plugin.common.PluginRegistry.Registrar;
+import io.flutter.plugins.webviewflutter.FlutterAssetManager.RegistrarFlutterAssetManager;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+@SuppressWarnings("deprecation")
+public class RegistrarFlutterAssetManagerTest {
+ @Mock AssetManager mockAssetManager;
+ @Mock Registrar mockRegistrar;
+
+ RegistrarFlutterAssetManager testRegistrarFlutterAssetManager;
+
+ @Before
+ public void setUp() {
+ mockAssetManager = mock(AssetManager.class);
+ mockRegistrar = mock(Registrar.class);
+
+ testRegistrarFlutterAssetManager =
+ new RegistrarFlutterAssetManager(mockAssetManager, mockRegistrar);
+ }
+
+ @Test
+ public void list() {
+ try {
+ when(mockAssetManager.list("test/path"))
+ .thenReturn(new String[] {"index.html", "styles.css"});
+ String[] actualFilePaths = testRegistrarFlutterAssetManager.list("test/path");
+ verify(mockAssetManager).list("test/path");
+ assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths);
+ } catch (IOException ex) {
+ fail();
+ }
+ }
+
+ @Test
+ public void registrar_getAssetFilePathByName() {
+ testRegistrarFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4");
+ verify(mockRegistrar).lookupKeyForAsset("sample_movie.mp4");
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html
new file mode 100644
index 0000000..9895dd3
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!-- Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html lang="en">
+<head>
+<title>Load file or HTML string example</title>
+<link rel="stylesheet" href="styles/style.css" />
+</head>
+<body>
+
+<h1>Local demo page</h1>
+<p>
+ This is an example page used to demonstrate how to load a local file or HTML
+ string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
+ webview</a> plugin.
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css b/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css
new file mode 100644
index 0000000..c2140b8
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css
@@ -0,0 +1,3 @@
+h1 {
+ color: blue;
+}
\ No newline at end of file
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
index 0c04c8c..3bd283c 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -185,6 +185,7 @@
listCache,
clearCache,
navigationDelegate,
+ loadFlutterAsset,
loadLocalFile,
loadHtmlString,
transparentBackground,
@@ -226,6 +227,9 @@
case _MenuOptions.navigationDelegate:
_onNavigationDelegateExample(controller.data!, context);
break;
+ case _MenuOptions.loadFlutterAsset:
+ _onLoadFlutterAssetExample(controller.data!, context);
+ break;
case _MenuOptions.loadLocalFile:
_onLoadLocalFileExample(controller.data!, context);
break;
@@ -268,6 +272,10 @@
child: Text('Navigation Delegate example'),
),
const PopupMenuItem<_MenuOptions>(
+ value: _MenuOptions.loadFlutterAsset,
+ child: Text('Load Flutter Asset'),
+ ),
+ const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.loadHtmlString,
child: Text('Load HTML string'),
),
@@ -357,6 +365,11 @@
await controller.loadUrl('data:text/html;base64,$contentBase64');
}
+ Future<void> _onLoadFlutterAssetExample(
+ WebViewController controller, BuildContext context) async {
+ await controller.loadFlutterAsset('assets/www/index.html');
+ }
+
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
index 395966b..b32deab 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
@@ -382,6 +382,14 @@
return _webViewPlatformController.loadFile(absoluteFilePath);
}
+ /// Loads the Flutter asset specified in the pubspec.yaml file.
+ ///
+ /// Throws an ArgumentError if [key] is not part of the specified assets
+ /// in the pubspec.yaml file.
+ Future<void> loadFlutterAsset(String key) {
+ return _webViewPlatformController.loadFlutterAsset(key);
+ }
+
/// Loads the supplied HTML string.
///
/// The [baseUrl] parameter is used when resolving relative URLs within the
diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
index 59579df..85990bd 100644
--- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
@@ -34,3 +34,5 @@
assets:
- assets/sample_audio.ogg
- assets/sample_video.mp4
+ - assets/www/index.html
+ - assets/www/styles/style.css
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index ecd6f33..a7561ce 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -7,6 +7,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart' show AndroidViewSurface;
+import 'android_webview.pigeon.dart';
import 'android_webview_api_impls.dart';
// TODO(bparrishMines): This can be removed once pigeon supports null values: https://github.com/flutter/flutter/issues/59118
@@ -770,3 +771,23 @@
/// Describes the error.
final String description;
}
+
+/// Manages Flutter assets that are part of Android's app bundle.
+class FlutterAssetManager {
+ /// Constructs the [FlutterAssetManager].
+ const FlutterAssetManager();
+
+ /// Pigeon Host Api implementation for [FlutterAssetManager].
+ @visibleForTesting
+ static FlutterAssetManagerHostApi api = FlutterAssetManagerHostApi();
+
+ /// Lists all assets at the given path.
+ ///
+ /// The assets are returned as a `List<String>`. The `List<String>` only
+ /// contains files which are direct childs
+ Future<List<String?>> list(String path) => api.list(path);
+
+ /// Gets the relative file path to the Flutter asset with the given name.
+ Future<String> getAssetFilePathByName(String name) =>
+ api.getAssetFilePathByName(name);
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
index ae528a6..f936856 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -1616,6 +1616,73 @@
}
}
+class _FlutterAssetManagerHostApiCodec extends StandardMessageCodec {
+ const _FlutterAssetManagerHostApiCodec();
+}
+
+class FlutterAssetManagerHostApi {
+ /// Constructor for [FlutterAssetManagerHostApi]. The [binaryMessenger] named argument is
+ /// available for dependency injection. If it is left null, the default
+ /// BinaryMessenger will be used which routes to the host platform.
+ FlutterAssetManagerHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = _FlutterAssetManagerHostApiCodec();
+
+ Future<List<String?>> list(String arg_path) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object>[arg_path]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null,
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as List<Object?>?)!.cast<String?>();
+ }
+ }
+
+ Future<String> getAssetFilePathByName(String arg_name) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object>[arg_name]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null,
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as String?)!;
+ }
+ }
+}
+
class _WebChromeClientFlutterApiCodec extends StandardMessageCodec {
const _WebChromeClientFlutterApiCodec();
}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index 0bfa04f..25f9874 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -5,7 +5,6 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
-
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'src/android_webview.dart' as android_webview;
@@ -20,6 +19,8 @@
required this.javascriptChannelRegistry,
required this.onBuildWidget,
@visibleForTesting this.webViewProxy = const WebViewProxy(),
+ @visibleForTesting
+ this.flutterAssetManager = const android_webview.FlutterAssetManager(),
});
/// Initial parameters used to setup the WebView.
@@ -47,6 +48,11 @@
/// This should only be changed for testing purposes.
final WebViewProxy webViewProxy;
+ /// Manages access to Flutter assets that are part of the Android App bundle.
+ ///
+ /// This should only be changed for testing purposes.
+ final android_webview.FlutterAssetManager flutterAssetManager;
+
/// Callback to build a widget once [android_webview.WebView] has been initialized.
final Widget Function(WebViewAndroidPlatformController controller)
onBuildWidget;
@@ -67,6 +73,7 @@
callbacksHandler: widget.callbacksHandler,
javascriptChannelRegistry: widget.javascriptChannelRegistry,
webViewProxy: widget.webViewProxy,
+ flutterAssetManager: widget.flutterAssetManager,
);
}
@@ -91,6 +98,8 @@
required this.callbacksHandler,
required this.javascriptChannelRegistry,
@visibleForTesting this.webViewProxy = const WebViewProxy(),
+ @visibleForTesting
+ this.flutterAssetManager = const android_webview.FlutterAssetManager(),
}) : assert(creationParams.webSettings?.hasNavigationDelegate != null),
super(callbacksHandler) {
webView = webViewProxy.createWebView(
@@ -134,6 +143,11 @@
/// This should only be changed for testing purposes.
final WebViewProxy webViewProxy;
+ /// Manages access to Flutter assets that are part of the Android App bundle.
+ ///
+ /// This should only be changed for testing purposes.
+ final android_webview.FlutterAssetManager flutterAssetManager;
+
/// Receives callbacks when content should be downloaded instead.
@visibleForTesting
late final WebViewAndroidDownloadListener downloadListener =
@@ -167,6 +181,28 @@
}
@override
+ Future<void> loadFlutterAsset(String key) async {
+ final String assetFilePath =
+ await flutterAssetManager.getAssetFilePathByName(key);
+ final List<String> pathElements = assetFilePath.split('/');
+ final String fileName = pathElements.removeLast();
+ final List<String?> paths =
+ await flutterAssetManager.list(pathElements.join('/'));
+
+ if (!paths.contains(fileName)) {
+ throw ArgumentError(
+ 'Asset for key "$key" not found.',
+ 'key',
+ );
+ }
+
+ return webView.loadUrl(
+ 'file:///android_asset/$assetFilePath',
+ <String, String>{},
+ );
+ }
+
+ @override
Future<void> loadUrl(
String url,
Map<String, String>? headers,
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
index 0fdec2c..78672ea 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -193,6 +193,13 @@
void create(int instanceId, int webViewClientInstanceId);
}
+@HostApi(dartHostTestHandler: 'TestAssetManagerHostApi')
+abstract class FlutterAssetManagerHostApi {
+ List<String> list(String path);
+
+ String getAssetFilePathByName(String name);
+}
+
@FlutterApi()
abstract class WebChromeClientFlutterApi {
void dispose(int instanceId);
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 75245a3..bbe9ee1 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget on Android.
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.5.0
+version: 2.6.0
environment:
sdk: ">=2.14.0 <3.0.0"
@@ -19,7 +19,7 @@
dependencies:
flutter:
sdk: flutter
- webview_flutter_platform_interface: ^1.7.0
+ webview_flutter_platform_interface: ^1.8.0
dev_dependencies:
build_runner: ^2.1.4
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
index 942e59a..90c1474 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
@@ -1055,3 +1055,56 @@
}
}
}
+
+class _TestAssetManagerHostApiCodec extends StandardMessageCodec {
+ const _TestAssetManagerHostApiCodec();
+}
+
+abstract class TestAssetManagerHostApi {
+ static const MessageCodec<Object?> codec = _TestAssetManagerHostApiCodec();
+
+ List<String?> list(String path);
+ String getAssetFilePathByName(String name);
+ static void setup(TestAssetManagerHostApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final String? arg_path = (args[0] as String?);
+ assert(arg_path != null,
+ 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null, expected non-null String.');
+ final List<String?> output = api.list(arg_path!);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName',
+ codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final String? arg_name = (args[0] as String?);
+ assert(arg_name != null,
+ 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null, expected non-null String.');
+ final String output = api.getAssetFilePathByName(arg_name!);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index 0e7d5fc..b903678 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -22,6 +22,7 @@
TestWebSettingsHostApi,
TestWebViewClientHostApi,
TestWebViewHostApi,
+ TestAssetManagerHostApi,
WebChromeClient,
WebView,
WebViewClient,
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index 3c1cd61..a08019e 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -340,6 +340,27 @@
String toString() => super.toString();
}
+/// A class which mocks [TestAssetManagerHostApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTestAssetManagerHostApi extends _i1.Mock
+ implements _i3.TestAssetManagerHostApi {
+ MockTestAssetManagerHostApi() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ List<String?> list(String? path) =>
+ (super.noSuchMethod(Invocation.method(#list, [path]),
+ returnValue: <String?>[]) as List<String?>);
+ @override
+ String getAssetFilePathByName(String? name) =>
+ (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]),
+ returnValue: '') as String);
+ @override
+ String toString() => super.toString();
+}
+
/// A class which mocks [WebChromeClient].
///
/// See the documentation for Mockito's code generation for more information.
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index 460cb54..f3867b3 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -16,6 +16,7 @@
import 'webview_android_widget_test.mocks.dart';
@GenerateMocks(<Type>[
+ android_webview.FlutterAssetManager,
android_webview.WebSettings,
android_webview.WebView,
WebViewAndroidDownloadListener,
@@ -30,6 +31,7 @@
TestWidgetsFlutterBinding.ensureInitialized();
group('$WebViewAndroidWidget', () {
+ late MockFlutterAssetManager mockFlutterAssetManager;
late MockWebView mockWebView;
late MockWebSettings mockWebSettings;
late MockWebViewProxy mockWebViewProxy;
@@ -44,6 +46,7 @@
late WebViewAndroidPlatformController testController;
setUp(() {
+ mockFlutterAssetManager = MockFlutterAssetManager();
mockWebView = MockWebView();
mockWebSettings = MockWebSettings();
when(mockWebView.settings).thenReturn(mockWebSettings);
@@ -77,6 +80,7 @@
callbacksHandler: mockCallbacksHandler,
javascriptChannelRegistry: mockJavascriptChannelRegistry,
webViewProxy: mockWebViewProxy,
+ flutterAssetManager: mockFlutterAssetManager,
onBuildWidget: (WebViewAndroidPlatformController controller) {
testController = controller;
return Container();
@@ -299,6 +303,67 @@
));
});
+ testWidgets('loadFlutterAsset', (WidgetTester tester) async {
+ await buildWidget(tester);
+ const String assetKey = 'test_assets/index.html';
+
+ when(mockFlutterAssetManager.getAssetFilePathByName(assetKey))
+ .thenAnswer(
+ (_) => Future<String>.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets/test_assets'))
+ .thenAnswer(
+ (_) => Future<List<String>>.value(<String>['index.html']));
+
+ await testController.loadFlutterAsset(assetKey);
+
+ verify(mockWebView.loadUrl(
+ 'file:///android_asset/flutter_assets/$assetKey',
+ <String, String>{},
+ ));
+ });
+
+ testWidgets('loadFlutterAsset with file in root',
+ (WidgetTester tester) async {
+ await buildWidget(tester);
+ const String assetKey = 'index.html';
+
+ when(mockFlutterAssetManager.getAssetFilePathByName(assetKey))
+ .thenAnswer(
+ (_) => Future<String>.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets')).thenAnswer(
+ (_) => Future<List<String>>.value(<String>['index.html']));
+
+ await testController.loadFlutterAsset(assetKey);
+
+ verify(mockWebView.loadUrl(
+ 'file:///android_asset/flutter_assets/$assetKey',
+ <String, String>{},
+ ));
+ });
+
+ testWidgets(
+ 'loadFlutterAsset throws ArgumentError when asset does not exists',
+ (WidgetTester tester) async {
+ await buildWidget(tester);
+ const String assetKey = 'test_assets/index.html';
+
+ when(mockFlutterAssetManager.getAssetFilePathByName(assetKey))
+ .thenAnswer(
+ (_) => Future<String>.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets/test_assets'))
+ .thenAnswer((_) => Future<List<String>>.value(<String>['']));
+
+ expect(
+ () => testController.loadFlutterAsset(assetKey),
+ throwsA(
+ isA<ArgumentError>()
+ .having((ArgumentError error) => error.name, 'name', 'key')
+ .having((ArgumentError error) => error.message, 'message',
+ 'Asset for key "$assetKey" not found.'),
+ ),
+ );
+ });
+
testWidgets('loadHtmlString without base URL',
(WidgetTester tester) async {
await buildWidget(tester);
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index 6ee53f9..f3b06ea 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -27,6 +27,28 @@
class _FakeWebView_2 extends _i1.Fake implements _i2.WebView {}
+/// A class which mocks [FlutterAssetManager].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockFlutterAssetManager extends _i1.Mock
+ implements _i2.FlutterAssetManager {
+ MockFlutterAssetManager() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i4.Future<List<String?>> list(String? path) =>
+ (super.noSuchMethod(Invocation.method(#list, [path]),
+ returnValue: Future<List<String?>>.value(<String?>[]))
+ as _i4.Future<List<String?>>);
+ @override
+ _i4.Future<String> getAssetFilePathByName(String? name) =>
+ (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]),
+ returnValue: Future<String>.value('')) as _i4.Future<String>);
+ @override
+ String toString() => super.toString();
+}
+
/// A class which mocks [WebSettings].
///
/// See the documentation for Mockito's code generation for more information.