cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Announcements
Want to know what we learned at IBC? Check out our learnings on media, remote working and more right here.

Dropbox API Support & Feedback

Find help with the Dropbox API from other developers.

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Dropbox Xamarin OAuth Flow Question - Retrieving the Access Token

Dropbox Xamarin OAuth Flow Question - Retrieving the Access Token

NachoMurphy
Explorer | Level 3

Hi there, 

I have a mobile app on Android that uses Xamarin's tools to do some basic dropbox operations. In the past we used the Sync SDK to use dropbox quite easily. With the June v1 shut off coming up, I'm now in the process of updatng the app to use v2 Core API, namely with techniques shown in the Java SDK examples(I've read on these forums that that's the recommended place to learn from for Android).

 

In any case, I'm doing the OAuth flow implementation and have a very similar question to this one: https://www.dropboxforum.com/t5/API-support/Cocoa-WebView-not-reacting-to-quot-Allow-quot-button-cli.... I appear to be close to redirecting back to my app, but I must be doing something wrong.

 

More info:

My app uses the Implicit grant type. I've registered redirect URIs on my apps page at Dropbox developer site(for instance, I have myappname://imagegallery/ as one of them). For the OAth flow, we're assuming that the user doesn't have the official Dropbox mobile app and instead is using an external Android browser(namely Chrome). I'm able to get to the dropbox site just fine to sign in, get to the ALLOW screen but after that I get an unhandled exception in my app. 

 

Here are what my AndroidManifest.xml intent filters looks like, with important values substituted:

<activity android:name="ImageGallery"
  android:configChanges="orientation|keyboard"
  android:launchMode="singleTask">
  <intent-filter>
    <data android:scheme="db-<My_App_Key_Here>"/>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>

  <intent-filter>
    <action android:name="android.intent.action.VIEW"></action>
    <data android:scheme="myappname" android:host="imagegallery"></data>
    <category android:name="android.intent.category.DEFAULT"></category>
    <category android:name="android.intent.category.BROWSABLE"></category>
  </intent-filter>
</activity>

 

I'm expecting the behavior to be as follows: The user signs in & presses the allow button, then the browser switches back to the onResume() of my imagegallery activity. Unfortunately I've been stuck at this point in the auth flow for a day or two. Any advice is much appreciated, thanks.

 

10 Replies 10

Greg-DB
Dropbox Staff
Can you share the full error/output for the exception you mentioned?

We can't offer much help with anything Xamarin-specific, but we'll be happy to take a look and see what's going on with the Dropbox side of things.

Thanks in advance!

NachoMurphy
Explorer | Level 3

Thanks for your response Greg. I'm happy to share more info.

All I'm really after is grabbing the access token from the android browser and returning to my app in order to use it. There's just a disconect between when the user hits 'Allow' and when they return to my app. I can see the access token etc in the redirect URL, but it's unclear how I'd take that for use. When I use localhost as the redirect address, after Allow an app chooser appears and I can choose my app to use to go to. However, that's when the exception occurs.

 

The only error message I see in my output is :

 

Thread finished: <Thread Pool> #4
The thread 'Unknown' (0x4) has exited with code 0 (0x0).
05-09 08:33:25.048 D/AndroidRuntime(23519): Shutting down VM
An unhandled exception occured.

 

It's not so much an specific exception, but seemingly nothing happens when the user gets redirected.

 

