Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unable to access ApplicationDocumentsDirectory in real Ios devices #748

Closed
4 tasks done
uc-dve opened this issue Mar 25, 2021 · 9 comments
Closed
4 tasks done

unable to access ApplicationDocumentsDirectory in real Ios devices #748

uc-dve opened this issue Mar 25, 2021 · 9 comments
Labels
bug Something isn't working

Comments

@uc-dve
Copy link

uc-dve commented Mar 25, 2021

Environment

Technology Version
Flutter version 2.0.2
Plugin version 5.2.1
Android version
iOS version 14.2
Xcode version 12.4

Device information: Apple iPhone SE

Description

Expected behavior:

It should be able to access document directory files in webview.

Current behavior:

unable to access file from ios document directory in real ios devices.

Steps to reproduce

I have made a sample app to reproduce the issue, below is the github link =>

https://github.com/uc-dve/flutter_webview

working video of webview in simulator => https://www.screencast.com/t/lFI4HAhsKDK

not working on real ios device => https://www.screencast.com/t/Ef8ELwReHhS, https://www.screencast.com/t/ea9zwx54Wy

Images

Stacktrace/Logcat

@uc-dve uc-dve added the bug Something isn't working label Mar 25, 2021
@pichillilorenzo
Copy link
Owner

As the official API documentation says, the allowingReadAccessTo option works only with of initialUrlRequest property that uses the file:// scheme.

So, to make it work in your scenario, you should save your pageHtml HTML data in a local File and, then, load it using the initialUrlRequest with the file:// scheme.

Here is your code example updated (also with null-safety) and tested on a real iPhone 14.4:

import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:path_provider/path_provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late String dirPath;

  @override
  void initState() {
    setInit();
    super.initState();
  }

  setInit() async {
    String dPath = (await getApplicationDocumentsDirectory()).path;
    setState(() {
      dirPath = dPath;
    });
  }

  Future<File> _downloadFile({required String url, required String filename}) async {
    var httpClient = new HttpClient();
    var request = await httpClient.getUrl(Uri.parse(url));
    var response = await request.close();
    var bytes = await consolidateHttpClientResponseBytes(response);
    File file = new File('$dirPath/$filename');
    await file.writeAsBytes(bytes);
    print("downloaded $filename");
    return file;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
              onPressed: () async {
                await _downloadFile(
                    url:
                    "https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css",
                    filename: "bootstrap.min.css");
                await _downloadFile(
                    url:
                    "https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js",
                    filename: "bootstrap.min.js");
                await _downloadFile(
                    url:
                    "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
                    filename: "jquery.min.js");

                final snackBar = SnackBar(
                  content: Text('Assets downloaded! Open your WebView!'),
                  duration: Duration(seconds: 1),
                );
                ScaffoldMessenger.of(context).showSnackBar(snackBar);
              },
              child: SizedBox(
                  height: 50, width: 100, child: Text("download asset first")),
            ),
            InkWell(
              onTap: () {
                Navigator.of(context).push(MaterialPageRoute(
                    builder: (context) => WebviewPage(dirPath)));
              },
              child: SizedBox(
                  height: 50, width: 100, child: Text("then Load Webview")),
            ),
          ],
        ),
      ),
    );
  }
}

class WebviewPage extends StatefulWidget {
  String dirPath;
  WebviewPage(this.dirPath);

  @override
  WebviewPageStete createState() => WebviewPageStete();
}

class WebviewPageStete extends State<WebviewPage> {
  late File pageHtmlFile;

