Android Code Snippet [updated] GoogleDrive via REST API V3 - Small Testproject

Updated Oct 30, 2023
Attached is a small project (version 19) to get a grip on Google Drive.

Most work was done by mw71, but this brings it all together from scattered informations to a usable entrypoint.
' ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~--​
' Previous Info sources on B4X (sorted by date DEC)​
' ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~--​
' ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~-- ~~~--
' 2023-10-28, JohnC, changes in libs and modules --> https://www.b4x.com/android/forum/t...st-api-v3-small-testproject.95778/post-964342


Libraries:​
30-10-2023_09-34-07.png

Modules:​
30-10-2023_09-34-36.png
Manifest:​
B4X:
'This code will be applied to the manifest file during compilation.

'You do not need to modify it in most cases.

'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136

AddManifestText(

<uses-sdk android:minSdkVersion="20" android:targetSdkVersion="33"/>

<supports-screens android:largeScreens="true"

    android:normalScreens="true"

    android:smallScreens="true"

    android:anyDensity="true"/>)

SetApplicationAttribute(android:icon, "@drawable/icon")

SetApplicationAttribute(android:label, "$LABEL$")

'End of default text.

AddActivityText(Main,

  <intent-filter>

        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />

        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="$PACKAGE$" />

    </intent-filter>

    )

Notes:​
1. The module GoogleOauth2 contains "Sub Testconnect" from mw71
2. Read and understand the project specific setup below​
Create a new project in the Google AP developer console
04-08-_2018_13-05-22.jpg

Select "Google Drive API" in the dashboard​
04-08-_2018_13-09-40.jpg

Enable the API​
04-08-_2018_13-11-49.jpg

Create credentials​
04-08-_2018_13-14-25.jpg
As alerted by (1) on first entry you may create the credentials via button (2) right now. It is recommended to create credentials via button (3)​
04-08-_2018_13-55-29.jpg 04-08-_2018_13-55-36.jpg 04-08-_2018_13-55-49.jpg
You need to fill in "SHA-1" and "Package name". Both are available in your B4A IDE:​
04-08-_2018_13-29-14.jpg 04-08-_2018_13-34-38.jpg


Then receive your client ID:​
04-08-_2018_13-51-02.jpg


...then scroll down to the "Advanced Settings" and check-mark the "Enable Custom URI Scheme" (then it says it may take 5 mins to a few hours for the change to take effect). @John

EnableCustomURI.png


The program sequence is as follows:​
1. "GoogleDrive" is initialized in Main with the "ClientId" for Oauth2​
1.1 "GoogleDrive" initializes "GoogleOauth2" and informs about the "Scope= ....drive"​

2. Main calls "ConnectToDrive" and waits for message from "GD_Connected".​
2.1 "GoogleDrive" requests an AccessToken via "GoogleOauth2"​
2.2 "GoogleDrive" executes a "TestConnect" and reports via "GD_Connected" to waiting Main​

3. Main displays the AccessToken in Label1​

With the AccessToken several actions can be can performed with the drive:​
If you need a "ClientSecret" at some point create a new OAuth2 ClientId with the ApplicationType "Web application"

B4X:
' ------------------------------------------------------------------------------
Sub btnStart_Click As ResumableSub
   Log("#-btnStart_Click -----------------------------------")
   Label1.Text = "(Trying to connect to Google Drive...)"
   GD.Initialize(Me, "GD", ClientIdOauth, ClientSecret) ', AppApiKey)
   GD.ConnectToDrive
   wait for GD_Connected(mapRet As Map)
   ' ww~~-- ww~~-- ────────────────────
   '
   'Log("#-  x129, GD_Connected, mapRet=" & mapToPrettyString(mapRet) )
   Label1.Text = "Access_token= " & mapRet.GetDefault("access_token", "?")
   '
   GD.ShowFileList("")
   wait for GD_FileListResult(lstFiles As List)
   ' ww~~-- ww~~-- ────────────────────
   '
   Log("#-GD_FileListResult, lstFiles=" & lstToPrettyString(lstFiles) )
   ListView1.Clear
   Dim lblX As Label
   lblX = ListView1.SingleLineLayout.Label
   lblX.TextSize = 12
   lblX.TextColor = Colors.Green
   For i=0 To lstFiles.Size -1
       ListView1.AddSingleLine(lstFiles.Get(i))
   Next
   '
   btnCreaFolder.Enabled = True
   btnUpload.Enabled = True
   btnDownload.Enabled = True
   btnSearch.Enabled = True
   '
   Return Null
