Xamarin.Android: Opening an Excel document using Intents in Android 10+ fails

Federico Navarrete 616 Reputation points
2021-05-26T21:14:21.873+00:00

I have the following code for exporting an Excel file. The final result is opened in the Excel App. This is the code for opening the file:

if (file.Exists())  
    Intent intent = new Intent(Intent.ActionView);  

    if (Build.VERSION.SdkInt < BuildVersionCodes.N)  
    {  
        intent.SetDataAndType(Android.Net.Uri.FromFile(file), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));  
    }  
    else  
    {  
        intent.AddFlags(ActivityFlags.GrantReadUriPermission);  
        intent.SetDataAndType(Android.Net.Uri.Parse(file.Path), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));  
    }  
    context.StartActivity(Intent.CreateChooser(intent, context.GetString(Resource.String.LblChooseApp)));  
}  

I know the file is created because I don't get any error and reaches this point properly. Also, I added the flag FLAG_GRANT_READ_URI_PERMISSION/GrantReadUriPermission already and when I try to open the file, I get this message:

Can't open file | Try saving the file on the device and then opening
it
This is part of my AndroidManifest.xml

<application android:theme="@style/Launcher" android:icon="@drawable/icon" android:label="@string/app_name" android:requestLegacyExternalStorage="true">  
<activity android:name="tk.supernova.tmtimer.MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><meta-data android:name="android.app.shortcuts" android:resource="@XML  /shortcuts" /></activity></application>

And I already requested the Read/Write permissions:

private readonly string[] PermissionToCheck = {  
    Android.Manifest.Permission.WriteExternalStorage,  
    Android.Manifest.Permission.ReadExternalStorage  
};  
private const int REQUEST_ID = 0;  
private const int STORAGE_PERMISSION_CODE = 23;  

protected override void OnCreate(Bundle savedInstanceState)  
{  
    Platform.Init(this, savedInstanceState);  
    if (Build.VERSION.SdkInt >= BuildVersionCodes.M)  
    {  
        RequestPermissions(PermissionToCheck, REQUEST_ID);  
    }  

    if (Build.VERSION.SdkInt >= BuildVersionCodes.N)  
    {  
        try  
        {  
            StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()  
                                .PenaltyDeathOnFileUriExposure()  
                                .DetectFileUriExposure()  
                                .Build();  
            StrictMode.SetVmPolicy(policy);  
        }  
        catch { }  
    }  
}  

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

Any idea what am I doing wrong? Thanks.

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,366 questions
0 comments No comments
{count} votes

Accepted answer
  1. Federico Navarrete 616 Reputation points
    2021-05-27T20:48:20.303+00:00

    Based on @blackapps suggestions, I did the following changes.

    1 - To add a new section in the AndroidManifest.xml:

    <provider android:name="androidx.core.content.FileProvider" android:authorities="tk.supernova.tmtimer.tk.supernova.tmtimer.fileprovider" android:exported="false" android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
    </provider>
    

    2 - To create a new file in the XML folder called file_paths that contains:

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

    3 - And this changes to open and save the file correctly:

    private File CreateDirFile(string fileName)
    {
        var meetingPath = new File(Xamarin.Essentials.FileSystem.AppDataDirectory, "meetings");
        meetingPath.Mkdir();
        var file = new File(meetingPath, fileName);
    
        if (file.Exists())
        {
            file.Delete();
        }
    
        file.CreateNewFile();
        return file;
    }
    
    private void Save(string fileName, string contentType, MemoryStream stream, Context context)
    {
        var file = CreateDirFile(fileName);
        try
        {
            FileOutputStream outs = new FileOutputStream(file, false);
            outs.Write(stream.ToArray());
            outs.Flush();
            outs.Close();
        }
        catch
        {
            Toast.MakeText(context, context.GetString(Resource.String.LblStorageIssue), ToastLength.Long).Show();
        }
        if (file.Exists() && contentType != APP_TYPE)
        {
            Intent intent = new Intent(Intent.ActionView);
    
            if (Build.VERSION.SdkInt < BuildVersionCodes.N)
            {
                intent.SetDataAndType(Android.Net.Uri.FromFile(file), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
            }
            else
            {
                intent.AddFlags(ActivityFlags.GrantReadUriPermission);
                intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
    
                var contentUri = FileProvider.GetUriForFile(Application.Context, FILE_PROVIDER, file);
    
                intent.SetDataAndType(contentUri, MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
            }
            context.StartActivity(Intent.CreateChooser(intent, context.GetString(Resource.String.LblChooseApp)));
        }
    }
    

    4 - I removed the strict section.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.