I have an app that exports a CSV file using the ACTION_CREATE_DOCUMENT
intent to get a Uri to write to. This was working fine for some time, but recently when I went to save a file and selected Google Drive I noticed that the resulting file was empty (0 bytes). However, if I select the Downloads folder as the destination (local storage) the file is created and written to correctly.
I condensed the problem down to a simple test Activity (below) and verified that all parts of the code are in fact being executed (no caught exceptions, ParcelFileDescriptor
is not null, etc...).
I tested this on a Android 7 and 8 emulators and an Android 8 physical device and all showed the same behavior, but an old Android 5 phone worked correctly (??).
The full version of the app has, and correctly requests, read and write external storage permissions to handle some third party file managers, but that isn't needed for Google Drive so I've omitted it from this example (behavior is the same with the permissions granted).
The uri I get looks like a valid content Uri (content://com.google.android.apps.docs.storage/document/acc%3...
)
Has anyone else seen this issue? Am I doing something wrong here? How can I make this work with Google Drive again?
public class MainActivity extends AppCompatActivity {
private static final int FILE_EXPORT_REQUEST_CODE = 12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK)
return;
switch(requestCode) {
case FILE_EXPORT_REQUEST_CODE:
if( data != null ) {
Uri uri = data.getData();
if( uri != null ) {
new ExportCSV(this).execute(uri);
}
}
break;
}
}
// test Activity has a single button that calls this
public void saveFile(View v) {
Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
exportIntent.addCategory(Intent.CATEGORY_OPENABLE);
exportIntent.setType("text/csv");
String filename = "test.csv";
exportIntent.putExtra(Intent.EXTRA_TITLE, filename);
startActivityForResult(exportIntent, FILE_EXPORT_REQUEST_CODE);
}
private static class ExportCSV extends AsyncTask<Uri, Void, Boolean> {
private final WeakReference<Context> context;
ExportCSV(Context c) {
context = new WeakReference<>(c);
}
@Override
protected Boolean doInBackground(Uri... uris) {
Uri uri = uris[0];
Context c = context.get();
if( c == null ) {
return false;
}
String data = "colA,colB\n1,2\n";
boolean success = false;
try {
ParcelFileDescriptor pfd = c.getContentResolver().openFileDescriptor(uri, "w");
if( pfd != null ) {
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(data.getBytes());
fileOutputStream.close();
success = true;
}
}
catch(Exception e) {
e.printStackTrace();
}
return success;
}
}
}
The comment from @greenapps put me on the right path. Changing it to use getContentResolver().openOutputStream(...)
fixed the problem:
try {
OutputStream os = c.getContentResolver().openOutputStream(uri);
if( os != null ) {
os.write(data.getBytes());
os.close();
}
}
catch(IOException e) {
e.printStackTrace();
}
Update: After awhile of using this in my app, the problem re-occurred (files written to Google Drive were 0 bytes, but could be written locally). Clearing the cache data for my app solved it again.
Refer to official documentation at https://developer.android.com/guide/topics/providers/document-provider
On Android 4.3 and lower, if you want your app to retrieve a file from another app, it must invoke an intent such as ACTION_PICK or ACTION_GET_CONTENT. The user must then select a single app from which to pick a file and the selected app must provide a user interface for the user to browse and pick from the available files.
On Android 4.4 (API level 19) and higher, you have the additional option of using the ACTION_OPEN_DOCUMENT intent, which displays a system-controlled picker UI controlled that allows the user to browse all files that other apps have made available. From this single UI, the user can pick a file from any of the supported apps.
On Android 5.0 (API level 21) and higher, you can also use the ACTION_OPEN_DOCUMENT_TREE intent, which allows the user to choose a directory for a client app to access.
For me the following solution works perfect for both local as well as google drive on Android 9
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && data != null) {
if (requestCode == REQUEST_SAVE_File) {
try {
stream = getContentResolver().openOutputStream(data.getData());
((BitmapDrawable) imageView.getDrawable()).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream);
strem.close(); ///very important
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
And intent is created as follows
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TITLE, "my-image.png");
startActivityForResult(intent, REQUEST_SAVE_FILE);
Make sure you close the stream after writing. Otherwise file may not be saved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With