End Sub
' ------------------------------------------------------------------------------
Sub btnCreaFolder_Click As ResumableSub
   Log("#-")
   Log("#- ---*** ---*** ---*** ---*** ---*** ---*** ---*** ---*** ")
   Log("#-Sub btnCreaFolder_Click")
   Dim strFolderParentID As String = ""
   edtFolderToCreate.Text = "Testfolder_01"
   Label3.Text = $"(Trying to create folder ${edtFolderToCreate.Text})"$
   GD.CreateFolder(edtFolderToCreate.Text, strFolderParentID)
   wait for GD_FolderCreated(strFileId As String)
   ' ww~~-- ww~~-- ────────────────────
   '
   Label3.Text = "FileId= " & strFileId
   Log("#-  x178, strFileId=" & strFileId)
   Return Null
End Sub
' ------------------------------------------------------------------------------
Sub btnUpload_Click As ResumableSub
   Log("#-")
   Log("#- ---*** ---*** ---*** ---*** ---*** ---*** ---*** ---*** ")
   Log("#-Sub btnUpload_Click")
   Dim strFolderParentID As String = ""
   Dim strFilenameInGdrive As String = "testdatafile4"
   EditText2.Text = strFilenameInGdrive
   Dim strFileToUpload As String     = "testdata2.json"
   Label2.Text = $"(Trying to upload ${strFileToUpload})"$
   GD.UploadFile("", File.DirAssets, strFileToUpload, strFolderParentID, strFilenameInGdrive)
   wait for GD_FileUploadDone(strFileId As String)
   ' ww~~-- ww~~-- ────────────────────
   '
   Label2.Text = "FileId= " & strFileId
   Log("#-  x130, strFileId=" & strFileId)

   edtFileToDownload.Text = strFileId

   Return Null
End Sub
' ------------------------------------------------------------------------------
Sub btnSearch_Click As ResumableSub
   Log("#-")
   Log("#- ---*** ---*** ---*** ---*** ---*** ---*** ---*** ---*** ")
   Log("#-Sub btnSearch_Click")
   Label4.Text = $"(Trying to search for ${EditText2.Text.Trim})"$
   GD.SearchForFileID(EditText2.Text.Trim, EditText1.Text.Trim)
   wait for GD_FileFound(strFileId As String)
   ' ww~~-- ww~~-- ────────────────────
   '
   Label4.Text = "FileId= " & strFileId
   Log("#-  x170, strFileId=" & strFileId)
   Return Null
End Sub
' ------------------------------------------------------------------------------
Sub btnDownload_Click As ResumableSub
   Log("#-")
   Log("#- ---*** ---*** ---*** ---*** ---*** ---*** ---*** ---*** ")
   Log("#-Sub btnDownload_Click")

   Label5.Text = $"(Trying to download ${edtFileToDownload.Text.Trim})"$
   GD.DownloadFile(edtFileToDownload.Text.Trim, File.DirDefaultExternal, "aaa_file_" & edtFileToDownload.Text.Trim)
   wait for GD_FileDownloaded(strRet As String)
   ' ww~~-- ww~~-- ────────────────────
   '
   Label5.Text = "strRet= " & strRet
   Log("#-  x203, strRet=" & strRet)

End Sub
' ------------------------------------------------------------------------------
During the first run you might see a warning if the app hasn't been verified:

30-10-2023_09-29-13.png


Just click "Advanced" and "Go to grive..."

Now you're ready to test:

04-08-_2018_18-41-27.jpg


B4X:
'           Sub ConnectToDrive
'            Sub ShowFileList(ParentFolderID As String)
'            Sub CreateFolder(FolderName As String, ParentFolderID As String)
'            Sub UploadFile(FileId As String, LocalPath As String, LocalFilename As String, ParentFolderID As String, Name As String)
'            Sub DownloadFile(FileID As String, LocalPath As String, LocalFilename As String)
'            Sub SearchForFileID(SearchFile As String, ParentFolderID As String)
'            Sub SearchForFolderID(SearchFolder As String, ParentFolderID As String)

If something is fundamentally wrong, please explain and enclose a corresponding test project.

Questions should be asked in the Android questions forum including errormessages or code in code-tags.

Edit: Testproject updated to 19 due to module, lib and setupcondition bits.


Info: If you have trouble with apostrophes in the name of files or folders then this will help (thanks to @Dave O): https://www.b4x.com/android/forum/t...api-for-folder-names-with-apostrophes.130495/
 

Attachments

  • googledrivetest_19.zip
    19.6 KB · Views: 90
Last edited:

fredo

Well-Known Member
Licensed User
Longtime User
...disallowed_useragent error.

As Don mentioned it is most likely a misconfiguration.

Make sure you have understood and correctly performed the steps in #1.
In particular, the "Setup the API project and get the Client Id" section must be executed correctly.

Please do not answer here in this thread, because for important reasons it is always better to open a new thread.
 

JohnC

Expert
Licensed User
Longtime User
To do a quick update to this test project and get it running without using the custom HTTPJOB and HTTPUtils2Service modules, just do these steps:

1) Remove the HTTPJOB module from the project
2) Remote the HTTPUtils2Service module from the project
3) Add OKHTTPUtils2 Library (v2.82) to the project

Done.

P.S. Thanks @fredo for putting this together :)
 
