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 the Principal Mobile Developer with IFS Core. He has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and a couple dozen 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 *