1

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.

Federico Navarrete
  • 3,069
  • 5
  • 41
  • 76

1 Answers1

0

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.

Federico Navarrete
  • 3,069
  • 5
  • 41
  • 76
  • Glad you solved all by that little hint. But... `var file = CreateDirFile(fileName);` I saw you removing the code for this function. Do away with that function too. You do not need to first delete an existing file and create a new one as new FileOutputStream(file) will do that all. You only need to define your File instance in the right way. File file ... Well you know what it should be.. – blackapps May 27 '21 at 08:19
  • Hi @blackapps when I did that it crashed :/. It said that the file didn´t exist that´s why I wrote it like this. – Federico Navarrete May 27 '21 at 08:19
  • Well try again. It's nicer to do withhout. Debug what is really happening.. – blackapps May 27 '21 at 08:20
  • 1
    You have a Toast in that catch. Which is good. But after that you use a file.exists(). But then you will not know if new FileOutputStream failed as you created the file yourself before. That code flow is not very robust. – blackapps May 27 '21 at 08:23