Here's what my ImageGallery activity code looks like that interacts with this:

        private void HandleUpload ()
	{
            // see if there is an existing user access token stored
            sp = GetSharedPreferences(SharedPrefsName, 
                    FileCreationMode.Private);
            AccessTokenValue = sp.GetString(AccessTokenKey, string.Empty);
            waitingOnDropboxUpload = true;

            if (AccessTokenValue.Equals(string.Empty))
            {
                string authAddress = getOAuthAddress();
                Intent browserIntent = new Intent(Intent.ActionView, Android.Net.Uri.Parse(authAddress));
                StartActivityForResult(Intent.CreateChooser(browserIntent, "Open With"), 1);
            }
// continue with upload etc. } public static string getOAuthAddress() { string localRedirect = "https://localhost/"; var redirect = DropboxOAuth2Helper.GetAuthorizeUri( OAuthResponseType.Token, DropboxAppKey, localRedirect, AuthActivity.RandomString(16), true, false); string result = redirect.ToString(); return result; }

And of course the relevant manifest snippet:

 

 

<activity android:name="ImageGallery" android:launchMode="singleTask">
      <intent-filter>
	<data android:scheme="db-<My_App_Key>" />
	<action android:name="android.intent.action.VIEW" />
	<category android:name="android.intent.category.BROWSABLE" />
	<category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW"></action>
        <data android:scheme="https" android:host="localhost"></data>
        <category android:name="android.intent.category.DEFAULT"></category>
        <category android:name="android.intent.category.BROWSABLE"></category>
      </intent-filter>
</activity>

 

As I mentioned in the first post, per other forum advice I'm looking at the Java SDK and trying to emulate what that does. However it looks like as the comments in Auth.java & AuthActivity.java indicate, the auth flow used there is based on the SDK's OpenWith code for use with the official DropBox mobile app. 

 

Now, I'm exploring using a WebView to keep everything inside my app for more control. Even then, I'll need to figure out how to grab the returned parameters from the redirect url. Thanks again for your help.

NachoMurphy
Explorer | Level 3

I spoke too soon Greg, here's more exception details after I each time I get out of break mode:

 

05-09 09:01:00.418 E/AndroidRuntime(27739): FATAL EXCEPTION: main
05-09 09:01:00.418 E/AndroidRuntime(27739): Process: MyApp.App, PID: 27739
05-09 09:01:00.418 E/AndroidRuntime(27739): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{MyApp.App/MyApp.App.ImageGallery}: java.lang.ClassNotFoundException: Didn't find class "MyApp.App.ImageGallery" on path: DexPathList[[zip file "/data/app/MyApp.App-1/base.apk"],nativeLibraryDirectories=[/data/app/MyApp.App-1/lib/arm, /vendor/lib, /system/lib]]
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2236)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread.access$800(ActivityThread.java:151)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.os.Handler.dispatchMessage(Handler.java:102)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.os.Looper.loop(Looper.java:135)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread.main(ActivityThread.java:5254)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at java.lang.reflect.Method.invoke(Native Method)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at java.lang.reflect.Method.invoke(Method.java:372)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
05-09 09:01:00.418 E/AndroidRuntime(27739): Caused by: java.lang.ClassNotFoundException: Didn't find class "MyApp.App.ImageGallery" on path: DexPathList[[zip file "/data/app/MyApp.App-1/base.apk"],nativeLibraryDirectories=[/data/app/MyApp.App-1/lib/arm, /vendor/lib, /system/lib]]
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.Instrumentation.newActivity(Instrumentation.java:1066)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2226)
05-09 09:01:00.418 E/AndroidRuntime(27739): 	... 10 more
05-09 09:01:00.418 E/AndroidRuntime(27739): 	Suppressed: java.lang.ClassNotFoundException: MyApp.App.ImageGallery
05-09 09:01:00.418 E/AndroidRuntime(27739): 		at java.lang.Class.classForName(Native Method)
05-09 09:01:00.418 E/AndroidRuntime(27739): 		at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
05-09 09:01:00.418 E/AndroidRuntime(27739): 		at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
05-09 09:01:00.418 E/AndroidRuntime(27739): 		at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
05-09 09:01:00.418 E/AndroidRuntime(27739): 		... 13 more
05-09 09:01:00.418 E/AndroidRuntime(27739): 	Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available

Hope this helps.

 

Greg-DB
Dropbox Staff

Thanks for the additional information! 

 

If I understand correctly, you're using the API v2 .NET SDK, but you're trying to implement the app authorization flow the way the API v2 Java SDK does it. These two SDKs work differently though, so there will be a disconnect between the two. You won't be able to easily reconcile the two, so I recommend picking one or the other (whatever makes the most sense for your app/platform) and following the documentation/sample for the one you pick.

 

