MAUI: Issue with loading pdf and image files in webview handler

Sreenivasan, Sreejith 80 Reputation points
2025-10-08T06:38:32.7333333+00:00

I am migrating a Xamarin Forms project to MAUI. I have a custom webview for showing the pdf and image files in Xamarin Forms and I migrated it to a handler in MAUI. But the pdf and images are not visible on the UI in MAUI.

When opening the pdf, the page is going to full white initially and then it will show the status bar and error alert after couple of seconds. For image no error alert full white screen only. Please see the below screenshot:
Screenshot_1759903890

PdfWebViewer.cs

public class PdfWebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri", returnType: typeof(string), declaringType: typeof(PdfWebViewer), defaultValue: default(string));

    public string Uri
    {
        get { return (string)GetValue(UriProperty); }
        set { SetValue(UriProperty, value); }
    }

    public event EventHandler UrlLoaded;

    public void OnUrlLoaded()
    {
        UrlLoaded?.Invoke(this, null);
    }
}

PdfWebViewerHandler.cs

public class PdfWebViewerHandler : ViewHandler<PdfWebViewer, global::Android.Webkit.WebView>
{
    private static readonly WebClient _webClient = new WebClient();
    private WebViewClientDelegate _webDelegate;

    public static IPropertyMapper<PdfWebViewer, PdfWebViewerHandler> Mapper =
        new PropertyMapper<PdfWebViewer, PdfWebViewerHandler>(ViewHandler.ViewMapper)
        {
            [nameof(PdfWebViewer.Uri)] = MapUri
        };

    public PdfWebViewerHandler() : base(Mapper) { }

    protected override global::Android.Webkit.WebView CreatePlatformView()
    {
        var webView = new global::Android.Webkit.WebView(Context);
        webView.Settings.AllowUniversalAccessFromFileURLs = true;
        webView.Settings.JavaScriptEnabled = true;
        return webView;
    }

    protected override void ConnectHandler(global::Android.Webkit.WebView platformView)
    {
        base.ConnectHandler(platformView);

        if (VirtualView != null && platformView != null)
        {
            _webDelegate = new WebViewClientDelegate(VirtualView);
            platformView.SetWebViewClient(_webDelegate);

            if (!string.IsNullOrEmpty(VirtualView.Uri))
            {
                CheckLocalDownload(GetFileNameFromUri());
            }
        }
    }

    private static void MapUri(PdfWebViewerHandler handler, PdfWebViewer view)
    {
        handler?.CheckLocalDownload(handler.GetFileNameFromUri());
    }

    private string GetFileNameFromUri()
    {
        if (VirtualView != null && !string.IsNullOrEmpty(VirtualView.Uri))
        {
            var uri = new Uri(VirtualView.Uri);
            return Path.GetFileName(uri.AbsolutePath);
        }

        return $"{Guid.NewGuid()}.pdf";
    }

    private async void CheckLocalDownload(string fileName)
    {
        try
        {
            var fileDownloaded = await DownloadFile(VirtualView.Uri, fileName);
            if (fileDownloaded)
            {
                await LoadUrl(fileName);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"CheckLocalDownload Exception: {ex}");
        }
    }

    private async Task<bool> DownloadFile(string uri, string filename)
    {
        try
        {
            string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents");

            if (!Directory.Exists(documentsPath))
                Directory.CreateDirectory(documentsPath);

            string localPath = Path.Combine(documentsPath, filename);
            FileInfo fileInfo = new FileInfo(localPath);

            if (File.Exists(localPath) && fileInfo.Length > 0)
                return true;

            // Create a new WebClient per download
            using (var webClient = new WebClient())
            {
                await webClient.DownloadFileTaskAsync(new Uri(uri), localPath);
            }

            return File.Exists(localPath);
        }
        catch (Exception exc)
        {
            Console.WriteLine($"DownloadFile Exception: {exc}");
            return false;
        }
    }


