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.
Using this thread I have updated the handler and now I can see the download option for pdf but still image is not visible on the UI.
Expected output (For some reason PDF content is not visible):
Current output is adding below:

PdfWebViewer:
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
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);
// Enhanced settings for MAUI compatibility
webView.Settings.JavaScriptEnabled = true;
webView.Settings.AllowFileAccess = true;
webView.Settings.AllowContentAccess = true;
webView.Settings.AllowUniversalAccessFromFileURLs = true;
webView.Settings.AllowFileAccessFromFileURLs = true;
webView.Settings.DomStorageEnabled = true;
webView.Settings.MixedContentMode = MixedContentHandling.AlwaysAllow;
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);
// Check if file already exists
if (File.Exists(localPath))
{
var fileInfo = new FileInfo(localPath);
if (fileInfo.Length > 0)
return true;
}
// Use HttpClient instead of WebClient
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(5);
var response = await httpClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync(localPath, content);
return File.Exists(localPath);
}
return false;
}
catch (Exception exc)
{
System.Diagnostics.Debug.WriteLine($"Download failed: {exc.Message}");
return false;
}
}
private async Task LoadUrl(string fileName)
{
string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents");
string localPath = Path.Combine(documentsPath, fileName);
if (!File.Exists(localPath))
{
await ShowErrorMessage("File not found", "The requested file could not be located.");
return;
}
string fileType = Path.GetExtension(localPath).Replace(".", string.Empty).ToLower();
if (PlatformView == null || VirtualView == null)
return;
try
{
switch (fileType)
{
case "pdf":
//await LoadPdfFile(localPath, fileName);
await LoadPdfWithWebViewer(localPath, fileName);
//await LoadPdfAsBase64(localPath);
break;
case "jpg":
case "jpeg":
case "png":
case "gif":
case "bmp":
case "webp":
await LoadImageFile(localPath);
break;
case "txt":
//await LoadTextFile(localPath); // keep existing text loader if you have one
break;
default:
await ShowErrorMessage("Unsupported File Type", $"Cannot display .{fileType} files in this viewer.");
break;
}
}
catch (Exception ex)
{
await ShowErrorMessage("Loading Error", "An error occurred while loading the file.");
}
}
private async Task<bool> CheckAssetExists(string assetPath)
{
try
{
using var stream = Context.Assets.Open(assetPath);
return stream != null;
}
catch
{
return false;
}
}
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
{
// Copy to cache for PDF.js access
//string cacheDir = Context.CacheDir.AbsolutePath;
//string cachedPath = Path.Combine(cacheDir, fileName);
//File.Copy(localPath, cachedPath, true);
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);
// Load with PDF.js
//string pdfUrl = $"file:///android_asset/pdfjs/web/viewer.html?file=../../../cache/{fileName}";
//PlatformView.LoadUrl(pdfUrl);
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);
}
}
private async Task LoadPdfWithWebViewer(string localPath, string fileName)
{
try
{
byte[] pdfBytes = await File.ReadAllBytesAsync(localPath);
string base64 = Convert.ToBase64String(pdfBytes);
// Use object/embed instead of iframe
string html = $@"
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<style>
body {{ margin: 0; padding: 0; }}
.header {{ background: #2196F3; color: white; padding: 15px; text-align: center; }}
.pdf-container {{ width: 100%; height: calc(100vh - 60px); }}
</style>
</head>
<body>
<div class='header'>📄 {fileName}</div>
<object class='pdf-container'
data='data:application/pdf;base64,{base64}'
type='application/pdf'>
<p>PDF cannot be displayed. <a href='data:application/pdf;base64,{base64}' download='{fileName}'>Download PDF</a></p>
</object>
</body>
</html>";
PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
}
catch (Exception ex)
{
await ShowErrorMessage("PDF Error", $"Failed to load PDF: {ex.Message}");
}
}
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);
}
private async Task ShowPdfError(string fileName)
{
string html = $@"<html><body style='text-align:center; padding:40px;'>
<h2>PDF Viewer Unavailable</h2>
<p>File '{fileName}' downloaded but PDF.js assets are missing.</p>
</body></html>";
PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
}
private async Task LoadImageFile(string imagePath)
{
try
{
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
string base64 = Convert.ToBase64String(imageBytes);
string extension = Path.GetExtension(imagePath).ToLower();
string mimeType = extension switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
_ => "image/jpeg"
};
string html = $@"<html><body style='margin:0;background:#000;display:flex;justify-content:center;align-items:center;min-height:100vh;'>
<img src='data:{mimeType};base64,{base64}' style='max-width:100%;max-height:100vh;object-fit:contain;' />
</body></html>";
PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
}
catch (Exception ex)
{
await ShowErrorMessage("Image Error", "Failed to load image file.");
}
}
private async Task ShowErrorMessage(string title, string message)
{
string html = $@"<html><body style='text-align:center;padding:40px;background:#f5f5f5;'>
<div style='background:white;padding:30px;border-radius:8px;'>
<h2 style='color:#e74c3c;'>{title}</h2>
<p>{message}</p>
</div></body></html>";
PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
}
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();
}
}
}
Please help me to resolve the issue with both PDF and image.