This is my code:
protected S3TaskResult doInBackground(Uri... uris) {
if (uris == null || uris.length != 1) {
return null;
}
// The file location of the image selected.
Uri selectedImage = uris[0];
ContentResolver resolver = activity.getContentResolver();
String fileSizeColumn[] = {OpenableColumns.SIZE};
Cursor cursor = resolver.query(selectedImage,
fileSizeColumn, null, null, null);
cursor.moveToFirst();
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
// If the size is unknown, the value stored is null. But since an int can't be
// null in java, the behavior is implementation-specific, which is just a fancy
// term for "unpredictable". So as a rule, check if it's null before assigning
// to an int. This will happen often: The storage API allows for remote
// files, whose size might not be locally known.
String size = null;
if (!cursor.isNull(sizeIndex)) {
// Technically the column stores an int, but cursor.getString will do the
// conversion automatically.
size = cursor.getString(sizeIndex);
}
cursor.close();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(resolver.getType(selectedImage));
if(size != null){
metadata.setContentLength(Long.parseLong(size));
}
S3TaskResult result = new S3TaskResult();
// Put the image data into S3.
try {
s3ClientPasado.createBucket(Constants.getPictureBucket());
PutObjectRequest por = new PutObjectRequest(
Constants.getPictureBucket(), Constants.PICTURE_NAME,
resolver.openInputStream(selectedImage),metadata);
s3ClientPasado.putObject(por);
} catch (Exception exception) {
result.setErrorMessage(exception.getMessage());
}
return result;
}
It says that an error occurred while executing doInBackground()
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.net.Uri.getScheme()' on a null object reference
Any help would be appreciated.
I had a similar problem when the Uri I passed turned to be of null value.
The problem occurred in the resolver.query method.
You should post the rest of your code so we could understand it better.
For example - if you are using this code after getting an image from the camera please post how you invoke the camera intent and how you get the Uri.
Good luck.
Related
I'm having the issue that Android 10 can't read GPS information from my jpeg files. The exact same files are working on Android 9.
I tried the ExifInterface (androidx) which returns null, as well as apache commons imaging which is saying "GPSLatitude: Invalid rational (0/0), Invalid rational (0/0)".
Other exif information like the rating or XPKeywords are read correctly.
How to fix that?
Test code:
import androidx.exifinterface.media.ExifInterface;
ExifInterface exif2 = new ExifInterface(is);
double[] latLngArray = exif2.getLatLong();
getLatLong Implementation:
public double[] getLatLong() {
String latValue = getAttribute(TAG_GPS_LATITUDE);
String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
String lngValue = getAttribute(TAG_GPS_LONGITUDE);
String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF);
if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
try {
double latitude = convertRationalLatLonToDouble(latValue, latRef);
double longitude = convertRationalLatLonToDouble(lngValue, lngRef);
return new double[] {latitude, longitude};
} catch (IllegalArgumentException e) {
Log.w(TAG, "Latitude/longitude values are not parseable. " +
String.format("latValue=%s, latRef=%s, lngValue=%s, lngRef=%s",
latValue, latRef, lngValue, lngRef));
}
}
return null;
}
All getAttribute calls are returning null on Android Q/10. On Android 9 it is working. My test photo does contain GPS information.
Even Android itself was unable to index the GPS location into their media store. The field is null as well in their database. I transferred the test photo via mail.
As mentioned in developer android, now, on Android 10, the location embed on image files is not directly available to the apps:
Because this location information is sensitive, however, Android 10 by default hides this information from your app if it uses scoped storage.
On that same link, you can check how to fix:
Request the ACCESS_MEDIA_LOCATION permission in your app's manifest.
From your MediaStore object, call setRequireOriginal(), passing in the URI of the photograph, as shown in the following code snippet:
Code snippet:
Uri photoUri = Uri.withAppendedPath(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getString(idColumnIndex));
// Get location data from the ExifInterface class.
photoUri = MediaStore.setRequireOriginal(photoUri);
InputStream stream = getContentResolver().openInputStream(photoUri);
if (stream != null) {
ExifInterface exifInterface = new ExifInterface(stream);
// Don't reuse the stream associated with the instance of "ExifInterface".
stream.close();
} else {
// Failed to load the stream, so return the coordinates (0, 0).
latLong = new double[2];
}
Note how they are now using Uri, InputStream (and also FileDescriptor) to access the file since now, you can't access a file using its file path.
I'm writing a wrapper for SAF wrapper for Dropbox since everyone (including Google) is too lazy to implement this "very rich" (ie: awful) API. I've got my root in the picker, but I thought queryChildren should be called first. However, queryChildren is never called and it goes straight toqueryDocument`.
override fun queryRoots(projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
val row = result.newRow()
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, "com.anthonymandra.cloudprovider.dropbox")
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_dropbox_gray)
row.add(DocumentsContract.Root.COLUMN_TITLE, "Dropbox")
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE) // TODO:
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID)
return result
}
override fun queryChildDocuments(
parentDocumentId: String?,
projection: Array<out String>?,
sortOrder: String?
): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
val dropboxPath = if (parentDocumentId == ROOT_DOCUMENT_ID) "" else parentDocumentId
try {
val client = DropboxClientFactory.client
var childFolders = client.files().listFolder(dropboxPath)
while (true) {
for (metadata in childFolders.entries) {
addDocumentRow(result, metadata)
}
if (!childFolders.hasMore) {
break
}
childFolders = client.files().listFolderContinue(childFolders.cursor)
}
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
try {
val client = DropboxClientFactory.client
val metadata = client.files().getMetadata(documentId)
addDocumentRow(result, metadata)
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
Error:
java.lang.IllegalArgumentException: String 'path' does not match pattern
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:58)
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:80)
at com.dropbox.core.v2.files.DbxUserFilesRequests.getMetadata(DbxUserFilesRequests.java:1285)
at com.anthonymandra.cloudprovider.dropbox.DropboxProvider.queryDocument(DropboxProvider.kt:98)
at android.provider.DocumentsProvider.query(DocumentsProvider.java:797)
at android.content.ContentProvider$Transport.query(ContentProvider.java:240)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
at android.os.Binder.execTransact(Binder.java:731)
path is ROOT_DOCUMENT_ID which I'm expecting to go to queryChildDocuments first.
What am I missing here?
I have also written a SAF DropBox implementation, and I also was a bit confused at first about this.
From the documentation:
Note the following:
Each document provider reports one or more "roots" which are starting
points into exploring a tree of documents. Each root has a unique
COLUMN_ROOT_ID, and it points to a document (a directory)
representing the contents under that root. Roots are dynamic by
design to support use cases like multiple accounts, transient USB
storage devices, or user login/log out.
Under each root is a single document. That document points to 1 to N
documents, each of which in turn can point to 1 to N documents.
Each storage backend surfaces individual files and directories by
referencing them with a unique COLUMN_DOCUMENT_ID. Document IDs must
be unique and not change once issued, since they are used for
persistent URI grants across device reboots.
Documents can be either an openable file (with a specific MIME type),
or a directory containing additional documents (with the
MIME_TYPE_DIR MIME type).
Each document can have different capabilities, as described by
COLUMN_FLAGS. For example, FLAG_SUPPORTS_WRITE,
FLAG_SUPPORTS_DELETE, and FLAG_SUPPORTS_THUMBNAIL. The same
COLUMN_DOCUMENT_ID can be included in multiple directories.
That second bullet is the key bullet. After the return from queryRoots(), for each root you passed back, the SAF makes a call to queryDocument(). This is essentially to create the "root file folder" document that appears in the list. What I did was in queryDocument() I check to see if the documentId passed in matches the unique value I gave to DocumentsContract.Root.COLUMN_ROOT_ID in the queryRoots() call. If it is, then you know this queryDocument() call needs to return a folder representing that root. Otherwise, I use the path from DropBox as my documentId everywhere else, so I use that documentID value in calls via DbxClientV2.
Here is some sample code - note that in my case I created an AbstractStorageProvider class from which all my various providers (Dropbox, Instagram, etc.) extend. The base class handles receiving the calls from SAF, and it does some housekeeping (like creating the cursors) and then calls the methods in the implementing classes to populate the cursors as required by that particular service:
Base Class
public Cursor queryRoots(final String[] projection) {
Timber.d( "Lifecycle: queryRoots called");
// If they are not paid up, they do not get to use any of these implementations
if (!InTouchUtils.isLoginPaidSubscription()) {
return null;
}
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultRootProjection());
// Classes that extend this one must implement this method
addRowsToQueryRootsCursor(cursor);
return cursor;
}
From DropboxProvider addRowsToQueryRootsCursor:
protected void addRowsToQueryRootsCursor(MatrixCursor cursor) {
// See if we need to init
long l = System.currentTimeMillis();
if ( !InTouchUtils.initDropboxClient()) {
return;
}
Timber.d( "Time to test initialization of DropboxClient: %dms.", (System.currentTimeMillis() - l));
l = System.currentTimeMillis();
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getResources().getString(R.string.pref_dropbox_displayname_token_default));
batchSize = Long.valueOf(Objects.requireNonNull(sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_query_limit_key),
getContext().getResources().getString(R.string.pref_dropbox_query_limit_key_default))));
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, <YOUR_UNIQUE_ROOTS_KEY_HERE>);
row.add(DocumentsContract.Root.COLUMN_TITLE,
String.format(getContext().getString(R.string.dropbox_root_title),getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Root.COLUMN_SUMMARY,displayname+
getContext().getResources().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_RECENTS | DocumentsContract.Root.FLAG_SUPPORTS_SEARCH);
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID,<YOUR_UNIQUE_ROOT_FOLDER_ID_HERE>);
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.intouch_for_dropbox);
} catch (Exception e) {
Timber.d( "Called addRowsToQueryRootsCursor got exception, message was: %s", e.getMessage());
}
Timber.d( "Time to queryRoots(): %dms.", (System.currentTimeMillis() - l));
}
Then queryDocument() method in the base class:
#Override
public Cursor queryDocument(final String documentId, final String[] projection) {
Timber.d( "Lifecycle: queryDocument called for: %s", documentId);
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
// Return a cursor with a getExtras() method, to avoid the immutable ArrayMap problem.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultDocumentProjection()){
Bundle cursorExtras = new Bundle();
#Override
public Bundle getExtras() {
return cursorExtras;
}
};
addRowToQueryDocumentCursor(cursor, documentId);
return cursor;
}
And addRowToQueryDocumentCursor() in DropboxProvider:
protected void addRowToQueryDocumentCursor(MatrixCursor cursor,
String documentId) {
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getString(R.string.pref_dropbox_displayname_token_default));
if ( !InTouchUtils.initDropboxClient()) {
return;
}
if ( documentId.equals(<YOUR_UNIQUE_ROOTS_ID_HERE>)) {
// root Dir
Timber.d( "addRowToQueryDocumentCursor called for the root");
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, <YOUR_UNIQUE_FOLDER_ID_HERE>);
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME,
String.format(getContext().getString(R.string.dropbox_root_title),
getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Document.COLUMN_SUMMARY,displayname+
getContext().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Document.COLUMN_ICON, R.drawable.folder_icon_dropbox);
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
row.add(DocumentsContract.Document.COLUMN_FLAGS, 0);
row.add(DocumentsContract.Document.COLUMN_SIZE, null);
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, null);
return;
}
Timber.d( "addRowToQueryDocumentCursor called for documentId: %s", documentId);
DbxClientV2 mDbxClient = DropboxClientFactory.getClient();
Metadata metadata = mDbxClient.files().getMetadata(documentId);
if ( metadata instanceof FolderMetadata) {
Timber.d( "Document was a folder");
includeFolder(cursor, (FolderMetadata)metadata);
} else {
Timber.d( "Document was a file");
includeFile(cursor, (FileMetadata) metadata);
}
} catch (Exception e ) {
Timber.d( "Called addRowToQueryDocumentCursor got exception, message was: %s documentId was: %s.", e.getMessage(), documentId);
}
}
The documentation for implementing a DocumentsProvider is... limited. In particular, there is no documented guarantee of the order of calls. As such, a DocumentsProvider really should be implemented to make as few assumptions as possible about the order of those calls.
For example, I would not assume that queryRoots() is called first. It probably will be first, if the first use of the DocumentsProvider for this process happens to be the Storage Access Framework UI. However, given that clients can (with care) persist a document or document tree Uri, you might wind up being called with something else first in your process, if the first thing happens to be a client using a persisted Uri.
And, in your specific case, I would not assume that queryChildDocuments() occurs before or after queryDocument().
I am trying to get the image path string from an imageView.
The imageView is charged using a string path store in SQLite db.
When I modify an item of my Db in some fiels other than image path one, I can't read back the path from imageView to correctly update the db. So the field image path become null after any kind of update.
In code, when I click the save button I call the saveProduct() method:
private void saveProduct() {
// Read from input field
//Use trim to eliminate leading or trailing white space
String nameString = mNameEditText.getText().toString().trim();
String qtyString = mQtyEditText.getText().toString().trim();
String priceString = mPriceEditText.getText().toString().trim();
String mailString = mEmailEditText.getText().toString().trim();
String phoneString = mPhoneEditText.getText().toString().trim();
// Check if this is a new product or an existing one
if (mcurrentProdUri == null &&
TextUtils.isEmpty(nameString) && TextUtils.isEmpty(qtyString)
&& TextUtils.isEmpty(priceString)&& TextUtils.isEmpty(mailString)
&& TextUtils.isEmpty(phoneString)) {
return;
}
//Create a ContentValues object to populate the database
ContentValues values = new ContentValues();
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_NAME, nameString);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_QTY, qtyString);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_PRICE, priceString);
values.put(InventoryContract.ProductEntry.COLUMN_EMAIL, mailString);
values.put(InventoryContract.ProductEntry.COLUMN_PHONE, phoneString);
values.put(InventoryContract.ProductEntry.COLUMN_PRODUCT_PIC, picPath);
//Determine if this is a new product or not by checking if mCurrentProdUri is null or not
if (mcurrentProdUri == null) {
Uri newUri = getContentResolver().insert(InventoryContract.ProductEntry.CONTENT_URI, values);
// check if newUri is null or not
if (newUri == null){
//Show an error toast message
Toast.makeText(this, "There has been an error inserting a new product", Toast.LENGTH_SHORT).show();
} else {
//Otherwise show a successful addition message
Toast.makeText(this, "A new product has been added!", Toast.LENGTH_SHORT).show();
}
} else {
//Otherwise if this is an existing product proceed updating it with new data
int rowsAffected = getContentResolver().update(mcurrentProdUri, values, null, null);
if (rowsAffected == 0) {
// If no rows were affected show error toast message
Toast.makeText(this, "There has been an error in updating the product", Toast.LENGTH_SHORT).show();
} else {
// Otherwise the update was successful and we can show the related Toast message
Toast.makeText(this, "The product has been updated!", Toast.LENGTH_SHORT).show();
}
}
}
I already read all related posts, but I really don't know how I can make the method read the imageView to get out the related image path (or Uri)
Many thanks for support
You need to keep track of the uri at the activity/fragment level before you load the imageview as it's not the responsibility of a regular ImageView to remember it's source.
You can, however, build an extended ImageView that is responsible for that if it helps your case:
class MyImageView extends ImageView {
private Uri mImageUri;
public MyImageView(Context context) {
super(context);
}
#Override
public void setImageURI(#Nullable Uri uri) {
super.setImageURI(uri);
mImageUri = uri;
}
public Uri getImageUri(){
return mImageUri;
}
}
If you do go that route, you have to take view state saving into consideration during config changes or you may lose that uri.
This question already has answers here:
Check whether a String is not Null and not Empty
(35 answers)
Closed 6 years ago.
I have a String imagepath which has path of the selected image. If user selects image i have to upload it on sever. I want to check wheter imagepath is null. I have tried this but it give NullPointerException.
This is my code.
public String imagepath = null;
public String imagepath1 = null;
and I am checking if it is null as:
Log.e("imagepath",""+imagepath);
Log.e("imagepath1", ""+imagepath1);
if (imagepath.equals(null)){
Log.e("no image path","dfdsfdsfdsfdsfdf");
}
else {
uploadFile(imagepath);
}
if (imagepath1.equals(null)){
Log.e("no imagepath1 path","imagepath1");
}
else {
uploadFile2(imagepath);
}
If I do not select any image, it shows NullPointerException. Please help me. What is wrong here.
Try this:
if(imagepath != null && imagepath.isEmpty()) { /* do your stuffs here */ }
for more reference check this:
Checking if a string is empty or null in Java
more specific way to check if a string is null or not is using TextUtils:
if (TextUtils.isEmpty(imagepath )) {
Log.d(TAG, "imagepath is empty or null!");
}
You should use try catch to handle this exception because NullPointerException occurs when object not have any value or not defined and here you are not selecting any image that's why NullPointerExcepion occured Reference for nullPointerException
See first that the two strings are not null before proceeding. Try this:
if(imagepath!=null) {
//Do your stuff
uploadfile(imagepath);
} else {
// Handle the exception
}
if(imagepath1!=null) {
//Do your stuff
uploadfile(imagepath1);
} else {
// Handle the exception
}
im using the Google Drive API to save(use as backup) a database there, its working nice, but just if i use the ROOT
the Api Call:
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
......build();
Drive.DriveApi.getRootFolder(mGoogleApiClient)
.createFile(mGoogleApiClient, metadataChangeSet, result.getDriveContents())
.setResultCallback(fileCallback);
CallBack to Save the file:
final public ResultCallback < DriveFolder.DriveFileResult > fileCallback = new
ResultCallback < DriveFolder.DriveFileResult > () {
#Override
public void onResult(DriveFolder.DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
return;
}
Log.i(TAG, "Successfull !");
}
};
i know that i must get the Folder, but if i do this, i need to do a CallBack to call another callback and then save?
isnt any way to directly do .createNewFile inside the FOLDER? without doing another Query for folder, check if the folder exist than create the folder, than use the DriveID, than create the file?
Remember, that in the GooDrive universe, the tree structure (folder, subfolder, ...) is a mirage. The Drive is a flat system of objects (files, folders) where one of the metadata fields is a 'set of parent IDs', that actually forms the notion of parentobject - childobject structure. Actually the classic tree (one parent many children) is not even enforced, so a child object can 'appear' in more that one parent.
This fact explains that you CAN NOT create an OS type of path in one shot. The objects (parents) must be created before their IDs can be plugged into child objects' metadata.
So the only way to do it, is to do what you say:
if folder exists
return it's ID
else
return ID of newly created one
create a child object with parent's ID
... and here is an example how I create a structure of type:
/ MYROOT / 2015 / 2015-12
(where MYROOT, 2015 , 2015-12 are subfloders the Drive root)
new Thread(new Runnable() {
#Override
public void run() {
DriveId Id = getFolder( getFolder( getFolder(
Drive.DriveApi.getRootFolder(mGAC).getDriveId(), "MYROOT"),
"2015",
"2015-12"
);
}
}).start();
GoogleApiClient mGAC;
DriveId getFolder(DriveId parentId, String titl) {
DriveId dId = null;
if (parentId != null && titl != null) try {
ArrayList<Filter> fltrs = new ArrayList<>();
fltrs.add(Filters.in(SearchableField.PARENTS, parentId));
fltrs.add(Filters.eq(SearchableField.TITLE, titl));
fltrs.add(Filters.eq(SearchableField.MIME_TYPE, "application/vnd.google-apps.folder"));
Query qry = new Query.Builder().addFilter(Filters.and(fltrs)).build();
MetadataBuffer mdb = null;
DriveApi.MetadataBufferResult rslt = Drive.DriveApi.query(mGAC, qry).await();
if (rslt.getStatus().isSuccess()) try {
mdb = rslt.getMetadataBuffer();
if (mdb.getCount() > 0)
dId = mdb.get(0).getDriveId();
} catch (Exception ignore) {}
finally { if (mdb != null) mdb.close(); }
if (dId == null) {
MetadataChangeSet meta = new Builder().setTitle(titl).setMimeType(UT.MIME_FLDR).build();
DriveFolderResult r1 = parentId.asDriveFolder().createFolder(mGAC, meta).await();
DriveFolder dFld = (r1 != null) && r1.getStatus().isSuccess() ? r1.getDriveFolder() : null;
if (dFld != null) {
MetadataResult r2 = dFld.getMetadata(mGAC).await();
if ((r2 != null) && r2.getStatus().isSuccess()) {
dId = r2.getMetadata().getDriveId();
}
}
}
} catch (Exception e) { e.printStackTrace(); }
return dId;
}
In the 'mdb.get(0).getDriveId()' area, you can see how hacky it gets when you try to impose a classic tree structure on the Drive. The search here can return multiple objects with the same name, so I use the first one. There should be some kind of error reporting here.
As you can see it is possible to replace callbacks with the 'await()' method, flattening the code into a classic DOS style spaghetti code as long as you place the whole sequence off-UI thread (asynctask, thread, ....)
Still, more elegant (IMO) option to accomplish this is to use recursive call from the result callback.
fromPath(Drive.DriveApi.getRootFolder(mGAC).getDriveId(), "MYROOT/2015/2015-12/file.jpg");
....
void fromPath(final DriveId parentId, final String path) {
if (parentId != null && path != null) {
final int idx = path.indexOf('/');
if (idx < 0) {
// reached last path item - probably file name
// CREATE FILE WITH patentID AND QUIT
return; //--- DONE -------------------->>>
}
final String titl = path.substring(0, idx);
ArrayList<Filter> fltrs = new ArrayList<>();
fltrs.add(Filters.in(SearchableField.PARENTS, parentId));
fltrs.add(Filters.eq(SearchableField.TITLE, titl));
fltrs.add(Filters.eq(SearchableField.MIME_TYPE, UT.MIME_FLDR));
Query qry = new Query.Builder().addFilter(Filters.and(fltrs)).build();
Drive.DriveApi.query(mGAC, qry).setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(DriveApi.MetadataBufferResult rslt) {
MetadataBuffer mdb = null;
if (rslt != null && rslt.getStatus().isSuccess()) {
try {
mdb = rslt.getMetadataBuffer();
for (Metadata md : mdb) {
if (md.isTrashed()) continue;
fromPath(md.getDriveId(), path.substring(idx + 1));
return; //+++ first found, NEXT +++++++>>>
}
} finally { if (mdb != null) mdb.close(); }
}
MetadataChangeSet meta = new Builder().setTitle(titl).setMimeType(UT.MIME_FLDR).build();
parentId.asDriveFolder().createFolder(mGAC, meta)
.setResultCallback(new ResultCallback<DriveFolderResult>() {
#Override
public void onResult(DriveFolderResult rslt) {
DriveFolder dFld = rslt != null && rslt.getStatus().isSuccess() ? rslt.getDriveFolder() : null;
if (dFld != null) {
dFld.getMetadata(mGAC).setResultCallback(new ResultCallback<MetadataResult>() {
#Override
public void onResult(MetadataResult rslt) {
if (rslt != null && rslt.getStatus().isSuccess()) {
fromPath(rslt.getMetadata().getDriveId(), path.substring(idx + 1));
return; //+++ created, NEXT +++++++>>>
}
}
});
}
}
});
}
});
}
}
A WORD OF CAUTION:
As I called this sequence repeatedly, using the last DriveId (like 2015-12) as a parent of a JPEG image file, I have experienced weird behavior, like suddenly getting a 'null' result from 'Drive.DriveApi.getRootFolder(mGAC).getDriveId()'. It shouldn't happen and I assume it is a bug in GDAA. I contribute this to the fact that the DriveId used inside GDAA is 'invalid' until the folder gets committed and the ResourceId is resolved in underlying REST Api. Unfortunately, there is no completion event available for folder creation, so I resolved this by calling this sequence only once in onConnected() and caching the '2015-12's DriveId for later use as a parent of the image JPEG files.
Actually you can see it here (createTree() method) with text file on the tail, but the moment I switched the TEXT to JPEG, all hell broke lose.
Good Luck