For reference, the Android app sample that uses the API v2 Java SDK uses a custom URL scheme as defined in the Manifest to directly receive the redirect after sending the user through the app authorization flow. Dropbox does some special handling for you to make use of that "db-<app_key>" URL scheme.

 

The API v2 .NET SDK on the other hand uses either the standard OAuth 2 'token' or 'code' flow. You can see how the code flow is initiated here and received here. There's a sample of initiating the token flow here (which your code so far uses) and receiving it here. I.e., you can use ParseTokenFragment to parse the token from the redirect URI.

 

(Also, I should note that you should no longer use web view for the OAuth app authorization flow, in order to support the embedded Google Sign In flow, as Google is no longer allowing their sign in flow to be processed in web views.)

 

In any case, the actual exception you're getting seems to be a ClassNotFoundException for class "MyApp.App.ImageGallery". That's not part of the Dropbox SDK though, and seems to just be part of your app, so I'm afraid I can't offer help with that in particular.

NachoMurphy
Explorer | Level 3

Thanks for the response.

In light of the Web View news, I'm going to opt for only using the .NET SDK after all. It's true that I had been using parts of both SDKs before. It's frustrating because in the time between my posts I had implemented a WebView soution that was partially working. Oh well, so much for that.

 

Back to the issue then: I'm using the .NET approach to the OAuth flow. I use the android browser to navigate to the authorize URL. Then the user would sign in if needed and hit Allow. My newest issue then becomes: I get how the .NET SDK example uses BrowserNavigating to capture the event and can then parse the access token from the URI, but how would I do that for android? Is there a way to attach a listener to the browser intent? This is the immediate obstacle I'm facing at present.

 

