Search code examples
xamarin.formsxamarin.androidandroid-webview

Load file into webView with scoped storage


I am showing pdf files and images in my app via a custom webview, which is showing a little html site which is located in my app assets folder. This "homepage" gets the file to be shown via get parameters. The file to be shown is inside my private apps documents folder. This worked fine before I had to target API level 30 or higher, which enforces the usage of scoped storage. Now the "homepage" can't access the file that's inside the private documents folder (access violation). I would like to know if there is a way to still use this method of showing pdf files and images inside your app via a webview with scoped storage enabled.

Here are the details how it's currently implemented:

In the OnElementChanged method of my android renderer for my custom webview (extends the Xamarin.Forms.WebView with a Uri Property) I set up my Android webView (the Control) and then load the uri to the image homepage or the pdf.js webviewer, with the file (image or pdf) in the get parameters in the uri:

protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                var customWebView = Element as CustomWebView;
                Control.Settings.AllowUniversalAccessFromFileURLs = true;
                Control.Settings.SetSupportZoom(true);
                Control.Settings.BuiltInZoomControls = true;
                Control.Settings.DisplayZoomControls = true;


                int width = MainApplication.intWidth;
                string PathCopy = customWebView.Uri;
                customWebView.Uri = customWebView.Uri.Replace(" ", "%20");

                string Extension = Path.GetExtension(WebUtility.UrlEncode(customWebView.Uri)).ToUpper();

                switch (Extension)
                {
                    case ".PDF": Control.LoadUrl(string.Format("file:///android_asset/pdfjs/web/viewer.html?file={0}", WebUtility.UrlEncode(customWebView.Uri))); break;
                    default: Control.LoadUrl(string.Format("file:///android_asset/ImageHomepage/ImageHomepage.html?pfad={0}&width={1}", customWebView.Uri, width)); break;
                }
            }
        }

For pdf files I'm using pdf.js (as shown in this example: https://github.com/xamarin/docs-archive/tree/master/Recipes/xamarin-forms/Controls/display-pdf) and for images this little html site:

<head>
<script> 
function getURLParameter(name)
{
    var value = decodeURIComponent((RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, ""])[1]);
    return (value !== 'null') ? value : false;
}
</script>
</head>

<body>

<script language = "Javascript" type="text/javascript">
<!--
var param = getURLParameter('pfad');
var width = getURLParameter('width');

document.write('<img src="');document.write(param); document.write('" width="'); document.write(width); document.write('" style="width:100%" >'); 
//-->

</script>
</body>

The files to be shown are downloaded from my webservice and stored inside my "PrivateDocumentsFolder" which is declared like this:

public static string PrivateFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
public static string PrivateDocumentsFolder = Path.Combine(PrivateFolder, "Dokumente");

So the files are stored at: /data/user/0/myPackageName/files/Dokumente, which is the private folder I also get when accessing the FilesDir property of my android context object.

Targetting API 29 with requestLegacyExternalStorage set to true works with this method. Opening the files with Xamarin.Essentials Launcher works with scoped storage enabled but I'd like to continue to show the files inside my app and not in an external app.

How can I use this method with API Level >29 and scoped storage enabled? Using Environment.SpecialFolder.MyDocuments should be the correct way if I understand this correctly, because it is inside my apps private scope and there shouldn't be a problem to access these files from inside my app.


Solution

  • At first, have you granted the read and write storage permission? The file is in the app's private folder. Generally speaking, the app can access it directly.

    In addition, if user update to the API 30, you can use preserveLegacyExternalStorage=true and android:requestLegacyExternalStorage=true to keep and use the old storage model.

    Finally, you can add some changes of the WebView's Settings. Such as:

     Control.Settings.AllowFileAccess = true;
    

    For the api 29, the default value of it is true. And api 30 is false.

    You can check the official document: https://developer.android.com/reference/android/webkit/WebSettings#setAllowFileAccess(boolean)