The task I am trying to complete is to connect to a Google Cloud MySQL database via an Android application. I am following this guide: https://cloud.google.com/sql/docs/mysql/connect-connectors. I have added my IP address to the allowed networks for the database instance and have enabled the cloud SQL API. I used the example code as a starting point, but I get this error when I run the application (using a simulator):
2023-03-25 15:58:37.727 13755-13755/com.example.connectiontest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.connectiontest, PID: 13755
com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Could not create connection to database server.
at com.zaxxer.hikari.pool.HikariPool.throwPoolInitializationException(HikariPool.java:595)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:581)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81)
at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53)
at android.view.View.performClick(View.java:7506)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202)
at android.view.View.performClickInternal(View.java:7483)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29334)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: java.sql.SQLNonTransientConnectionException: Could not create connection to database server.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:997)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:814)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:444)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:237)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:121)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:477)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:560)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81)
at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53)
at android.view.View.performClick(View.java:7506)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202)
at android.view.View.performClickInternal(View.java:7483)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29334)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: java.lang.RuntimeException: Unable to obtain credentials to communicate with the Cloud SQL API
at com.google.cloud.sql.core.CoreSocketFactory$ApplicationDefaultCredentialFactory.create(CoreSocketFactory.java:392)
at com.google.cloud.sql.core.CoreSocketFactory.getInstance(CoreSocketFactory.java:140)
at com.google.cloud.sql.core.CoreSocketFactory.connect(CoreSocketFactory.java:256)
at com.google.cloud.sql.core.CoreSocketFactory.connect(CoreSocketFactory.java:215)
at com.google.cloud.sql.mysql.SocketFactory.connect(SocketFactory.java:57)
at com.google.cloud.sql.mysql.SocketFactory.connect(SocketFactory.java:43)
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:63)
at com.mysql.cj.NativeSession.connect(NativeSession.java:120)
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:944)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:814)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:444)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:237)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:121)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:477)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:560)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81)
at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53)
at android.view.View.performClick(View.java:7506)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202)
at android.view.View.performClickInternal(View.java:7483)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29334)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
at com.google.auth.oauth2.DefaultCredentialsProvider.getDefaultCredentials(DefaultCredentialsProvider.java:125)
2023-03-25 15:58:37.728 13755-13755/com.example.connectiontest E/AndroidRuntime: at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:125)
at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:97)
at com.google.cloud.sql.core.CoreSocketFactory$ApplicationDefaultCredentialFactory.create(CoreSocketFactory.java:390)
... 33 more
I read up on the Application Default Credentials, but I do not believe environment variables are a thing on mobile devices (correct me if I am wrong), and I have been unsuccessful in finding an alternate method of authentication in Google's docs. My simplified application code is below for reference.
Main Activity
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class MainActivity extends AppCompatActivity {
private static final String CONNECTION_NAME = "***";
private static final String DB_NAME = "***";
private static final String DB_USER = "***";
private static final String DB_PASSWORD = "***";
Button testConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testConn = (Button) findViewById(R.id.button);
testConn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String jdbcURL = String.format("jdbc:mysql:///%s", DB_NAME);
Properties connProps = new Properties();
connProps.setProperty("user", DB_USER);
connProps.setProperty("password", DB_PASSWORD);
connProps.setProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
connProps.setProperty("cloudSqlInstance", CONNECTION_NAME);
HikariConfig config = new HikariConfig();
config.setJdbcUrl(jdbcURL);
config.setDataSourceProperties(connProps);
config.setConnectionTimeout(10000);
HikariDataSource connectionPool = new HikariDataSource(config);//this is where the code breaks
try {
Connection conn = connectionPool.getConnection();
String stmt = "SELECT test_field FROM test_table limit 1;";
PreparedStatement selectStmt = conn.prepareStatement(stmt);
selectStmt.setQueryTimeout(10);
ResultSet rs = selectStmt.executeQuery();
while (rs.next()) {
Toast.makeText(getApplicationContext(), rs.getString("test_field"), Toast.LENGTH_LONG).show();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
});
}
}
build.gradle
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.connectiontest'
compileSdk 33
defaultConfig {
applicationId "com.example.connectiontest"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
packagingOptions {
resources.excludes.add("META-INF/*")
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation files('libs/mysql-connector-j-8.0.32.jar')
implementation 'com.zaxxer:HikariCP:3.4.5'
implementation 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.11.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
Any help is be greatly appreciated!