Reputation: 151
I'm trying to display a PDF on Android in a Xamarin.Forms project and it works fine, except for the first time it's loaded where just one blank page appears 9 times out of 10.
The first call is to this function, located in the Android project:
public string HTMLToPDF(string html, string filename) {
//html param is a full html description of the pdf
//filename param is something like "example.pdf"
try {
var dir = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/folder/");
var file = new Java.IO.File(dir + "/" + filename);
if (!dir.Exists())
dir.Mkdirs();
int x = 0;
while (file.Exists())
{
x++;
file = new Java.IO.File(dir + "/" + filename + "( " + x + " )");
}
if (webpage == null)
webpage = new Android.Webkit.WebView(Android.App.Application.Context);
else
webpage.RemoveAllViews();
int width = 2100;
int height = 2970;
webpage.Layout(0, 0, width, height);
webpage.LoadData(html, "text/html", "UTF-8");
webpage.SetWebViewClient(new WebViewCallBack(file.ToString()));
this.Print(webpage, file.ToString(), filename);
return file.ToString();
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Error", e.Message, "Ok");
}
return "";
}
This is what the WebViewCallBack
class looks like:
class WebViewCallBack : Android.Webkit.WebViewClient
{
string fileNameWithPath = null;
public WebViewCallBack(string path)
{
this.fileNameWithPath = path;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
view.SetInitialScale(1);
view.Settings.LoadWithOverviewMode = true;
view.Settings.UseWideViewPort = true;
PdfDocument document = new Android.Graphics.Pdf.PdfDocument();
Android.Graphics.Pdf.PdfDocument.Page page = document.StartPage(new Android.Graphics.Pdf.PdfDocument.PageInfo.Builder(2100, 2970, 1).Create());
view.Draw(page.Canvas);
document.FinishPage(page);
Stream filestream = new MemoryStream();
Java.IO.FileOutputStream fos = new Java.IO.FileOutputStream(fileNameWithPath, false);
try
{
document.WriteTo(filestream);
fos.Write(((MemoryStream)filestream).ToArray(), 0, (int)filestream.Length);
fos.Close();
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
}
}
}
And the method Print
called at the end:
public void Print(Android.Webkit.WebView webView, string filename, string onlyFileName)
{
try
{
PrintAttributes.Builder builder = new PrintAttributes.Builder();
PrintAttributes.Margins margins = new PrintAttributes.Margins(0, 0, 0, 80);
builder.SetMinMargins(margins);
builder.SetMediaSize(PrintAttributes.MediaSize.IsoA4);
builder.SetColorMode(PrintColorMode.Color);
PrintAttributes attr = builder.Build();
PrintManager printManager = (PrintManager)Forms.Context.GetSystemService(Android.Content.Context.PrintService);
var printAdapter = new GenericPrintAdapter(Forms.Context, webView, filename, onlyFileName);
printAdapter.OnEnded += PrintAdapter_OnEnded;
printAdapter.OnError += PrintAdapter_OnError;
printManager.Print(filename, printAdapter, attr);
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
}
}
When I put a breakpoint on the first line of the OnPageFinished
callback of the WebViewCallBack
class, I see two different things at this point:
Thus I guess I have to find a way to force the loader to wait for the OnPageFinished
method to run first? But that seems wrong.
I can also add that the original HTML contains images, which are all appearing as base64 string in the html string I'm feeding to HTMLToPDF
. I noticed that the PDF loads well even on the first try if there are no images in the HTML, so I thought the problem might be that the PDF loads before it's ready on the first try only, maybe because of the images. I couldn't find a fix for that though.
Can anybody shed some light on this for me?
Upvotes: 0
Views: 1166
Reputation: 588
I had similar problem with blank page. But I used google doc for displaying it. http://docs.google.com/gview?embedded=true&url=
I could solved my problem only by checking operation time between OnPageStarted
and OnPageFinished
. If it took short time, then something go wrong and page is blank.
here is my webClient.
Every time then page is blank I just reload it. Also add simple circuit breaker just in case
public class MyWebViewClient : WebViewClient
{
private readonly WebView _webView;
private DateTime _dateTime = DateTime.Now;
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
private int _breakerHits;
private const int _BreakerCount = 2;
public MyWebViewClient(WebView webView)
{
_webView = webView;
}
public override async void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
await _semaphoreSlim.WaitAsync();
_dateTime = DateTime.Now;
base.OnPageStarted(view, url, favicon);
_webView.SendNavigating(new WebNavigatingEventArgs(WebNavigationEvent.NewPage, null, url));
_semaphoreSlim.Release();
}
public override async void OnPageFinished(Android.Webkit.WebView view, string url)
{
await _semaphoreSlim.WaitAsync();
base.OnPageFinished(view, url);
if (url.Contains(".pdf"))
{
var diff = DateTime.Now - _dateTime;
if (diff > TimeSpan.FromMilliseconds(700) || _breakerHits > _BreakerCount)
{
_breakerHits = 0;
_webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
WebNavigationResult.Success));
}
else
{
_breakerHits++;
view.Reload();
}
}
else
{
_webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
WebNavigationResult.Success));
}
_semaphoreSlim.Release();
}
}
Upvotes: 0
Reputation: 74209
So there are a couple of ways to handle the flow, but I would go with just an event or an Observable.
So lets convert your WebViewCallBack
to something that actually performs a callback when its OnPageFinished
is called. This is using System.Reactive
but you could also use an EventHandler
...
// a few class level variables
bool busy;
WebView webView;
IDisposable WhenPageIsLoadedSubscription;
public class WebViewObservable : WebViewClient
{
Subject<string> pageLoaded = new Subject<string>();
public IObservable<string> WhenPageIsLoaded
{
get { return pageLoaded.AsObservable(); }
}
public override void OnPageFinished(WebView view, string url)
{
pageLoaded.OnNext(url);
}
}
Now define your print routine (the calls in your original OnPageFinished and Print method). This will automatically be called when the webpage is finished loading.
void PrintWebPage(WebView webView, string url)
{
Log.Debug("SO", $"Page: {url} loaded, lets print it now" );
// Perform the work that you used to do in OnPageFinished
// Perform the work in your Print method
// Now turn off a Progress indictor if desired
busy = false;
}
Setup your WebView
with the observable that calls the PrintWebPage
action every time a page is loaded...
void LoadAndPrintWebPage(string html))
{
busy = true;
if (webView == null)
{
int width = 2100;
int height = 2970;
webView = new WebView(this);
var client = new WebViewObservable();
WhenPageIsLoadedSubscription = client.WhenPageIsLoaded.Subscribe((url) => { PrintWebPage(webView, url); });
webView.SetWebViewClient(client);
webView.Layout(0, 0, width, height);
}
webView.LoadData(html, "text/html", "UTF-8");
}
Now call LoadWebPage
with your html content and it will be automatically printed after the page is finished loading...
LoadAndPrintWebPage(html);
When you are done, clean up your observable and webview to avoid memory leaks...
void CleanupWebView()
{
WhenPageIsLoadedSubscription?.Dispose();
webView?.Dispose();
webView = null;
}
Upvotes: 1