PDF Viewer for Android Using Xamarin.forms

This article is going to explain how to create a pdf viewer inside the Android application using Xamarin forms in Visual studio and here, I am using the latest version Visual Studio 2017.

Step 1 

Create a project in Visual Studio which has Xamarin installed.

To create the project, open Visual Studio and select File -> New -> Project then you can see a popup and select template as shown below.   

Xamarin

 

As you can see, I have selected cross-platform Xamarin forms and I have given a name as pdf viewer. After clicking the ok button you can see one more popup and select that as in the following figure.

Xamarin

 

After clicking ok, you have successfully created a solution which contains portable project (pdfViewer) and android project (pdfViewer.android or pdfViewer.Droid).

Step 2

Now, design a page in portable project (pdfViewer) using xaml, for that you can see by default one page has been created; that is MainPage.xaml. Open that and add some labels and buttons as given in the below code.

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              xmlns:local="clr-namespace:pdfViewer"  
  5.              x:Class="pdfViewer.MainPage">  
  6.   
  7.     <StackLayout BackgroundColor="White" >  
  8.         <Label Text="Select a pdf file" TextColor="Black"  />  
  9.         <StackLayout x:Name="MainStack" >  
  10.             <Grid>  
  11.                 <Grid.RowDefinitions>  
  12.                     <RowDefinition  Height="*"/>  
  13.                 </Grid.RowDefinitions>  
  14.                 <Grid.ColumnDefinitions>  
  15.                     <ColumnDefinition Width="80"  />  
  16.                     <ColumnDefinition Width="*" />  
  17.                 </Grid.ColumnDefinitions>  
  18.                 <Button Text="Browse" HeightRequest="40" TextColor="Black" WidthRequest="80" Grid.Column="0" Clicked="btnBrowse"/>  
  19.                 <Label Grid.Column="1" x:Name="lblPdfName" TextColor="Blue" VerticalTextAlignment="End"/>  
  20.             </Grid>  
  21.         </StackLayout>  
  22.   
  23.     </StackLayout>  
  24. </ContentPage>  

Step 3

Now that we have designed a part of a page we have to add a pdf viewer. For this, download some support files from here

And unzip that, now you can see two folders, build and web. Create one folder and give a name as pdfjs and copy those two folders into this pdfjs folder as shown in the below figure.

Xamarin

 

 After that copy this pdfjs folder into the Android project (pdfViewer.android or pdfViewer.Droid) -> Assets Folder as shown in the below figure.

Xamarin

 

Step 4

Let’s start writing functionality, for this create one class in the Android project (pdfViewer.android or pdfViewer.Droid) by right-clicking this project. Add a class and give a name as PdfViewRenderer and make this class public and add the following namespaces.

  1. using System.ComponentModel;  
  2. using Android.Print;  
  3. using Android.Webkit;  
  4. using pdfViewer.Droid;  
  5. using pdfViewer;  
  6. using Xamarin.Forms;  
  7. using Xamarin.Forms.Platform.Android;  
  8. using WebView = Xamarin.Forms.WebView;  

After that, inherit PdfViewRenderer class from WebViewRenderer Android platform class as shown below

  1. public class PdfViewRenderer: WebViewRenderer  
  2. {  
  3. }   

And we have to write functionality for pdf view inside this class, before that we have to create a property in the portable project (pdfViewer) to access to PdfViewRenderer class, for this create one class in portable project (pdfViewer) and give a name as PdfView and inherit this from WebView and make the class public.

  1. public class PdfView: WebView  
  2. {  
  3. }  

And create one bindable property as shown in the below code

  1. public class PdfView: WebView  
  2.     {  
  3.         public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri), typeof(string), typeof(PdfView));  
  4.         public string Uri  
  5.         {  
  6.             get { return (string)GetValue(UriProperty); }  
  7.             set { SetValue(UriProperty, value); }  
  8.         }  
  9.     }  

Now, let’s access that PdfViewRenderer class through this above property, using this property you can pass pdf file path.

To give a connection between these two classes from different projects you have to export this PdfView into that PdfViewRenderer by adding the below code above the namespace of pdfViewer.Droid

  1. [assembly: ExportRenderer(typeof(PdfView), typeof(PdfViewRenderer))]  

Now, we can access PdfViewRenderer class and pass the file path to it.