  @override
  void initState() {
    String pageHtmlData = """
    <!doctype html>
      <html>
        <head>
          <meta name="format-detection" content="telephone=no">
          <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0">
          <script type="text/javascript" src="jquery.min.js"></script>
          <script type="text/javascript" src="bootstrap.min.js"></script>
          <link href="bootstrap.min.css" rel="stylesheet" type="text/css"/>
        </head>
        <body>
          <div class="jumbotron text-center">
            <h1>My First Bootstrap Page</h1>
            <p>Resize this responsive page to see the effect!</p> 
          </div>
            
          <div class="container">
            <div class="row">
              <div class="col-sm-4">
                <h3>Column 1</h3>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
                <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...</p>
              </div>
              <div class="col-sm-4">
                <h3>Column 2</h3>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
                <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...</p>
              </div>
              <div class="col-sm-4">
                <h3>Column 3</h3>        
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
                <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...</p>
              </div>
            </div>
          </div>
          </body>
        </html>
          """;

    pageHtmlFile = new File('${widget.dirPath}/page.html');
    pageHtmlFile.writeAsBytesSync(utf8.encode(pageHtmlData));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Webview"),
      ),
      body: InAppWebView(
        initialUrlRequest: URLRequest(url: Uri.parse('file://${pageHtmlFile.path}')),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            minimumFontSize: 25,
            cacheEnabled: false,
            javaScriptEnabled: true,
            transparentBackground: true,
            verticalScrollBarEnabled: true,
            allowFileAccessFromFileURLs: true,
            allowUniversalAccessFromFileURLs: true,
            clearCache: true,
          ),
          android: AndroidInAppWebViewOptions(useHybridComposition: true),
          ios: IOSInAppWebViewOptions(
            allowingReadAccessTo: Uri.parse("file://${widget.dirPath}/"),
          ),
        ),
        onWebViewCreated: (webViewController) {
          webViewController.addJavaScriptHandler(
              handlerName: "getPostMessage", callback: (args) {});
        },
      ),
    );
  }
}

This is the screen recording:

748.mov

@uc-dve
Copy link
Author

uc-dve commented Apr 2, 2021

Thanks for the suggestion, it is helpful,

But it is not our use case. I am showing at least 100 pages caching them and loading it in PageView. i can't create 100 files on my device for each page. I need dynamic content my code is working fine I that case. i only need to access files from document directory in ios.
Why it is not possible?

@pichillilorenzo
Copy link
Owner

pichillilorenzo commented Apr 2, 2021

Because there is no other way to do it.
On the iOS native side, the allowingReadAccessTo parameter is used only by loadfileurl method, that, as you can read from the official documentation, it requires file-based URL.

You don't need to store these HTML files permanently. You can simply create a new File on the fly and then delete it when you don't need it anymore (for example when you dispose the page widget showing that webpage).

@uc-dve
Copy link
Author

uc-dve commented Apr 2, 2021

Please check it once we are using it in webview_flutter.
It is working fine for loading local files, in ios device.
flutter/plugins#2830
If you can do it in your plugin.

@pichillilorenzo
Copy link
Owner

Then, please share the code that you are using with webview_flutter

@uc-dve
Copy link
Author

uc-dve commented Apr 2, 2021

Yes please check in below url
flutter/plugins@master...uc-ach:11129_flutter

And we are using it in pubspec.yaml like that

 webview_flutter:
    git:
      url: https://github.com/uc-ach/plugins.git
      path: packages/webview_flutter
      ref: 11129_flutter

@pichillilorenzo
Copy link
Owner

Ok, thanks! I figured it out. Didn't know that you could call loadFileURL and then loadHTMLString / load native methods to make it work this way.

@pichillilorenzo
Copy link
Owner

pichillilorenzo commented Apr 2, 2021

I will add in the next release the iosAllowingReadAccessTo parameter to the loadData method and use the allowingReadAccessTo iOS-specific WebView option also for initialData to allow WebKit to read that folder path when loading an HTML String.

@pichillilorenzo
Copy link
Owner

Released new version 5.3.1.
allowingReadAccessTo support also initialData, so now it should work:

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Webview"),
      ),
      body: InAppWebView(
        initialData: InAppWebViewInitialData(
          data: pageHtml,
          baseUrl: Uri.parse("file://${widget.dirPath}/"),
        ),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            minimumFontSize: 25,
            cacheEnabled: false,
            javaScriptEnabled: true,
            transparentBackground: true,
            verticalScrollBarEnabled: true,
            allowFileAccessFromFileURLs: true,
            allowUniversalAccessFromFileURLs: true,
            clearCache: true,
          ),
          android: AndroidInAppWebViewOptions(useHybridComposition: true),
          ios: IOSInAppWebViewOptions(
            allowingReadAccessTo: Uri.parse("file://${widget.dirPath}/"),
          ),
        ),
        onWebViewCreated: (webViewController) {
          webViewController.addJavaScriptHandler(
              handlerName: "getPostMessage", callback: (args) {});
        },
      ),
    );
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants