diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index caee5f7cb75a..e8d9e639c59d 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 e70d4e68edc8..37954b36a834 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 @@ android {
}
}
}
- 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 000000000000..1d484d8639a0
--- /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".
+ *
+ *
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 000000000000..791912adb815
--- /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}.
+ *
+ *
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 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 a5632d351f83..0123790ee799 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 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) {
}
}
+ 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 list(String path);
+
+ String getAssetFilePathByName(String name);
+
+ /** The codec used by FlutterAssetManagerHostApi. */
+ static MessageCodec getCodec() {
+ return FlutterAssetManagerHostApiCodec.INSTANCE;
+ }
+
+ /**
+ * Sets up an instance of `FlutterAssetManagerHostApi` to handle messages through the
+ * `binaryMessenger`.
+ */
+ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi api) {
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger, "dev.flutter.pigeon.FlutterAssetManagerHostApi.list", getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map wrapped = new HashMap<>();
+ try {
+ ArrayList args = (ArrayList) message;
+ String pathArg = (String) args.get(0);
+ if (pathArg == null) {
+ throw new NullPointerException("pathArg unexpectedly null.");
+ }
+ List 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 channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map wrapped = new HashMap<>();
+ try {
+ ArrayList args = (ArrayList) 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 2b174ff35e6f..cbeda8dcf493 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 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra
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 @@ private void setUp(
BinaryMessenger binaryMessenger,
PlatformViewRegistry viewRegistry,
Context context,
- View containerView) {
+ View containerView,
+ FlutterAssetManager flutterAssetManager) {
new FlutterCookieManager(binaryMessenger);
InstanceManager instanceManager = new InstanceManager();
@@ -111,6 +115,8 @@ private void setUp(
binaryMessenger,
new WebSettingsHostApiImpl(
instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator()));
+ FlutterAssetManagerHostApi.setup(
+ binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager));
}
@Override
@@ -120,7 +126,9 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
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 000000000000..f530365a9334
--- /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 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 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 000000000000..1f556b7bd486
--- /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 000000000000..86b0fb5432b9
--- /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 000000000000..9895dd3ce6cb
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/example/assets/www/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+Load file or HTML string example
+
+
+
+
+Local demo page
+
+ This is an example page used to demonstrate how to load a local file or HTML
+ string using the Flutter
+ webview plugin.
+
+
+
+
\ 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 000000000000..c2140b8b0fd8
--- /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 0c04c8ca4004..3bd283c3e712 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 @@ enum _MenuOptions {
listCache,
clearCache,
navigationDelegate,
+ loadFlutterAsset,
loadLocalFile,
loadHtmlString,
transparentBackground,
@@ -226,6 +227,9 @@ class _SampleMenu extends StatelessWidget {
case _MenuOptions.navigationDelegate:
_onNavigationDelegateExample(controller.data!, context);
break;
+ case _MenuOptions.loadFlutterAsset:
+ _onLoadFlutterAssetExample(controller.data!, context);
+ break;
case _MenuOptions.loadLocalFile:
_onLoadLocalFileExample(controller.data!, context);
break;
@@ -267,6 +271,10 @@ class _SampleMenu extends StatelessWidget {
value: _MenuOptions.navigationDelegate,
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 @@ class _SampleMenu extends StatelessWidget {
await controller.loadUrl('data:text/html;base64,$contentBase64');
}
+ Future _onLoadFlutterAssetExample(
+ WebViewController controller, BuildContext context) async {
+ await controller.loadFlutterAsset('assets/www/index.html');
+ }
+
Future _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 395966bdd744..b32deab05477 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 @@ class WebViewController {
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 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 59579df8ca50..85990bd02139 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 @@ flutter:
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 ecd6f33d1e63..a7561cea687c 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 'dart:ui';
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 @@ class WebResourceError {
/// 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`. The `List` only
+ /// contains files which are direct childs
+ Future> list(String path) => api.list(path);
+
+ /// Gets the relative file path to the Flutter asset with the given name.
+ Future 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 ae528a64bb8f..f93685611ba3 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 WebChromeClientHostApi {
}
}
+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 codec = _FlutterAssetManagerHostApiCodec();
+
+ Future> list(String arg_path) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map? replyMap =
+ await channel.send([arg_path]) as Map?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null,
+ );
+ } else if (replyMap['error'] != null) {
+ final Map error =
+ (replyMap['error'] as Map?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as List?)!.cast();
+ }
+ }
+
+ Future getAssetFilePathByName(String arg_name) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final Map? replyMap =
+ await channel.send([arg_name]) as Map?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null,
+ );
+ } else if (replyMap['error'] != null) {
+ final Map error =
+ (replyMap['error'] as Map?)!;
+ 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 0bfa04fde095..25f98742c119 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 @@ class WebViewAndroidWidget extends StatefulWidget {
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 @@ class WebViewAndroidWidget extends StatefulWidget {
/// 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 @@ class _WebViewAndroidWidgetState extends State {
callbacksHandler: widget.callbacksHandler,
javascriptChannelRegistry: widget.javascriptChannelRegistry,
webViewProxy: widget.webViewProxy,
+ flutterAssetManager: widget.flutterAssetManager,
);
}
@@ -91,6 +98,8 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
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 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
/// 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 =
@@ -166,6 +180,28 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
return webView.loadUrl(url, {});
}
+ @override
+ Future loadFlutterAsset(String key) async {
+ final String assetFilePath =
+ await flutterAssetManager.getAssetFilePathByName(key);
+ final List pathElements = assetFilePath.split('/');
+ final String fileName = pathElements.removeLast();
+ final List 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',
+ {},
+ );
+ }
+
@override
Future loadUrl(
String url,
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 0fdec2c17756..78672ea5b671 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 @@ abstract class WebChromeClientHostApi {
void create(int instanceId, int webViewClientInstanceId);
}
+@HostApi(dartHostTestHandler: 'TestAssetManagerHostApi')
+abstract class FlutterAssetManagerHostApi {
+ List 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 75245a31e4c2..bbe9ee174162 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@ name: webview_flutter_android
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 @@ flutter:
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 942e59a0b2b3..90c1474f6c67 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 @@ abstract class TestWebChromeClientHostApi {
}
}
}
+
+class _TestAssetManagerHostApiCodec extends StandardMessageCodec {
+ const _TestAssetManagerHostApiCodec();
+}
+
+abstract class TestAssetManagerHostApi {
+ static const MessageCodec codec = _TestAssetManagerHostApiCodec();
+
+ List list(String path);
+ String getAssetFilePathByName(String name);
+ static void setup(TestAssetManagerHostApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ '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 args = (message as List?)!;
+ 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 output = api.list(arg_path!);
+ return {'result': output};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ '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 args = (message as List?)!;
+ 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 {'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 0e7d5fcd552e..b903678ecef8 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 @@ import 'android_webview_test.mocks.dart';
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 3c1cd610c136..a08019e112ef 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 @@ class MockTestWebViewHostApi extends _i1.Mock
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 list(String? path) =>
+ (super.noSuchMethod(Invocation.method(#list, [path]),
+ returnValue: []) as List);
+ @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 460cb54bd393..f3867b313d7d 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 'package:webview_flutter_platform_interface/webview_flutter_platform_inte
import 'webview_android_widget_test.mocks.dart';
@GenerateMocks([
+ android_webview.FlutterAssetManager,
android_webview.WebSettings,
android_webview.WebView,
WebViewAndroidDownloadListener,
@@ -30,6 +31,7 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('$WebViewAndroidWidget', () {
+ late MockFlutterAssetManager mockFlutterAssetManager;
late MockWebView mockWebView;
late MockWebSettings mockWebSettings;
late MockWebViewProxy mockWebViewProxy;
@@ -44,6 +46,7 @@ void main() {
late WebViewAndroidPlatformController testController;
setUp(() {
+ mockFlutterAssetManager = MockFlutterAssetManager();
mockWebView = MockWebView();
mockWebSettings = MockWebSettings();
when(mockWebView.settings).thenReturn(mockWebSettings);
@@ -77,6 +80,7 @@ void main() {
callbacksHandler: mockCallbacksHandler,
javascriptChannelRegistry: mockJavascriptChannelRegistry,
webViewProxy: mockWebViewProxy,
+ flutterAssetManager: mockFlutterAssetManager,
onBuildWidget: (WebViewAndroidPlatformController controller) {
testController = controller;
return Container();
@@ -299,6 +303,67 @@ void main() {
));
});
+ testWidgets('loadFlutterAsset', (WidgetTester tester) async {
+ await buildWidget(tester);
+ const String assetKey = 'test_assets/index.html';
+
+ when(mockFlutterAssetManager.getAssetFilePathByName(assetKey))
+ .thenAnswer(
+ (_) => Future.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets/test_assets'))
+ .thenAnswer(
+ (_) => Future>.value(['index.html']));
+
+ await testController.loadFlutterAsset(assetKey);
+
+ verify(mockWebView.loadUrl(
+ 'file:///android_asset/flutter_assets/$assetKey',
+ {},
+ ));
+ });
+
+ testWidgets('loadFlutterAsset with file in root',
+ (WidgetTester tester) async {
+ await buildWidget(tester);
+ const String assetKey = 'index.html';
+
+ when(mockFlutterAssetManager.getAssetFilePathByName(assetKey))
+ .thenAnswer(
+ (_) => Future.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets')).thenAnswer(
+ (_) => Future>.value(['index.html']));
+
+ await testController.loadFlutterAsset(assetKey);
+
+ verify(mockWebView.loadUrl(
+ 'file:///android_asset/flutter_assets/$assetKey',
+ {},
+ ));
+ });
+
+ 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.value('flutter_assets/$assetKey'));
+ when(mockFlutterAssetManager.list('flutter_assets/test_assets'))
+ .thenAnswer((_) => Future>.value(['']));
+
+ expect(
+ () => testController.loadFlutterAsset(assetKey),
+ throwsA(
+ isA()
+ .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 6ee53f9f9362..f3b06ea0a0bb 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 _FakeJavascriptChannelRegistry_1 extends _i1.Fake
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? path) =>
+ (super.noSuchMethod(Invocation.method(#list, [path]),
+ returnValue: Future>.value([]))
+ as _i4.Future>);
+ @override
+ _i4.Future getAssetFilePathByName(String? name) =>
+ (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]),
+ returnValue: Future.value('')) as _i4.Future);
+ @override
+ String toString() => super.toString();
+}
+
/// A class which mocks [WebSettings].
///
/// See the documentation for Mockito's code generation for more information.