0

I have a pdf i've added as an AndroidAsset and a BundleResource for my Android and IOS projects using xamarin forms.

I just want to be able to open those files from any device, using whatever pdf viewer the device defaults to.

Essentially, i just want to be able to do something like:

Device.OpenUri("file:///android_asset/filename.pdf"); 

but this doesn't seem to work. Nothing happens and the user is never prompted to open the pdf. I don't want to use any 3rd party libraries that allow the pdf to open in app, i just want it to redirect the user to a pdf viewer or browser.

Any ideas?

1
  • Device.OpenUri opens the file in the default browser. You need to provide full file path to the function and in order to get the full path for each platform, you need to create a dependency service in all platforms. This dependency service needs to send the full path of the document with respect to the device. Commented Sep 12, 2018 at 10:36

1 Answer 1

5

First of all you will need an interface class, since you need to call the dependency service in order to pass your document to the native implementation(s) of your app:

so in your shared code add an interface, called "IDocumentView.cs":

public interface IDocumentView { void DocumentView(string file, string title); } 

Android

Now in your android project create the corresponding implementation "DocumentView.cs":

assembly: Dependency(typeof(DocumentView))] namespace MyApp.Droid.Services { public class DocumentView: IDocumentView { void IDocumentView.DocumentView(string filepath, string title) { try { File file = new File(filepath); String mime = FileTypes.GetMimeTypeByExtension(MimeTypeMap.GetFileExtensionFromUrl(filepath)); File extFile = new File (Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments), file.Name); File extDir = extFile.ParentFile; // Copy file to external storage to allow other apps to access ist if (System.IO.File.Exists(extFile.AbsolutePath)) System.IO.File.Delete(extFile.AbsolutePath); System.IO.File.Copy(file.AbsolutePath, extFile.AbsolutePath); file.AbsolutePath, extFile.AbsolutePath); // if copying was successful, start Intent for opening this file if (System.IO.File.Exists(extFile.AbsolutePath)) { Intent intent = new Intent(); intent.SetAction(Android.Content.Intent.ActionView); intent.SetDataAndType(Android.Net.Uri.FromFile(extFile), mime); MainApplication.FormsContext.StartActivityForResult(intent, 10); } } catch (ActivityNotFoundException anfe) { // android could not find a suitable app for this file var alert = new AlertDialog.Builder(MainApplication.FormsContext); alert.SetTitle("Error"); alert.SetMessage("No suitable app found to open this file"); alert.SetCancelable(false); alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide()); alert.Show(); } catch (Exception ex) { // another exception var alert = new AlertDialog.Builder(MainApplication.FormsContext); alert.SetTitle("Error"); alert.SetMessage("Error when opening document"); alert.SetCancelable(false); alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide()); alert.Show(); } } } } 

Please note that MainApplication.FormsContext is a static variable I added to my MainApplication.cs in order to be able to access the Context of the app quickly.

In your Android manifest, add

In your application resources, add an xml resource (into folder "xml") called file_paths.xml with the following content:

<paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="root" path="/"/> <external-files-path name="files" path="files" /> </paths> 

Also you need to ensure that there are apps installed on the target device, which are able to handle the file in question. (Acrobat Reader, Word, Excel, etc...).

iOS

iOS already comes with a quite nice document preview built in, so you can simply use that (again create a file named "DocumentView.cs" in your iOS Project):

[assembly: Dependency(typeof(DocumentView))] namespace MyApp.iOS.Services { public class DocumentView: IDocumentView { void IDocumentView.DocumentView(string file, string title) { UIApplication.SharedApplication.InvokeOnMainThread(() => { QLPreviewController previewController = new QLPreviewController(); if (File.Exists(file)) { previewController.DataSource = new PDFPreviewControllerDataSource(NSUrl.FromFilename(file), title); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null); } }); } } public class PDFItem : QLPreviewItem { public PDFItem(string title, NSUrl uri) { this.Title = title; this.Url = uri; } public string Title { get; set; } public NSUrl Url { get; set; } public override NSUrl ItemUrl { get { return Url; } } public override string ItemTitle { get { return Title; } } } public class PDFPreviewControllerDataSource : QLPreviewControllerDataSource { PDFItem[] sources; public PDFPreviewControllerDataSource(NSUrl url, string filename) { sources = new PDFItem[1]; sources[0] = new PDFItem(filename, url); } public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index) { int idx = int.Parse(index.ToString()); if (idx < sources.Length) return sources.ElementAt(idx); return null; } public override nint PreviewItemCount(QLPreviewController controller) { return (nint)sources.Length; } } } 

Finally you can call

DependencyService.Get<IDocumentView>().DocumentView(file.path, "Title of the view"); 

to display the file in question.

Sign up to request clarification or add additional context in comments.

6 Comments

On Android, I keep getting exceptions that the file is not found, when checking if it exists or trying to copy. Currently, the filepath i'm passing is something like "file:///android_asset/name.pdf".
That won't work, since your file isn't lying on a proper file path but is being compiled into your application's assets. Even worse, another app won't be able to access it, since other apps are not allowed to access your app's (internal) assets. So basically you need to get the file content by using Assets.Open("name.pdf"); and write that to a public file location so that the file can be accessed by other apps.
Ah, ok. Isn't that the reason we are copying the file in the first place? So if the file is in my assets, i would have to use Assets.Open("name.pdf"), write it that to a public file location and i wouldn't need to copy it? What's a public location that all android phones have?
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments) should return a public directory to use. By default your app, it's assets and even all files it downloads or generates by itself are put inside their own sandbox, which is closed to the outside world (meaning other apps on the same device). The android solution basically asks the other apps, if they can handle the file and then shows the user which apps can use that file. So the file has to made accessible to them by either copying or writing them to a place they are "allowed" to access.
I just saw that my answer was lacking setting up the fileprovider information in the manifest. I updated that in my answer in the android section.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.