Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[webview_flutter] Add loadAssetFile to load html file from local assets #1247

Closed
wants to merge 4 commits into from

Conversation

shaqian
Copy link

@shaqian shaqian commented Feb 21, 2019

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.

@collinjackson collinjackson changed the title Add loadAssetFile to load html file from local assets [webview] Add loadAssetFile to load html file from local assets Feb 21, 2019
@cyanglaz cyanglaz changed the title [webview] Add loadAssetFile to load html file from local assets [webview_flutter] Add loadAssetFile to load html file from local assets Feb 23, 2019
@diegovar
Copy link

Thank you for adding this!

@mehmetf mehmetf requested a review from amirh March 4, 2019 04:37
@Matcho0
Copy link

Matcho0 commented Mar 4, 2019

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.

@diegovar
Copy link

diegovar commented Mar 4, 2019

Maybe it might be better to have an explicit "initialAsset" field in the Webview widget, instead of overloading the "initialUrl" with extra semantics?

@rlecheta
Copy link

what about the postUrl method?

@vvlong-can
Copy link

NSString* key = [_registrar lookupKeyForAsset:@"assets/xxx.html"];
NSURL* nsUrl = [[NSBundle mainBundle] URLForResource:key withExtension:nil];
[self.webview loadFileURL:nsUrl allowingReadAccessToURL:[NSURL URLWithString:@"file:///"]];
can solve this problem, but you should modify open source code for your url schema rule. (get _registrar from plugin entrance )

@ened
Copy link
Contributor

ened commented May 13, 2019

@shaqian Hi! Could you please update this PR so that the Flutter team can review again and eventually merge?

@googlebot
Copy link

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 cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

ℹ️ Googlers: Go here for more info.

@shaqian
Copy link
Author

shaqian commented May 14, 2019

Hi Sebastian, I merged your PR. Thank you!

andreibosco pushed a commit to andreibosco/plugins that referenced this pull request May 31, 2019
…ew_flutter 0.3.9 (android only for now)
andreibosco pushed a commit to andreibosco/plugins that referenced this pull request May 31, 2019
…ew_flutter 0.3.9 (iOS code: not tested)
@shaqian
Copy link
Author

shaqian commented Jun 4, 2019

@ened I think you need to sign the CLA according to googlebot's comment?

@ened
Copy link
Contributor

ened commented Jun 4, 2019

@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);
Copy link
Contributor

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)

@divan
Copy link

divan commented Jun 12, 2019

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 file:// URLs are assets, which is not always true.

Would it be possible to incorporate this use case to this PR as well?

@hgl
Copy link

hgl commented Jul 4, 2019

I'd recommend to contain two methods loadAsset and loadFile to accommodate the two cases mentioned by @divan

The naming is by analogy to Image.asset and Image.file.

Just my 2 cents.

@IonelLupu
Copy link

IonelLupu commented Jul 15, 2019

Can this be merged, please? We really need it. @amirh ?

@Nico04
Copy link

Nico04 commented Jul 18, 2019

Do you have any idea of a release date ?

@amirh amirh added the in review label Aug 2, 2019
@IonelLupu
Copy link

@shaqian This is a nice feature to have inside the Webview. Nice work. Can't wait to be merged.

@amirh
Copy link
Contributor