    private async Task LoadUrl(string fileName)
    {
        string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
        string localPath = Path.Combine(documentsPath, fileName);

        string fileType = Path.GetExtension(localPath).Replace(".", string.Empty).ToLower();
        string urlToLoad = VirtualView.Uri;

        if (PlatformView == null || VirtualView == null)
            return;

        switch (fileType)
        {
            case "pdf":
                urlToLoad = $"file:///android_asset/pdfjs/web/viewer.html?file={localPath}";
                break;
            case "txt":
                var textFromFile = await File.ReadAllTextAsync(localPath);
                PlatformView.LoadData(textFromFile, "text/html", "UTF-8");
                return;
        }

        PlatformView.LoadUrl(urlToLoad);
    }

    private class WebViewClientDelegate : WebViewClient
    {
        private readonly PdfWebViewer _pdfWebViewer;

        public WebViewClientDelegate(PdfWebViewer pdfWebViewer)
        {
            _pdfWebViewer = pdfWebViewer;
        }

        public override void OnPageStarted(global::Android.Webkit.WebView view, string url, global::Android.Graphics.Bitmap favicon)
        {
            base.OnPageStarted(view, url, favicon);
        }

        public override void OnPageFinished(global::Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            _pdfWebViewer?.OnUrlLoaded();
        }
    }
}

Expected output:
Screenshot_1759905084

Output box message:

[AndroidProtocolHandler] Unable to open asset URL: file:///android_asset/pdfjs/web/viewer.html?file=/data/user/0/com.companyname.inventiva/files/Documents/3f0befda8f7a623f9ccc9d350ef0464b7f66e3f0.pdf

Update 10/14/2025 - 1

Suggestion 1:

I have updated the LoadPdfFile as per the suggestion like below, but no luck.

private async Task LoadPdfFile(string localPath, string fileName)
{
    // Check if PDF.js assets exist
    bool pdfJsExists = await CheckAssetExists("pdfjs/web/viewer.html");

    if (pdfJsExists)
    {
        try
        {
            string filesDir = Context.FilesDir.AbsolutePath;
            string pdfDir = Path.Combine(filesDir, "pdfs");
            if (!Directory.Exists(pdfDir))
                Directory.CreateDirectory(pdfDir);

            string savedPath = Path.Combine(pdfDir, fileName);
            File.Copy(localPath, savedPath, true);

            string pdfUrl = $"file:///android_asset/pdfjs/web/viewer.html?file=../../../files/pdfs/{fileName}";
            PlatformView.LoadUrl(pdfUrl);
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"PDF.js failed: {ex.Message}");
            await ShowPdfError(fileName);
        }
    }
    else
    {
        await ShowPdfError(fileName);
    }
}

Adding the complete stacktrace below:

[0:] Missing Remote Translation Key: NavActionBack Language: ,

[0:] Missing Consuming App Resx Translation Key: NavActionBack Language: , [0:] Missing Remote Translation Key: NavActionBack Language: ,

[0:] Missing Consuming App Resx Translation Key: NavActionBack Language: ,

[MaterialButton] MaterialButton manages its own background to control elevation, shape, color and states. Consider using backgroundTint, shapeAppearance and other attributes where available. A custom background will ignore these attributes and you should consider handling interaction states such as pressed, focused and disabled

[yname.inventiva] type=1400 audit(0.0:7126): avc: denied { ioctl } for path="/data/data/com.companyname.inventiva/files/pdfs/dabbf1b2e2cba189972c6d03b446c6712e01d980.pdf" dev="dm-40" ino=32957 ioctlcmd=0x9409 scontext=u:r:untrusted_app:s0:c207,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c207,c256,c512,c768 tclass=file permissive=0 app=com.companyname.inventiva

