I am creating an Android game with java, using CredentialManager to prompt the user to choose a Google account.
My code works fine, but in some cases it throws the NoCredentialException instead of another more suitable exception (such as a ConnectionException or a more generic InterruptedException):
public void signIn_credentialManager() {
String nonce = randomNonce();
GetCredentialRequest request;
googleIdOption = new GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId("...")
.setAutoSelectEnabled(true)
.setNonce(nonce)
.build();
request = new GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build();
log(LOG_TAG, "credentialManager.getCredentialAsync");
credentialManager.getCredentialAsync(
context,
request,
new CancellationSignal(),
executor,
new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
@Override
public void onResult(GetCredentialResponse result) {
log(LOG_TAG, "CredentialManagerCallback onResult");
}
@Override
public void onError(@NonNull GetCredentialException e) {
log(LOG_TAG, "CredentialManagerCallback Error!");
e.printStackTrace();
if(e instanceof NoCredentialException) {
// HERE
}
}
}
);
}
build.gradle (Android module):
plugins {
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
android {
compileSdk 35
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs('src/main/java')
aidl.srcDirs('src/main/java')
renderscript.srcDirs('src/main/java')
res.srcDirs('res')
assets.srcDirs('../assets')
jniLibs.srcDirs('libs')
}
}
packagingOptions {
resources.with {
excludes += ['META-INF/robovm/ios/robovm.xml',
'META-INF/DEPENDENCIES.txt', 'META-INF/DEPENDENCIES', 'META-INF/dependencies.txt', '**/*.gwt.xml']
pickFirsts += ['META-INF/LICENSE.txt', 'META-INF/LICENSE', 'META-INF/license.txt', 'META-INF/LGPL2.1',
'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/notice.txt']
}
}
defaultConfig {
applicationId 'app.mywebsite.myappname'
minSdkVersion 24
targetSdkVersion 35
versionCode 1
versionName "1.0.0 test"
}
namespace "app.mywebsite.myappname"
compileOptions {
sourceCompatibility "11"
targetCompatibility "11"
coreLibraryDesugaringEnabled true
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable true
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable false
}
}
buildFeatures {
buildConfig = true
}
}
repositories {
google()
}
configurations { natives }
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
implementation project(':core')
natives "com.badlogicgames.gdx:..."
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation platform('com.google.firebase:firebase-bom:33.10.0')
implementation 'com.google.firebase:firebase-crashlytics:19.4.1'
implementation 'com.google.firebase:firebase-analytics:22.3.0'
implementation "androidx.credentials:credentials:1.5.0"
implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
implementation "com.google.android.gms:play-services-games-v2:20.1.2"
implementation 'com.google.android.gms:play-services-auth:21.3.0'
}
tasks.register('run', Exec) {
def path
def localProperties = project.file("../local.properties")
if (localProperties.exists()) {
Properties properties = new Properties()
localProperties.withInputStream { instr ->
properties.load(instr)
}
def sdkDir = properties.getProperty('sdk.dir')
if (sdkDir) {
path = sdkDir
} else {
path = "$System.env.ANDROID_SDK_ROOT"
}
} else {
path = "$System.env.ANDROID_SDK_ROOT"
}
def adb = path + "/platform-tools/adb"
commandLine "$adb", 'shell', 'am', 'start', '-n', 'app.mywebsite.myappname/app.mywebsite.myappname.android.MainActivity'
}
eclipse.project.name = appName + "-android"
I am using a Samsung Galaxy M53 5G with Android 14, “Google Play system update” May 1, 2024 and One UI version 6.0.
Why is a wrong exception thrown? Does it perform some kind of connection request to get the available accounts? It should just reference the data in the device, without performing any kind of connection... (in this case, if I try again after the connection is back, it works without any change to the code or the device). As for changing the status of the App, should it resume the action, or not?
I need to handle all possible error cases correctly, but I can't do it if the wrong exception is thrown. Am I doing something wrong?
Thanks
Edit:
Cannot use getCause() because this is the exception, and it is not caused by another exception:
androidx.credentials.exceptions.NoCredentialException: No credentials available
at androidx.credentials.internal.ConversionUtilsKt.toJetpackGetException(ConversionUtils.kt:82)
at androidx.credentials.CredentialProviderFrameworkImpl.convertToJetpackGetException$credentials_release(CredentialProviderFrameworkImpl.kt:295)
at androidx.credentials.CredentialProviderFrameworkImpl$onGetCredential$outcome$2.onError(CredentialProviderFrameworkImpl.kt:162)
at androidx.credentials.CredentialProviderFrameworkImpl$onGetCredential$outcome$2.onError(CredentialProviderFrameworkImpl.kt:150)
at android.credentials.CredentialManager$GetCredentialTransport.lambda$onError$2(CredentialManager.java:694)
at android.credentials.CredentialManager$GetCredentialTransport.$r8$lambda$DFYUIuyfauIGF(Unknown Source:0)
at android.credentials.CredentialManager$GetCredentialTransport$$ExternalSyntheticLambda2.run(Unknown Source:6)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Based on your snippet, it seems you're interested in Google accounts on the device. To answer your question on whether any network call is made or not for fetching Google accounts, the answer is yes: although the accounts are on the device, we need to make sure that those accounts are in good shape; for example, those accounts may need reauthentication before being able to be used (and some other reasons as well). If your device doesn't have any network connectivity, those checks cannot be performed.
Now on the question of the error that is returned: remember that the CredentialManager accepts requests for different types of credentials (and those can be mixed together in one request) and for each type, there might be multiple providers. When CredentialManager sends the request to all the providers on the device, each provider may come back with some credentials or may come back with no credentials (matching your request). Each of those providers, for each type of credential, may have a different reason that they couldn't return a credential so CredentialManager cannot really give a single reason/error that can cover all the details, hence gives a generic error like the one you're seeing (for example, one provider can return no result since it couldn't find any credential matching your request, and a second provider can return empty result since there was a network issue at the moment that it made a network call and a third provider may return an empty result since your device didn't have a screen-lock set up, etc). As you can see, CredentialManager cannot return one exception that describes all of these, hence it uses that generic exception. Hope that helps.