amirh commented Aug 7, 2019

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 (flutter_asset:// ?), if we do that then I don't think we'd want a loadAssetFile method, you'd just loadUrl. Though I'm also on the fence regarding having the webview_flutter plugin support such a URL scheme directly vs. doing flutter/flutter#27896 and providing the users with full flexibility.

// 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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_channel undefined name

@IchordeDionysos
Copy link

IchordeDionysos commented Sep 8, 2019

@amirh Actually I do this:

(e.g how would a page include an image that's a Flutter asset).

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 index.html and katex.js are both in the same sub-folder in the assets.


Or do you want to allow loading local Flutter assets from an HTTP loaded page?

@Nico04
Copy link

Nico04 commented Sep 10, 2019

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

//Tells Android WebView to allow js script to load local files using "file://" protocol.
webView.getSettings().setAllowFileAccessFromFileURLs(true);

Similar for iOS :
[wkWebView.configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];

I hope this whole topic will be merge soon...

@jazzbpn
Copy link

jazzbpn commented Sep 18, 2019

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.

How to render image using HTML to create Flutter-iOS-App?

https://stackoverflow.com/questions/57702924/how-to-render-image-using-html-css-using-flutter-to-create-ios-app

@KaMoKuMo
Copy link

KaMoKuMo commented Sep 26, 2019

Is there anything I could do to speed up the merging? I am a bit confused, about the missing work...

@Nico04
Copy link

Nico04 commented Sep 26, 2019

To be abble to load a local, non-asset file on iOS, you need :

  • Set [wkWebView.configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
  • add "file://" to your file's path
  • pass the closest containing folder to allowingReadAccessToURL (pass 'file:///' doens't work for me)
  • all the files the html will need access to needs to be in this folder, not outside.

I really hope this local loading feature will be merge...

@3mini
Copy link

3mini commented Nov 7, 2019

Any updates?
Can't wait to be merged.

@@ -289,6 +311,20 @@ - (bool)loadUrl:(NSString*)url withHeaders:(NSDictionary<NSString*, NSString*>*)
return true;
}

- (bool)loadAssetFile:(NSString*)url {
NSString* key = [_registrar lookupKeyForAsset:url];
Copy link

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.

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?

Copy link

@kittugit kittugit Dec 10, 2019

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?

Copy link

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.

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

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;
}

@Tembocs
Copy link

Tembocs commented Dec 15, 2019

Hi everyone. Any show blocker for this to be merged? Any help needed?

@AdnanAlshami
Copy link

I am working on a app that depends on this feature.
please merge

@amondnet
Copy link

amondnet commented Dec 26, 2019

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.

@davbaron
Copy link

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:

initialUrl: "file:///android_asset/flutter_assets/assets/local_site_folder/index.htm"

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...

@cetorres
Copy link

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.
So, my solution was this. Once the app runs for the first time, I copied the HTML files from the asset folder to the app Documents folder (getApplicationDocumentsDirectory() from the path_provider package) and made the initialUrl the following with the var '_htmlPath' being the Documents folder

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

@timeuser
Copy link

@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.

@cetorres
Copy link

@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.
I'm using these libs for that:
archive: ^2.0.11
path_provider: ^1.5.1

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(() => {});
    }
  }

@asjqkkkk
Copy link

asjqkkkk commented Mar 7, 2020

load asset file or local file

  webview_flutter:
    git:
      url: https://github.com/asjqkkkk/plugins.git
      path: packages/webview_flutter
      ref: webview

Assetfiles

    WebView(
      onWebViewCreated: (WebViewController controller) {
        final url = 'assets/index.html';
        controller.loadAssetHtmlFile(url);
      },
      javascriptMode: JavascriptMode.unrestricted,
    );

Localfiles

    WebView(
      onWebViewCreated: (WebViewController controller) {
        controller.loadLocalHtmlFile(filePath)
      },
      javascriptMode: JavascriptMode.unrestricted,
    );

  Future<void> downloadFile(String url) async {
    Dio dio = Dio();
    String path = (await getApplicationDocumentsDirectory()).path;
    String fileName=url.substring(url.lastIndexOf("/")+1);
    await Dio().download(url, '$path/$fileName',onReceiveProgress: (rec,total){
      progress = ((rec/total) * 100).floor();
      if(progress >= 100){
        filePath= '$path/$fileName';
      }
      setState(() {});
    });
  }

the pr link is there: /pull/2583

@cetorres
Copy link

cetorres commented Mar 9, 2020

Please @amirh approve the PR from @asjqkkkk. That's a very important feature that should be added to the package.
Thank you.

@zhouxting
Copy link

When will it be released

@ArsalanSavand
Copy link

Any ETA for when will this pull request be merged?
Please we need this feature.

@faruktoptas
Copy link

I'm waiting for this PR too. When will it be released?

@jamesdixon
Copy link

jamesdixon commented Dec 10, 2020

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
<script src="annotator/fabric.js"></script>

Works
<script src="fabric.js"></script>

@jamesdixon
Copy link

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 😬

jamesdixon added a commit to jamesdixon/plugins that referenced this pull request Dec 16, 2020
@jamesdixon
Copy link

If anyone is looking for a more up-to-date version of these changes, here's a branch based on webview_flutter: 1.0.7

  webview_flutter:
    git:
      url: https://github.com/jamesdixon/plugins
      path: packages/webview_flutter
      ref: local-asset-support

@cyanglaz
Copy link
Contributor

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.

@cyanglaz cyanglaz closed this Jan 14, 2021
@konnic
Copy link

konnic commented Apr 13, 2022

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. So, my solution was this. Once the app runs for the first time, I copied the HTML files from the asset folder to the app Documents folder (getApplicationDocumentsDirectory() from the path_provider package) and made the initialUrl the following with the var '_htmlPath' being the Documents folder

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 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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.