-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[webview_flutter] Add loadAssetFile to load html file from local assets #1247
Conversation
Thank you for adding this! |
This breaks using Uri.dataFromString().toString() as the initialUrl. Instead of rendering the HTML, it will try to find a filename that matches the resulting string. |
Maybe it might be better to have an explicit "initialAsset" field in the Webview widget, instead of overloading the "initialUrl" with extra semantics? |
what about the postUrl method? |
|
@shaqian Hi! Could you please update this PR so that the Flutter team can review again and eventually merge? |
Webview assets patch
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the ℹ️ Googlers: Go here for more info. |
Hi Sebastian, I merged your PR. Thank you! |
…ew_flutter 0.3.9 (android only for now)
…ew_flutter 0.3.9 (iOS code: not tested)
@ened I think you need to sign the CLA according to googlebot's comment? |
@shaqian already signed, up to whoever merges the PR to manually confirm it. |
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. | ||
// https://github.com/flutter/flutter/issues/26431 | ||
// ignore: strong_mode_implicit_dynamic_method | ||
return _channel.invokeMethod('loadAssetFile', url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shaqian could you update the PR to clear this comment? (add type parameter)
Thanks for PR. Can't wait for this to be merged. I'd love to bring one more similar use case that seems to be quite common and doesn't play well with this PR – opening local files in WebView which are not assets. I have two apps where I download files (PDF, Images, HTMLs) first into app's folder and then try to display. The case is very similar (opening local file), and this PR assumes that all Would it be possible to incorporate this use case to this PR as well? |
I'd recommend to contain two methods The naming is by analogy to Just my 2 cents. |
Can this be merged, please? We really need it. @amirh ? |
Do you have any idea of a release date ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution!
I left a few comments, additionally this needs e2e tests and to be rebased before we can merge it.
@@ -48,7 +48,11 @@ | |||
|
|||
if (params.containsKey("initialUrl")) { | |||
String url = (String) params.get("initialUrl"); | |||
webView.loadUrl(url); | |||
if (url.contains("://")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes it so we accept something that isn't a URL as the initialUrl
.
We could consider adding an initialAsset
parameter, but I'd leave it for a different PR as it warrants some more discussion (generally it seems like users tend to get confused by the initialFoo
parameters)
/// Loads the specified file. | ||
/// | ||
/// `url` must not be null. | ||
Future<void> loadAssetFile(String url) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: lets call it loadAsset
.
@@ -429,6 +429,17 @@ class WebViewController { | |||
}); | |||
} | |||
|
|||
/// Loads the specified file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs should say that this is a Flutter asset.
/// Loads the specified file. | ||
/// | ||
/// `url` must not be null. | ||
Future<void> loadAssetFile(String url) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't a url
@@ -113,6 +120,12 @@ private void loadUrl(MethodCall methodCall, Result result) { | |||
result.success(null); | |||
} | |||
|
|||
private void loadAssetFile(MethodCall methodCall, Result result) { | |||
String url = (String) methodCall.arguments; | |||
webView.loadUrl("file:///android_asset/flutter_assets/" + url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use registrar.lookupKeyForAsset
@@ -77,7 +82,11 @@ - (instancetype)initWithFrame:(CGRect)frame | |||
|
|||
NSString* initialUrl = args[@"initialUrl"]; | |||
if ([initialUrl isKindOfClass:[NSString class]]) { | |||
[self loadUrl:initialUrl]; | |||
if ([initialUrl rangeOfString:@"://"].location == NSNotFound) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@shaqian This is a nice feature to have inside the Webview. Nice work. Can't wait to be merged. |
Some thoughts after wanting to use something similar in 3b7b0e2 : It may be useful to allow loading assets not just as the main url but also for resources referenced by that page (e.g how would a page include an image that's a Flutter asset). We can achieve this by supporting an asset url scheme ( |
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. | ||
// https://github.com/flutter/flutter/issues/26431 | ||
// ignore: strong_mode_implicit_dynamic_method | ||
return _channel.invokeMethod('loadAssetFile', url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_channel undefined name
@amirh Actually I do this:
by just doing something like: WebView(
initialUrl: 'assets/katex/index.html',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) async {
controller = webViewController;
},
onPageFinished: (str) async {
loadLatex();
},
) <html>
<head>
<script src="./katex.js"></script>
</head>
<body></body>
</html> Where both Or do you want to allow loading local Flutter assets from an HTTP loaded page? |
Meanwhile this PR is merged, I used @shaqian 's repo, which I had to modify a little bit to be compatible with latest official webview plugin. I also had to add a line in the FlutterWebView construtor of the FlutterWebView.java file, so js can load local image
Similar for iOS : I hope this whole topic will be merge soon... |
How to render image using HTML to create Flutter-iOS-App? |
Is there anything I could do to speed up the merging? I am a bit confused, about the missing work... |
To be abble to load a local, non-asset file on iOS, you need :
I really hope this local loading feature will be merge... |
Any updates? |
@@ -289,6 +311,20 @@ - (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*) | |||
return true; | |||
} | |||
|
|||
- (bool)loadAssetFile:(NSString*)url { | |||
NSString* key = [_registrar lookupKeyForAsset:url]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't work if the url has search or hash parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't work if the url has search or hash parameters.
Yes.. I try to load reactjs app from local file system. it is not loading url with bookmark (#). is there any work-around to load bookmark urls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes.. try to load bookmark url.. it is not working. . is there any work-around?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes.. try to load bookmark url.. it is not working. . is there any work-around?
The returned key of method lookupKeyForAsset
only can be found when the url
parameter is identical to the assets path configration in pubspec.yaml.
So before calling this method, we have to remove the search and hash parts in the url
string.
But we can append them back to nsUrl
, the absolute path, before calling loadFileURL
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for your response.. i'm new to objective c.. tried the proposed changes you described above.. still getting same error.. My changes are in
https://github.com/kittugit/webview_flutter/blob/master/ios/Classes/FlutterWebView.m
if i use "fullKey", instead of key, i'm getting error.
from console log:
key: Frameworks/App.framework/flutter_assets/assets/build/index.html
fullKey: Frameworks/App.framework/flutter_assets/assets/build/index.html/#home
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- (bool)loadAssetFile:(NSString*)url {
NSArray* array = [url componentsSeparatedByString:@"?"];
NSString* pathString = [array objectAtIndex:0];
NSLog(@"%@%@", @"pathString: ", pathString);
NSString* key = [_registrar lookupKeyForAsset:pathString];
NSURL* baseURL = [[NSBundle mainBundle] URLForResource:key withExtension:nil];
if (!baseURL) {
return false;
}
NSURL* newUrl = baseURL;
if ([array count] > 1) {
NSString* queryString = [array objectAtIndex:1];
NSLog(@"%@%@", @"queryString: ", queryString);
NSString* queryPart = [NSString stringWithFormat:@"%@%@", @"?", queryString];
NSLog(@"%@%@", @"queryPart: ", queryPart);
newUrl = [NSURL URLWithString:queryPart relativeToURL:baseURL];
}
if (@available(iOS 9.0, *)) {
[_webView loadFileURL:newUrl allowingReadAccessToURL:[NSURL URLWithString:@"file:///"]];
} else {
return false;
}
return true;
}
Hi everyone. Any show blocker for this to be merged? Any help needed? |
I am working on a app that depends on this feature. |
local asset + query string support. webview_flutter:
git:
url: https://github.com/amondnet/plugins.git
path: packages/webview_flutter
ref: webview
# your asset
assets:
- assets/chart/ WebView(
initialUrl: "assets/chart/weight.html",
javascriptMode: JavascriptMode.unrestricted,
); I've been using it in production app for 6 month. |
Can someone please summarize what is being merged, as in, what will the final pattern be within my Dart code. For Android, to 'reference' local asset files, I use the following line of code when defining my WebView widget:
This assumes that the 'index.htm, the 'local_site_folder', and any folders within 'local_site_folder' are spelled out in pubspec.yaml. The above does not work in iOS, and I am a bit confused over what the proposed solution is going to be. I can see that one or more people have 'fixed' the iOS code to allow iOS to use local files in addition to remote files, however I do not know what my eventual syntax would be. Also, what is the status of the merge? As someone who has not done anything like that before, it seems that one thing failed, namely an 'author' has not consented? Thank you - I also have a project waiting on this functionality... |
I believe I solved the problem on iOS (and working on Android as well)! On iOS is not possible to read the HTML files from the assets folder like in Android. WebView(
initialUrl: 'file://$_htmlPath/index.html',
...
), And it worked! For Android I just changed to use the Application Support Directory (getApplicationSupportDirectory()) and set to the same '_htmlPath' variable and it also worked. The key is that the webview on iOS cannot find or read the HTML files inside the assets folder. And in this way it finds the files and the whole structure you may have for your HTMLs, like JS, CSS, images etc. Make sure to copy the HTML files (and support files) on first time app runs to those folders. Then it will work for both iOS and Android. I tested with the latest release version of webview_flutter at this time: ^0.3.19+5 |
@cetorres How did you get the path to the directory containing the HTML and all linked files to copy? I don't see a way to get the path to files or directories in the assets folder. It looks like we have to reference each file directly with something like rootBundle.load(), which will require copying every file (HTML, CSS, images, JS) one by one in code from assets bundle to the documents directory. |
@timeuser The path to copy the HTMLs and all the support files I get like this: if (Platform.isIOS) {
_htmlPath = (await getApplicationDocumentsDirectory()).path;
} else {
_htmlPath = (await getApplicationSupportDirectory()).path;
} The path where my html.zip file starts is the asset folder I set on pubspec.yaml as assets/. My html.zip is inside that folder originally. Then I copy and unzip this file to the _htmlPath above. Here's my function where I do this: // Copy HTML.ZIP from assets to documents/support folder
// only on first time app runs.
void copyHtmlArchiveFromAssetToDocumentsFolder() async {
try {
Directory docsDir;
if (Platform.isIOS) {
docsDir = await getApplicationDocumentsDirectory();
} else {
docsDir = await getApplicationSupportDirectory();
}
String path = docsDir.path;
String filename = 'html.zip';
File okFile = File('$path/ok.txt');
// If archive is already on the docsDir don't go further
if (okFile.existsSync()) {
print('HTML content already on the folder $_htmlPath');
_contentOk = true;
setState(() => {});
return;
}
// Copy the HTML zip file to the documents path
File archiveFile = File('$path/$filename');
var bytes = await rootBundle.load("assets/$filename");
final buffer = bytes.buffer;
var archiveBytes = await archiveFile.writeAsBytes(buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
var bytes2 = archiveBytes.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes2);
// Unzip the HTML zip into the documents path
for (var file in archive) {
var filename = '$path/${file.name}';
if (file.isFile) {
var outFile = File(filename);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
// Save ok file
okFile.createSync();
print('Created ok file.');
// Delete archive file
archiveFile.deleteSync();
print('Deleted $filename file.');
// Set content ok
_contentOk = true;
print('HTML content copied and unzipped to the folder: $_htmlPath');
setState(() => {});
} on Exception catch (e) {
_loadingMessage = 'Error on processing HTML content. ${e.toString()}';
print(_loadingMessage);
setState(() => {});
}
} |
load asset file or local file
Assetfiles
Localfiles
the pr link is there: /pull/2583 |
When will it be released |
Any ETA for when will this pull request be merged? |
I'm waiting for this PR too. When will it be released? |
Would love to see this merged! Also, anyone know why it doesn't seem to work when referencing an asset in a subdirectory? Example: Doesn't work Works |
Also, does anyone a know a way to have clear whatever asset cache the webview pulls from? As of right now, if I make a change to an external file that I reference from in my html file (ex: styles.css), I need to stop the app and start it again from scratch to get the changes to take effect 😬 |
If anyone is looking for a more up-to-date version of these changes, here's a branch based on
|
I'm going to close the PR because the CLA is not signed. Feel free to ping me when the CLA is signed and I'll re-open the PR. Or you can open a new PR after signing the CLA. |
@cetorres does this approach still work for you on iOS? I'm able to copy the zip file, unzip it, but the Webview doesn't open my index.html file... It seems to be stuck at about:blank. I've also validated that index.html exists at the location, which I pass as an initialUrl. |
On iOS side, in order to get the asset file's URL via registar, changed initWithMessenger to initWithRegistrar.
On Android side, use "file:///android_asset/flutter_assets/" to load from assets.