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:

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:

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.

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.