Calaf
Calaf

Reputation: 10817

Hello-World of FileProvider

This question contained several sub-questions. I am forking these, starting by this question. I'll eventually clean up by deleting this question.

The following program will in theory share a hello-world text file. The code runs, but sharing to either Dropbox or to Gmail (by way of just two concrete examples) fails.

public class MainActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String filename = "hellow.txt";
        String fileContents = "Hello, World!\n";
        byte[] bytes = fileContents.getBytes();
        FileOutputStream fos = null;
        try {
            fos = this.openFileOutput(filename, MODE_PRIVATE);
            fos.write(bytes);
        } catch (IOException e) {                       
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {                       
                e.printStackTrace();
            }
        } 

        File file = new File(filename);
        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        shareIntent.setType("application/txt");
        startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));

        file.delete();
    }
}

Aside from adding a value for send_to in res/values/strings.xml, the only other pair of changes I did to the generic Hello, World that Eclipse creates is adding the following <provider> tag in AndroidManifest.xml:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.mycorp.helloworldtxtfileprovider.MainActivity"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

    <activity
        android:name="com.mycorp.helloworldtxtfileprovider.MainActivity"
        ...

... and adding the following in res/xml/my_paths.xml

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

My main question is the first, but while you're at this topic, a discussion of questions 2-4 would also be interesting.

  1. Why does the program above fail?
  2. Is it indeed the case that if one needs a custom ContentProvider, then one needs to extend that class, but if one just needs a FileProvider, then one can use that class without derivation?
  3. In this code, I needed to use filename twice—once with openFileOutput and another with new File(). Is there a way to avoid this duplication (that would guarantee that the same file is being referenced)?
  4. Is it safe to delete the file right after startActivity(..) is called, or is it necessary to devise a callback to wait learning that the file has been uploaded/shared. (The real file may take some time to share/upload.)

Edit

The code runs fine and shows a list of apps to send to.

If I select Dropbox, I can select the location just fine. Dropbox sends the notifications "Uploading to Dropbox" followed by "Upload failed: my_file.txt".

If I select Gmail, I can fill the recipient and the file appears to be attached, but after "sending message.." I get "Couldn't send attachment".

Upvotes: 3

Views: 8753

Answers (2)

volley
volley

Reputation: 6711

1.

Use FileProvider.getUriForFile(...) to construct the URI. This will direct the started activity to your FileProvider (which can then serve the file from your app's private files directory). Uri.fromFile(...) does not work because the started activity will try to directly access the private directory.

Set FLAG_GRANT_READ_URI_PERMISSION so that the started activity is granted read permission for the URI constructed by the FileProvider.

Finally, "text/plain" might work better than "application/txt" as MIME type.

I've had some problems getting this to work consistently across devices. This is my best bet so far (will edit if I ever refine it):

    final String auth = "org.volley.sndcard_android.fileprovider";
    final Uri uri = FileProvider.getUriForFile(activity, auth, file);
    final String type = activity.getContentResolver().getType(uriForFile);

    final Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.setDataAndType(uri, type);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uriForFile);
    shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    final Intent chooser = Intent.createChooser(shareIntent, "<title>");
    activity.startActivity(chooser);

Setting only the type works on my Nexus 5 but not on my Samsung tablet. It seems the Samsung tablet needs the URI as data in order to grant the permission. Also note that intent.getData() cancels any previous calls to intent.setType() and vice versa, so you have to use the combined method, as done above.

Gmail seems to interpret the additional data as a default To-address. Highly annoying! If anyone has a better solution, please share it (pun intended, I'm from Gothenburg).

Upvotes: 6

imranhasanhira
imranhasanhira

Reputation: 357

2.Yes, it is indeed. You see that ContentProvider is an abstract class, so to use custom content provider one must have to extend it. As FileProvider is a subclass of ContentProvider (which is not abstract), programmers can use FileProvider without subclassing it.

3.For ensuring same file you can follow the sample code below -

String fileName = "my_file.txt";
String fileData = "sample file content";

// Get the file
File file = new File(this.getFilesDir(), fileName);
if (!file.exists()) {
    file.createNewFile();
}

// Write data to file
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(fileData);
fileWriter.close();

// Get the uri
Uri uri = Uri.fromFile(file);

// Share the file with corresponding uri
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, "Send To"));

4.No, it's not safe to delete the file right after you call startActivity(). Because startActivity() is non-blocking function call and it'll return immediately. You have to wait for the time the file is being shared. You can do that by using startActivityForResult() . See if it serves the purpose.

Upvotes: 0

Related Questions