[yname.inventiva] type=1400 audit(0.0:7127): avc: denied { ioctl } for path="/data/data/com.companyname.inventiva/files/pdfs/dabbf1b2e2cba189972c6d03b446c6712e01d980.pdf" dev="dm-40" ino=32957 ioctlcmd=0x9409 scontext=u:r:untrusted_app:s0:c207,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c207,c256,c512,c768 tclass=file permissive=0 app=com.companyname.inventiva

[WindowOnBackDispatcher] sendCancelIfRunning: isInProgress=falsecallback=androidx.activity.OnBackPressedDispatcher$Api34Impl$createOnBackAnimationCallback$1@81659b8

Thread started: <Thread Pool> #32

[chromium] [INFO:CONSOLE:351] "Warning: Setting up fake worker.", source: file:///android_asset/pdfjs/build/pdf.mjs (351)

[chromium] [INFO:CONSOLE:16795] "Missing PDF file.

[chromium]

[chromium] PDF.js v5.4.296 (build: f56dc8601)

[chromium] Message: Unexpected server response (0) while retrieving PDF "file:///files/pdfs/dabbf1b2e2cba189972c6d03b446c6712e01d980.pdf".", source: file:///android_asset/pdfjs/web/viewer.mjs (16795)

[chromium] [INFO:CONSOLE:419] "Uncaught (in promise) ResponseException: Unexpected server response (0) while retrieving PDF "file:///files/pdfs/dabbf1b2e2cba189972c6d03b446c6712e01d980.pdf".", source: file:///android_asset/pdfjs/build/pdf.mjs (419)

[EGL_emulation] app_time_stats: avg=57.21ms min=3.62ms max=1382.79ms count=33

Suggestion 2: Tried loading the pdf as base 64 with the below implementation, but I am getting only white blank page.

 private async Task LoadPdfAsBase64(string pdfPath)
 {
     byte[] pdfBytes = await File.ReadAllBytesAsync(pdfPath);
     string base64 = Convert.ToBase64String(pdfBytes);

     string html = $@"
 <html>
     <body style='margin:0;'>
         <iframe 
             src='data:application/pdf;base64,{base64}' 
             style='width:100%; height:100vh;' 
             frameborder='0'></iframe>
     </body>
 </html>";

     PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
 }

Suggestion 3: I want to load the PDF file in the app itself. Is Google Drive Viewer allows that? It is usually opens outside the app, right?

Update 10/14/2025 - 2

The PDF file is not viewable with the updated base64 approach, screenshot adding below. Is there any way to view the PDF file in the app itself instead of download option? My requirement is to show it in app itself.

User's image

Images are still not working.

I print the pdf URL and I am getting below output.

fileNameOrUrl: >> 3f0befda8f7a623f9ccc9d350ef0464b7f66e3f0.pdf

It is not a URL, so Google Drive Viewer is not possible in my case.

Developer technologies | .NET | .NET MAUI
{count} votes

Answer accepted by question author
  1. Michael Le (WICLOUD CORPORATION) 3,495 Reputation points Microsoft External Staff
    2025-10-08T10:40:12.4133333+00:00

    Hello @Sreenivasan, Sreejith ,

    Thank you for the detailed updates and testing you've done so far.

    After analyzing all your results, it's clear that continuing to pursue WebView solutions will only lead to more dead ends.

    Given your requirement to display PDFs within the app itself, I recommend you to consider using commercial libraries like Syncfusion PDF Viewer or DevExpress PDF Viewer are specifically designed for MAUI applications. They have a free tier for small-scale usage, which might fit your needs.

    Unlike PDFs, images can be successfully displayed in WebView using base64 encoding. If your images aren't loading, the issue is likely in the implementation rather than a fundamental limitation. The base64 approach should work for common image formats (JPEG, PNG, GIF, WebP).

    Your WebView experiments have provided valuable learning about Android's security constraints, but it's time to use that knowledge to make a better architectural decision.

    I hope this clarifies the technical limitations and provides clear alternatives.

    Thank you.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.