Getting a PDF from a remote host to a Xamarin.Forms Application

It is fairly easy to display a PDF file that is local to your phone.  For example, see this article from Xamarin.com.

A bit trickier is to get a PDF from a server and then to display it.  Here’s how I did it (note, this blog post offers snippets of code, rather than a complete solution… I will reduce my larger program to something manageable asap)

The first thing I did was create a model object named ReportItem. Its primary goal is to help group all the items in my list of pdf’s, but it contains the all-important URL to the pdf itself.

Next, I created a page named ShowPDFPage.  In the XAML part of this page I just have a CustomWebView:

         <local:CustomWebView Uri="foo.pdf" 
                               HorizontalOptions="FillAndExpand"
                               VerticalOptions="FillAndExpand" />

The CustomWebView was taken from an article I found on the web.

 public class CustomWebView : WebView
    {
       public static readonly BindableProperty UriProperty = BindableProperty.Create (propertyName:"Uri",
             returnType:typeof(string),
             declaringType:typeof(CustomWebView),
             defaultValue:default(string));
 
       public string Uri
       {
          get { return ( string ) GetValueUriProperty ); }
          set { SetValueUriPropertyvalue ); }
       }
    }

This performs the magic of showing the PDF file, but how do we get it?

Gettig the PDF From The Server

In my ViewModel page I declare the FileName as a string property.  Every pdf will have the same name, and will overwrite the one obtained and displayed previously.  That is, I don’t persist the PDF, I just get it, show it, and then get another.

To do so, I pass the URL (and associated data) to my GetPDF method.  I then fill the headers that the server requires.  I start by setting up the Http client:

         client = new HttpClient();
          client.MaxResponseContentBufferSize = 256000;
          client.DefaultRequestHeaders.Accept.Clear();
          client.DefaultRequestHeaders.Accept.Addnew MediaTypeWithQualityHeaderValue"application/json" ) );

I then use James Montemagno’s terrific connectivity plugin to make sure I’m connected or to be notified when I am.

Next, I send the server-specific headers, e.g.,

      client.DefaultRequestHeaders.Add"version""1.0" );
 

I’m ready now to ask for the PDF file.  If I get a success code, I know that I’ve been sent a byte array representing the PDF, so I have to convert it. Once converted, I save it:

           var response = await client.GetAsync(configuration.Url);
             if ( response.IsSuccessStatusCode )
             {
 
                byte[] contentAsByteArray = await response.Content.ReadAsByteArrayAsync();
                await FileSaver.SaveFilecontentAsByteArray"MyFile.pdf" ); 
             }

It is that file that I feed to the custom viewer (shown above).

FileSaver is also “borrowed” from an article on the web, but I can’t find the URL, so if this is your code, let me know and I’ll add a link:

    public static class FileSaver
    {
       public static async Task<string> SaveFile( byte[] fileBytes, string filename )
       {
          try
          {
             string filepath = null;
 
             if ( Device.RuntimePlatform == Device.Android )
             {
                var pdfWriter = DependencyService.Get<IAndroidPDFWriter>();
                filepath = pdfWriter.CreateFile( filename, fileBytes.ToArray() );
             }
             else if ( Device.RuntimePlatform == Device.iOS )
             {
                var rootFolder = FileSystem.Current.GetFolderFromPathAsync( DependencyService.Get<IFileHelper>().GetLocalFilePath() ).Result;
                
                using ( var pdfBytes = new MemoryStream( fileBytes ) )
                {
      
                   var file = await rootFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
 
                   var newFile = await file.OpenAsync(FileAccess.ReadAndWrite);
                   using ( var outputStream = newFile )
                   {
                      pdfBytes.CopyTo( outputStream );
                   }
                }
             }
 
             if ( string.IsNullOrEmpty( filepath ) )
             {
                Log.LogThis( "file path empty""DataFileManager"DateTime.Now );
                return "Error: Unable to save file";
             }
             else
             {
                return filepath;
             }
          }
          catch ( Exception ex )
          {
             Log.LogThis( "Exception", ex.Message, DateTime.Now );
             return "Error: " + ex.Message;
          }
 
       }
    }

Notice that if you are on Android phone there is a call to AndroidPDFWriter.  This too came from the web, and looks like this (in the Android project):

[assembly: DependencytypeofAndroidPDFWriter ) )]
 
 namespace myProject.Droid
 {
    public class AndroidPDFWriter : IAndroidPDFWriter
    {
       public string CreateFilestring filenamebyte[] bytes )
       {
 
          if (! Directory.ExistsPath.Combineglobal::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath"myFileName" ) ) )
          {
             Directory.CreateDirectoryPath.Combineglobal::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath"myFileName" ) );
          }
 
 
          var path = Path.Combine(global::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath"myFileName"filename);
          
 
          File.WriteAllBytespathbytes );
 
          return path;
       }
    }
 }

This of course relies on dependency injection.  The interface we’re using looks like this:

    public interface IAndroidPDFWriter
    {
       string CreateFilestring filenamebyte[] bytes );
    }

Note that you’ll also need interfaces and implementations that know how to write to files on the individual platforms.  If this is of interest, I can follow up with a blog post on that as well.  Here’s the snippet that matters:

 [assembly: DependencytypeofFileImplementation ) )]
 namespace myProject.Droid
 {
    public class FileImplementation : IFile
    {
       public void SaveTextstring filenamestring text )
       {
          var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
          var filePath = Path.Combine(documentsPathfilename);
          File.DeletefilePath );
          File.WriteAllTextfilePathtext );
       }

Hope all that helps.

About Jesse Liberty

Jesse Liberty is a Senior Consultant with Wintellect, with three decades of experience writing and delivering software projects. He is the author of 2 dozen books and multiple Pluralsight & LinkedIn Learning courses, and has been a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP and a Microsoft MVP.
This entry was posted in Essentials, Xamarin and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *