Reputation: 10817
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.
ContentProvider
, then one needs to extend that class, but if one just needs a FileProvider
, then one can use that class without derivation?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)?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
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
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