Here's some revised code:

        private void HandleDropboxOperation()
        {
            waitingOnDropboxUpload = true;
            taskCode = AuthActivity.RandomString(16);
            Toast.MakeText(this, "Uploading...", ToastLength.Short).Show();

            //see if there is an existing user access token stored
            sp = GetSharedPreferences(SharedPrefsName, FileCreationMode.Private);
            AccessTokenValue = sp.GetString(AccessTokenKey, string.Empty);
            
            if (AccessTokenValue.Equals(string.Empty))
            {
                this.OAth2State = Guid.NewGuid().ToString("N");
                var authorizeUri = DropboxOAuth2Helper.GetAuthorizeUri(OAuthResponseType.Token, DropboxAppKey, new Uri(RedirectUri), state: Guid.NewGuid().ToString("N"));
                //no browser object available - use intent instead
                Android.Net.Uri.Builder b = new Android.Net.Uri.Builder();
                b.AppendPath(authorizeUri.ToString());
                Intent browserIntent = new Intent(Intent.ActionView, b.Build());
                StartActivityForResult(Intent.CreateChooser(browserIntent, "Open With"), 1);

//below is from the .NET SDK code - how to implement in Android? //OAuth2Response result = DropboxOAuth2Helper.ParseTokenFragment(e.Uri);
//proceed with upload after token is obtained } else { InitiateUpload(); } }

NachoMurphy
Explorer | Level 3

Similar question as well: For the Java SDK, does the auth flow in the android part of the project require the official Dropbox app? It still seems unclear to me if using the Java SDK relies on having the official Dropbox App installed or not. If not, it's possible to just use web browser auth then, correct?

Greg-DB
Dropbox Staff

In general, you should use a redirect URI to redirect the user back to somewhere that your app can access the redirect URI with the parameters/fragment added to it.

 

For a server-side app, that's generally just some route on the app's site.

 

For a client-side app, that can be a custom local URL scheme. For example, the Java SDK on Android has you use a db-<app_key> URL scheme by default. You can probably replicate the same/similar technique, though I can't speak to if/how using Xamarin/.NET on Android will complicate that. Were you able to fix the ClassNotFoundException issue? If so, I recommend trying to use the db-<app_key> URL scheme you already set up as your redirect URI. That should re-launch your app after the OAuth app authorization flow, at which point you can grab the full URL and use ParseTokenFragment to get the information from it, from within OnResume or wherever you catch that event in your Android/Xamarin setup. (The equivalent of that step in the Android sample app is here.)

 

Anyway, for the Java SDK on Android, the SDK does some extra work such that the official Dropbox for Android app is used for the app authorization flow (so the user doesn't have to log in again) if it's installed. It's not required though, and the browser will be used instead if it's not.

NachoMurphy
Explorer | Level 3

Thanks - yes, I was able to resolve the ClassNotFound issue. As it happens, I'm pursuing the Java SDK approach after all; it makes more sense and is easier to use for Android than the .NET SDK. I'm still determined to solve this!

 

I'm still stuck at the redirect point in the auth flow, however. It just tries to redirect in the browser - I can even see the parameters in the address field.

redirect_1.png

redirect_2.png

When I reach the redirect point, I'm still not getting back to my app. The redirect URL parameter remains as https://localhost/

I've still got it set in the developer app settings page as well:

Screen Shot 05-11-17 at 09.54 AM.PNG

My manifest is much the same still:

 

<activity android:name=".ImageGallery" >      
      <intent-filter>
        <data android:scheme="db-<appkeygoeshere>" />
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
        <data android:scheme="https" android:host="localhost"></data>
        <action android:name="android.intent.action.VIEW"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
        <category android:name="android.intent.category.BROWSABLE"></category>
      </intent-filter>
</activity>

It's got to be something trivial I think, like a typo in my actual auth address or manifest. Right now the auth address looks like this(sensitive values removed):

 

"https://www.dropbox.com/1/oauth2/authorize?client_id=db-mydropboxappkey& response_type=token&redirect_uri=https://localhost/& state=mygeneratedstateid"

 

A couple more questions that might help:

In my dropbox developer apps page, this app still has the development status - I don't have to be in production status for this to work right?

 

Also, I've implemented parts of the Auth and AuthActivity classes from the Java SDK to help me sort this out. One thing I've noticed is when I run AuthActivity.checkAppBeforeAuth, I never get any Activity results when it calls packageManager.QueryIntentActivities. Would that indicate I still don't have the manifest set correctly? 

 

Edit: I'm still getting the class not found error after all. 

It shows up after I exit debug mode, so without being patient I had thought it was gone. 

 

One thing I noticed: If I generate an Intent address for ImageGallery, the expanded path to the activity is MyApp.App/md5fac3f4919a5b0b9804623e8f923fe925.ImageGallery. If I use that instead of just .ImageGallery in the <activity> element of my manifest, there's a compilation error. Perhaps that's my issue? I can buid when I append that to my <manifest> element however, so I'll try that next.

 

Greg-DB
Dropbox Staff

As long as you're able to use it in Xamarin, using the Dropbox Java SDK is probably a better route. In that case, I highly recommend just using the pre-built Android app authorization flow. 

 

In that case, there should just be a few things you need to do:

1) Set up your manigest as shown here:

https://github.com/dropbox/dropbox-sdk-java/blob/master/examples/android/src/main/AndroidManifest.xm...

2) Start the authorization flow using startOAuth2Authentication as shown here:

https://github.com/dropbox/dropbox-sdk-java/blob/master/examples/android/src/main/java/com/dropbox/c...

3) Receive the result using getOAuth2Token as shown here:

https://github.com/dropbox/dropbox-sdk-java/blob/master/examples/android/src/main/java/com/dropbox/c...

 

Using that, you don't need to configure the redirect URI, etc. yourself. I'm not sure I follow your question about implementing Auth/AuthActivity though. Those are part of the SDK, so you shouldn't need to implement them yourself. If you're not getting anything back from QueryIntentActivities though, that should indicate that you don't have your URL scheme registered correctly. Double check your manifest against the link in #1 above.

 

And that's correct, development status is fine for now. Production status would just enable more users to connect to your app, but it's not necessary while you're developing and using your own account.

 

I'm afraid I cant offer help setting up the ImageGallery activity itself.

Need more support?
Who's talking

Top contributors to this post

  • User avatar
    NachoMurphy Explorer | Level 3
  • User avatar
    Greg-DB Dropbox Staff
What do Dropbox user levels mean?