Open pdf by 3rd party app in xamarin forms error &

2019-08-31 02:54发布

问题:

I am trying to open a pdf in a 3rd party app on the android device, but it's not possible. The applicaiton is built in xamarin forms with a shared project. I have used the solution provided here as a basis: Opening a PDF with Xamarin Forms.

//I changed the interface to a byte array because I want to download the pdf

public interface IDocumentView
{
    void DocumentView(byte[] bytes,string name, string title);
}

//In my android project:

[assembly: Xamarin.Forms.Dependency(typeof(DocumentView))]
namespace FMSXMobileApp.Droid
{   
    public class DocumentView : IDocumentView
    {
        void IDocumentView.DocumentView(byte[] bytes, string name, string title)
        {
            var context = Xamarin.Forms.Forms.Context;

            try
            {   
                File extFile = new File(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads), 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.WriteAllBytes(extFile.AbsolutePath, bytes);
                String mime = MimeTypeMap.GetFileExtensionFromUrl(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);
                    context.ApplicationContext.StartActivity(intent);
                }
            }
            catch (ActivityNotFoundException anfe)
            {
                // android could not find a suitable app for this file
                var alert = new AlertDialog.Builder(context);
                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(context);
                alert.SetTitle("Error");
                alert.SetMessage("Error when opening document");
                alert.SetCancelable(false);
                alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
                alert.Show();
            }
        }
    }
}

//From my xamarin forms shared project I call the next statement:

DependencyService.Get<IDocumentView>().DocumentView(doc.File, "test.pdf", "Title of the view");

//In the xml folder of my android project (file_paths.xml):

<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="root" path="/"/>
    <external-files-path name="files" path="files" />
</paths>

//in the manifest I added:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

The applicaiton fails at this line:

System.IO.File.WriteAllBytes(extFile.AbsolutePath, bytes);

But the code doesn't run I get the error message: Error when opening document. What am I doing wrong? I'm using android 9.0 sdk 28.

Here is the stack trace:

System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/Download/test.pdf" is denied.
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x001b7] in <ff07eae8184a40a08e79049bbcb31a0e>:0 
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <ff07eae8184a40a08e79049bbcb31a0e>:0 
  at (wrapper remoting-invoke-with-check) System.IO.FileStream..ctor(string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
  at System.IO.File.InternalWriteAllBytes (System.String path, System.Byte[] bytes) [0x00000] in <ff07eae8184a40a08e79049bbcb31a0e>:0 
  at System.IO.File.WriteAllBytes (System.String path, System.Byte[] bytes) [0x00039] in <ff07eae8184a40a08e79049bbcb31a0e>:0 
  at FMSXMobileApp.Droid.DocumentView.FMSXMobileApp.Interfaces.IDocumentView.DocumentView (System.Byte[] bytes, System.String name, System.String title) [0x0003d] in D:\FMSXAPP\FMSXMobileApp\FMSXMobileApp.Android\DocumentView.cs:28

回答1:

As CrazyTim answered in https://stackoverflow.com/a/24796126/11992780

There are 4 scenarios :

  • The caller does not have the required permission.
  • The file is an executable file that is in use.
  • Path is a directory.
  • Path specified a read-only file.


回答2:

Finally I have found a solution that worked for me. The problem was that my app needed to request access to storage. I used this library for the requests https://www.nuget.org/packages/Plugin.Permissions/.

This is my code from the documents.xaml page:

var status = await Utils.CheckPermissions(Permission.Storage);
if (status == PermissionStatus.Denied) return;
DependencyService.Get<IDocumentView>().DocumentView(doc.File, "test.pdf", "Title of the view");

In the MainActivity of my .android project I had to add this code:

 public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
        {
            PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

This is my new DocumentView in mij .android project:

[assembly: Xamarin.Forms.Dependency(typeof(DocumentView))]
namespace FMSXMobileApp.Droid
{
    public class DocumentView : IDocumentView
    {
        void IDocumentView.DocumentView(byte[] bytes, string name, string title)
        {
            var context = Android.App.Application.Context;

            try
            {
                var filePath = CommonLibrary.Helpers.FileHelper.WriteFileFromByteArray(bytes, Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).Path, "fmsxapp.pdf");
                var mime = MimeTypeMap.GetFileExtensionFromUrl(filePath);
                // if copying was successful, start Intent for opening this file
                if (System.IO.File.Exists(filePath))
                {                    
                    Android.Net.Uri pdfPath = FileProvider.GetUriForFile(context, "com.mydomain.fileprovider", new Java.IO.File(filePath));
                    context.GrantUriPermission(context.PackageName, pdfPath, ActivityFlags.GrantReadUriPermission);
                    Intent intent = new Intent();
                    intent.SetFlags(ActivityFlags.GrantReadUriPermission);                    
                    intent.SetAction(Android.Content.Intent.ActionView);
                    intent.SetDataAndType(pdfPath, $"application/{mime}");
                    context.StartActivity(intent);
                }
            }
            catch (ActivityNotFoundException anfe)
            {
                // android could not find a suitable app for this file
                var alert = new AlertDialog.Builder(context);
                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(context);
                alert.SetTitle("Error");
                alert.SetMessage("Error when opening document");
                alert.SetCancelable(false);
                alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
                alert.Show();
            }
        }
    }
}

I didn't change anything in the manifest file. I did change my file_paths.xml file:

<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-files-path name="root" path="."/>
  <external-files-path name="files" path="files" />
  <external-path name="external_files" path="."/>
</paths>

After this modifications it worked like a charm. I hope that this may help someone who is also struggeling to get it work.