Using this Uri property let's add functionality into PdfViewRenderer class as shown below:

  1. public class PdfViewRenderer: WebViewRenderer  
  2.     {  
  3.         internal class PdfWebChromeClient : WebChromeClient  
  4.         {  
  5.             public override bool OnJsAlert(Android.Webkit.WebView view, string url, string message, JsResult result)  
  6.             {  
  7.                 if (message != "PdfViewer_app_scheme:print")  
  8.                 {  
  9.                     return base.OnJsAlert(view, url, message, result);  
  10.                 }  
  11.                 using (var printManager = Forms.Context.GetSystemService(Android.Content.Context.PrintService) as PrintManager)  
  12.                 {  
  13.                     printManager?.Print(FileName, new FilePrintDocumentAdapter(FileName, Uri), null);  
  14.                 }  
  15.                 return true;  
  16.             }  
  17.             public string Uri { private get; set; }  
  18.             public string FileName { private get; set; }  
  19.         }  
  20.         protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)  
  21.         {  
  22.             base.OnElementChanged(e);  
  23.             if (e.NewElement == null)  
  24.             {  
  25.                 return;  
  26.             }  
  27.             var pdfView = Element as PdfView;  
  28.             if (pdfView == null)  
  29.             {  
  30.                 return;  
  31.             }  
  32.             if (string.IsNullOrWhiteSpace(pdfView.Uri) == false)  
  33.             {  
  34.                 Control.SetWebChromeClient(new PdfWebChromeClient  
  35.                 {  
  36.                     Uri = pdfView.Uri,  
  37.                     FileName = GetFileNameFromUri(pdfView.Uri)  
  38.                 });  
  39.             }  
  40.             Control.Settings.AllowFileAccess = true;  
  41.             Control.Settings.AllowUniversalAccessFromFileURLs = true;  
  42.             LoadFile(pdfView.Uri);  
  43.         }  
  44.         private static string GetFileNameFromUri(string uri)  
  45.         {  
  46.             var lastIndexOf = uri?.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase);  
  47.             return lastIndexOf > 0 ? uri.Substring(lastIndexOf.Value, uri.Length - lastIndexOf.Value) : string.Empty;  
  48.         }  
  49.         protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)  
  50.         {  
  51.             base.OnElementPropertyChanged(sender, e);  
  52.   
  53.             if (e.PropertyName != PdfView.UriProperty.PropertyName)  
  54.             {  
  55.                 return;  
  56.             }  
  57.             var pdfView = Element as PdfView;  
  58.             if (pdfView == null)  
  59.             {  
  60.                 return;  
  61.             }  
  62.             if (string.IsNullOrWhiteSpace(pdfView.Uri) == false)  
  63.             {  
  64.                 Control.SetWebChromeClient(new PdfWebChromeClient  
  65.                 {  
  66.                     Uri = pdfView.Uri,  
  67.                     FileName = GetFileNameFromUri(pdfView.Uri)  
  68.                 });  
  69.             }  
  70.             LoadFile(pdfView.Uri);  
  71.         }  
  72.         private void LoadFile(string uri)  
  73.         {  
  74.             if (string.IsNullOrWhiteSpace(uri))  
  75.             {  
  76.                 return;  
  77.             }  
  78.             Control.LoadUrl($"file:///android_asset/pdfjs/web/viewer.html?file=file://{uri}");  
  79.         }  
  80.     }  

Create one more class for printing and give name as FilePrintDocumentAdapter and inhert this class from PrintDocumentAdapter and add functionality into it as shown below:

  1. using System;  
  2. using Android.Print;  
  3. using Java.IO;  
  4. namespace pdfViewer.Droid  
  5. {  
  6.     public class FilePrintDocumentAdapter : PrintDocumentAdapter  
  7.     {  
  8.         private readonly string _fileName;  
  9.         private readonly string _filePath;  
  10.         public FilePrintDocumentAdapter(string fileName, string filePath)  
  11.         {  
  12.             _fileName = fileName;  
  13.             _filePath = filePath;  
  14.         }  
  15.         #region implemented abstract members of PrintDocumentAdapter  
  16.         public override void OnLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, Android.OS.CancellationSignal cancellationSignal, LayoutResultCallback callback, Android.OS.Bundle extras)  
  17.         {  
  18.             if (cancellationSignal.IsCanceled)  
  19.             {  
  20.                 callback.OnLayoutCancelled();  
  21.                 return;  
  22.             }  
  23.             callback.OnLayoutFinished(new PrintDocumentInfo.Builder(_fileName)  
  24.                 .SetContentType(PrintContentType.Document)  
  25.                 .Build(), true);  
  26.         }  
  27.         public override void OnWrite(PageRange[] pages, Android.OS.ParcelFileDescriptor destination, Android.OS.CancellationSignal cancellationSignal, WriteResultCallback callback)  
  28.         {  
  29.             try  
  30.             {  
  31.                 using (InputStream input = new FileInputStream(_filePath))  
  32.                 {  
  33.                     using (OutputStream output = new FileOutputStream(destination.FileDescriptor))  
  34.                     {  
  35.                         var buf = new byte[1024];  
  36.                         int bytesRead;  
  37.                         while ((bytesRead = input.Read(buf)) > 0)  
  38.                         {  
  39.                             output.Write(buf, 0, bytesRead);  
  40.                         }  
  41.                     }  
  42.                 }  
  43.                 callback.OnWriteFinished(new[] { PageRange.AllPages });  
  44.             }  
  45.             catch (FileNotFoundException fileNotFoundException)  
  46.             {  
  47.                 System.Diagnostics.Debug.WriteLine(fileNotFoundException);  
  48.             }  
  49.             catch (Exception exception)  
  50.             {  
  51.                 System.Diagnostics.Debug.WriteLine(exception);  
  52.             }  
  53.         }  
  54.         #endregion  
  55.     }  
  56. }  