Last edited:

fredo

Well-Known Member
Licensed User
Longtime User
Thank you for the suggestions @JohnC.
I updated the libraries, but the same error (400 invalid_request) that was previously reported has resurfaced.

After conducting some research, I've identified the reason behind this recurring error:
... Custom URI schemes are no longer supported on new Chrome apps and are disabled by default on new Android clients due to the risk of app impersonation.

Currently, I lack the resources to modify my example from #1.

If someone has a little more time at hand, the following tips may be useful to solve the problem:
 

JohnC

Expert
Licensed User
Longtime User
I am working on an application in which this test project is working - even for multipe scopes in the same app.

But maybe one of the below conditions is why it is working:

1) The device is running Android 10 (will that error only happen with more recent android versions?)
2) I have these SDKversions in the Manifest (will that error only happen with higher targetSDK?): <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26"/>

I had to make some slight modifications to the GoogleOauth2 module to support multiple scopes (ie. Gmail and Drive in the same app), but I can't see why that would be the reason it is working. This involved saving the token in a different file for each scope and making sure a new auth callback is handled by GoogleOauth2 only when it was requested so it won't corrupt the wrong scope.

When troubleshooting the mutliple-scope feature, I did find that after I did the Oauth for Gmail, I did have to clear the browser data in the chrome browser before the second Oauth process for the Google Drive would work - if this helps in any way. So maybe try uninstalling the app, then clearing the browser data before trying to do the Oauth again and see if that error happens.
 
Last edited:

JohnC

Expert
Licensed User
Longtime User
UPDATE: I think I know why it's working for me...

That google developer page you posted says "Custom URI schemes.......are disabled by default on new Android clients".

Since I am using an old application, I looked and it has "Enable custom URI scheme" already enabled for my app in the API and Services section of the dev console, and my app is probably working because back when I created the app in the dev console, custom URI schemes were "enabled" by default.

But as the message says, this setting is now "disabled by default", which suggestes that you should be able to manually re-enable it for any android client (android app) that was added to the play store console after the change of the default setting.

So, maybe the quick fix is to simply go into "API and Services" for your android app in the developer console, then click the pencil on the right side of your apps "Android Client" line item in the "OAuth 2.0 Client IDs" list to edit that line item, then scroll down to the "Advanced Settings" and check-mark the "Enable Custom URI Scheme" (then it says it may take 5 mins to a few hours for the change to take effect).

EnableCustomURI.png

 
Last edited:

fredo

Well-Known Member
Licensed User
Longtime User

Ferdari

Active Member
Licensed User
Longtime User
Thanks for the preliminary work on the research.
The suggestion is to store the data in a stream.

Unfortunately, I'm currently short on time to implement this approach in a new test project.

I'll try to address it specifically in the next few days.
Thanks in advance!🙌🙏
 

fredo

Well-Known Member
Licensed User
Longtime User
I'll try to address it ...
After some familiarization, I haven't made progress yet.
14-01-2024_14-06-51.png
Currently, I'm facing the issue that I haven't been able to get to the crucial point.
The response I consistently receive is:
JSON:
h_dl.ErrorMessage={
  "error": {
    "code": 400,
    "message": "Unsupported alt type \"media\" for non byte stream request.",
    "errors": [
      {
        "message": "Unsupported alt type \"media\" for non byte stream request.",
        "domain": "global",
        "reason": "badRequest"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}

So, it might take a bit more time until I figure out what the underlying issue is...

Perhaps the test project needs to be revised to match the latest api.
 

JohnC

Expert
Licensed User
Longtime User
After some familiarization, I haven't made progress yet.
View attachment 149686
Currently, I'm facing the issue that I haven't been able to get to the crucial point.
The response I consistently receive is:
JSON:
h_dl.ErrorMessage={
  "error": {
    "code": 400,
    "message": "Unsupported alt type \"media\" for non byte stream request.",
    "errors": [
      {
        "message": "Unsupported alt type \"media\" for non byte stream request.",
        "domain": "global",
        "reason": "badRequest"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}

So, it might take a bit more time until I figure out what the underlying issue is...

Perhaps the test project needs to be revised to match the latest api.

I wonder why it is reporting \"media\" and not "\media\"
 

Ferdari

Active Member
Licensed User
Longtime User
After some familiarization, I haven't made progress yet.
View attachment 149686
Currently, I'm facing the issue that I haven't been able to get to the crucial point.
The response I consistently receive is:
JSON:
h_dl.ErrorMessage={
  "error": {
    "code": 400,
    "message": "Unsupported alt type \"media\" for non byte stream request.",
    "errors": [
      {
        "message": "Unsupported alt type \"media\" for non byte stream request.",
        "domain": "global",
        "reason": "badRequest"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}

So, it might take a bit more time until I figure out what the underlying issue is...

Perhaps the test project needs to be revised to match the latest api.
There is a suggestion from Erel:
 
Top