Build errors with Dropbox in Android

I’m getting a strange pair of build errors trying to incorporate Dropbox’s Android API in my Oxygene project (10.0.0.2351). These are the errors:

  1. Indirectly used type "okhttp3.OkHttpClient" is defined in an unreferenced assembly ()
  2. Parameter 1 is "okhttp3.OkHttpClient", should be "okhttp3.OkHttpClient", in call to constructor OkHttp3Requestor(client: okhttp3.OkHttpClient)

I’m not sure what the first is referring to, and the second seems to be throwing an error because it found what it expected to find. Maybe this is an issue of conflicting versions grabbed by the package manager? okhttp3 is a dependency listed in the pom file for the dropbox-core-sdk project on Maven. It’s a SquareUp project that has gone through a few different lives.

I’m including the Dropbox API via Gradle package reference:
<GradleReference Include="com.dropbox.core:dropbox-core-sdk:3.0.9">

And here is the full code I’m testing (note that the constants for the api key and access token are stubbed in the version I’m posting):

NAMESPACE DropboxHelper;

INTERFACE

USES
    android.os, android.content,
    {}
    com.dropbox.core, com.dropbox.core.v2, com.dropbox.core.v2.users, com.dropbox.core.android, com.dropbox.core.http;

    CONST
      kDropboxAppKey = 'dropbox_app_key';
      kClientId = 'dbx-client';
      kSettingsDropboxAccessToken = 'fake_oauth_access_token';

TYPE
  DropboxCallback = PUBLIC INTERFACE
    PROCEDURE OnComplete (account: FullAccount);
    PROCEDURE OnError (e: Exception);
  END;


  AsyncDropboxGetAccountTask = PUBLIC CLASS(AsyncTask<Void, Void, FullAccount>)
  PRIVATE
    fDropboxClient: DbxClientV2;
    fCallback: DropboxCallback;
    fException: Exception;

  PROTECTED
    PROCEDURE onPreExecute;
    OVERRIDE;
    FUNCTION doInBackground (theParams: ARRAY OF Void): FullAccount;
    OVERRIDE;
    PROCEDURE onPostExecute (theAccount: FullAccount);
    OVERRIDE;

  PUBLIC
    CONSTRUCTOR (client: DbxClientV2;
    callback: DropboxCallback);

  END;

  DropboxClientFactory = PUBLIC CLASS
      PRIVATE CLASS sDropboxClient: DbxClientV2;

      PUBLIC CLASS PROCEDURE Init (accessToken: String);
      PUBLIC CLASS FUNCTION GetClient: DbxClientV2;
      END;

  DropboxHelper = PUBLIC CLASS
      PUBLIC
        fContext: Context;

        PROCEDURE DoSetup;
        PROCEDURE DoTest (accessToken: String);
  END;

IMPLEMENTATION

{----------------- AsyncDropboxGetAccountTask methods --------------------}

CONSTRUCTOR AsyncDropboxGetAccountTask (client: DbxClientV2;
callback: DropboxCallback);

BEGIN
  fDropboxClient := client;
  fCallback := callback;
END;


PROCEDURE AsyncDropboxGetAccountTask.onPreExecute;

BEGIN
END;


  FUNCTION AsyncDropboxGetAccountTask.doInBackground (theParams: ARRAY OF Void): FullAccount;

  BEGIN
    TRY
      EXIT fDropboxClient.users.getCurrentAccount;
    EXCEPT
      ON e: DbxException DO
        fException := e;
    END;

    EXIT NIL;
END;


  PROCEDURE AsyncDropboxGetAccountTask.onPostExecute (theAccount: FullAccount);

  BEGIN
    INHERITED onPostExecute(theAccount);

    IF fException = NIL THEN { was successful }
      fCallback.OnComplete(theAccount)
    ELSE
      fCallback.OnError(fException);
  END;

{----------------- DropboxClientFactory methods --------------------}

  CLASS PROCEDURE DropboxClientFactory.Init (accessToken: String);

  VAR
  requestConfig: DbxRequestConfig;
  aBuilder: DbxRequestConfig.Builder;

  BEGIN
  IF sDropboxClient = NIL THEN
  BEGIN
    aBuilder := DbxRequestConfig.newBuilder(kClientId).withHttpRequestor(NEW OkHttp3Requestor(OkHttp3Requestor.defaultOkHttpClient()));
    requestConfig := aBuilder.build;

    sDropboxClient := NEW DbxClientV2(requestConfig, accessToken);
  END;
END;


CLASS FUNCTION DropboxClientFactory.GetClient: DbxClientV2;

BEGIN
  IF sDropboxClient = NIL THEN
    RAISE NEW IllegalStateException("Client not initialized.");

  EXIT sDropboxClient;
END;

{----------------- DropboxHelper methods --------------------}

  PROCEDURE DropboxHelper.DoSetup;

    VAR
      accessToken, uid, storedUid: String;

  BEGIN
    accessToken := kSettingsDropboxAccessToken;
    SELF.DoTest(accessToken);
  END;


  PROCEDURE DropboxHelper.DoTest (accessToken: String);

  BEGIN
      DropboxClientFactory.Init(accessToken);

      Auth.startOAuth2Authentication(fContext, kDropboxAppKey);
  END;

END.

I doubt you need it, but for reference, I’m following the official Dropbox example on Github pretty closely:

Dropbox Example Contents

Update:
I was able to build and deploy successfully. The solution was to include the okhttp3 package directly as a second gradle reference in my project settings. The dropbox repo’s pom file lists both okhttp and okhttp3 as dependencies, so perhaps it is an issue of conflicting classes with the same name?

Also, in case anyone finds this thread in the future, the Gradle references must include attribute <Private>True</Private> or it will result in a ClassDefNotFound exception at runtime due to Dropbox’s decision to require their Auth activity to be added to the app manifest.

<GradleReference Include="com.squareup.okhttp3:okhttp:3.12.0">
	<Private>True</Private>
</GradleReference>
<GradleReference Include="com.dropbox.core:dropbox-core-sdk:3.0.11">
	<Private>True</Private>
</GradleReference>

What this error means is that you’re assembly a.jar, and access an API that uses (e.g. returns) a type defined in assembly b.jar — but you don’t have a direct reference to b.jar in your project, so the compiler does not know (the details of) the type.

An error like this is sometimes a compiler bug, but in this case it’s pretty likely a side effect of what you found out later — that there are two different versions of the type (from different (versions of the same) assemblies), one in the version you reference directly, and one in the one you don’t bt that a.jar references. To the compiler, these are two different types, even though they are named the same,

I agree that error reporting could be improved in this case, I’ll log a request.

I’d love to see a test case project that fails, so I can see what exactly it resolves via Gradle. it could be bug in the Gradle code, or it could be that the package info really is conflicting, and explicitly adding the second one is the right choice/fix.

—marc

Yes. Copy Local (aka Private) is what defines if the .jar will be packaged into the Android .apk (true) or if it is expected to just exist on the target system (false). Since on Android only android.jar is provided by the system, everything else needs to be Copy-Localed.

(I believe newly added references on Android get marked as such by default, at least in Fire/Water, correct?)