Step 5 

Now the pdf viewer is almost ready. Let’s see how to use this in our MainPage.xaml which we have designed. First call this pdf viewer inside this page, for this first add the reference in the above xaml page.

  1. xmlns:local="clr-namespace:pdfViewer"  

I think we have already added this, but  if not add it, then add the below lines after MainStack:

  1. <StackLayout x:Name="pdfStack">             
  2.             <local:PdfView  x:Name="PdfDocView"  HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />         
  3.         </StackLayout>  

So the full design code looks like below:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              xmlns:local="clr-namespace:pdfViewer"  
  5.              x:Class="pdfViewer.MainPage">  
  6.     <StackLayout BackgroundColor="White" >  
  7.         <Label Text="Select a pdf file" TextColor="Black"  />  
  8.         <StackLayout x:Name="MainStack" >  
  9.             <Grid>  
  10.                 <Grid.RowDefinitions>  
  11.                     <RowDefinition  Height="*"/>  
  12.                 </Grid.RowDefinitions>  
  13.                 <Grid.ColumnDefinitions>  
  14.                     <ColumnDefinition Width="80"  />  
  15.                     <ColumnDefinition Width="*" />  
  16.                 </Grid.ColumnDefinitions>  
  17.                 <Button Text="Browse" HeightRequest="40" TextColor="Black" WidthRequest="80" Grid.Column="0" Clicked="btnBrowse"/>  
  18.                 <Label Grid.Column="1" x:Name="lblPdfName" TextColor="Blue" VerticalTextAlignment="End"/>  
  19.             </Grid>  
  20.         </StackLayout>  
  21.         <StackLayout x:Name="pdfStack">             
  22.             <local:PdfView  x:Name="PdfDocView"  HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />         
  23.         </StackLayout>  
  24.     </StackLayout>  
  25. </ContentPage>  

So far we have added a pdf viewer, now let’s call pdf file from button. Before this we need to install one plug in for selecting a file so right click in solution and select Manage NuGet Packages for solution and search for File Picker and install as shown in fig.

Xamarin

 

After installation success, just write the following code to pick a file from the device in button click

  1. private void btnBrowse(object sender, EventArgs args)  
  2.         {  
  3.             FileData filedata = await CrossFilePicker.Current.PickFile();  
  4.             lblPdfName.Text = filedata.FileName;              
  5.         }  

But I don’t know from this if I will be able to get file path, we can get  only the name and file bytes, so we can create file in device with our own path and we can give that path to pdf viewer path, for this let’s create a dependency service . Create one interface in portable project (pdfViewer) for getting the path as shown below.

  1. using System.Threading.Tasks;  
  2. namespace pdfViewer  
  3. {  
  4.     public interface ISaveFile  
  5.     {  
  6.         Task<string> SaveFile(string filename, byte[] bytes);  
  7.     }  
  8. }  

Create one dependency class and inherit that from the above interface in pdfViewer.Droid project for creating a file and returning that path back.

  1. using System.IO;  
  2. using System.Threading.Tasks;  
  3. using pdfViewer.Droid;  
  4. [assembly: Xamarin.Forms.Dependency(typeof(SaveFileDependency))]  
  5. namespace pdfViewer.Droid  
  6. {  
  7.     public class SaveFileDependency:ISaveFile  
  8.     {  
  9.         public async Task<string> SaveFile(string filename, byte[] bytes)  
  10.         {  
  11.             var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);  
  12.             var filePath = Path.Combine(documentsPath, filename);  
  13.             File.WriteAllBytes(filePath, bytes);  
  14.             return filePath;  
  15.         }  
  16.          
  17.     }  
  18. }  

So, the dependency service is ready, next we can call this and get the path from this. After that we can assign this path to a pdf viewer, so rewrite the button code as shown.

  1. private async void btnBrowse(object sender, EventArgs args)  
  2.         {  
  3.             FileData filedata = await CrossFilePicker.Current.PickFile();  
  4.             lblPdfName.Text = filedata.FileName;  
  5.             if (filedata != null)  
  6.             {             
  7.                                 
  8.                 string URL = await DependencyService.Get<ISaveFile>().SaveFile(filedata.FileName, filedata.DataArray);                  
  9.                 PdfDocView.Uri = URL;  
  10.             }  
  11.         }  

Finally we have done it, but one more thing I have forgotten is that you have to give height for this pdf viewer. For this just override the OnSizeAllocated function as shown below

  1. protected override void OnSizeAllocated(double width, double height)  
  2.         {  
  3.             base.OnSizeAllocated(width, height);  
  4.             if (this.Width > 0)  
  5.             {  
  6.                 pdfStack.HeightRequest = this.Height- MainStack.Height;  
  7.                  
  8.             }  
  9.         }  

The output will be as follows,

Xamarin

 

G
M
T

 





Text-to-speech function is limited to 200 characters