Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Android app to use local emulator for development #2010

Merged
merged 21 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ detekt {
}

task checkCode(type: GradleBuild) {
tasks = ['checkstyle', 'lintDebug', 'ktfmtCheck', 'detekt']
tasks = ['checkstyle', 'lintLocalDebug', 'ktfmtCheck', 'detekt']
}

task clean(type: Delete) {
Expand Down
8 changes: 4 additions & 4 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ steps:
args:
- '-c'
- |
./gradlew -PdisablePreDex assembleStaging assembleStagingUnitTest -PtestBuildType=staging
./gradlew -PdisablePreDex assembleDevStaging assembleDevStagingUnitTest -PtestBuildType=staging

# TODO(#1547): Re-enable once instrumentation tests are fixed.
# if [[ "${_PUSH_TO_MASTER}" ]]; then
# ./gradlew -PdisablePreDex assembleStagingAndroidTest -PtestBuildType=staging
# ./gradlew -PdisablePreDex assembleDevStagingAndroidTest -PtestBuildType=staging
# fi

# Run code quality checks
Expand All @@ -114,11 +114,11 @@ steps:
args:
- '-c'
- |
./gradlew -PdisablePreDex testStagingUnitTest 2> unit-test-logs.txt || echo "fail" > build-status.txt
./gradlew -PdisablePreDex testDevStagingUnitTest 2> unit-test-logs.txt || echo "fail" > build-status.txt
cat unit-test-logs.txt

# TODO: Add a check for _PUSH_TO_MASTER
./gradlew jacocoTestStagingUnitTestReport
./gradlew jacocoTestDevStagingUnitTestReport

- name: 'gcr.io/$PROJECT_ID/android:34'
id: &authenticate_gcloud 'Authorize gcloud'
Expand Down
2 changes: 1 addition & 1 deletion config/lint/lint.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* Runs automatically with every build on Google Cloud Build.
*
* To run manually:
* $ ./gradlew lintDebug
* $ ./gradlew lintLocalDebug
*
* Reports (both html and xml formats) are stored under: ground/build/reports/lint/
*/
Expand Down
3 changes: 3 additions & 0 deletions config/lint/lint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,7 @@
<issue id="UseCompatTextViewDrawableXml" severity="error" />
<issue id="ValidActionsXml" severity="error" />
<issue id="WrongThreadInterprocedural" severity="error" />
<issue id="usesCleartextTraffic">
<ignore path="AndroidManifest.xml"/>
</issue>
</lint>
19 changes: 19 additions & 0 deletions ground/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ android {
multiDexEnabled true
// For rendering vector map markers.
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "EMULATOR_HOST", "\"10.0.2.2\""
buildConfigField "int", "FIRESTORE_EMULATOR_PORT", "8080"
buildConfigField "int", "AUTH_EMULATOR_PORT", "9099"
}

// Use flag -PtestBuildType with desired variant to change default behavior.
Expand Down Expand Up @@ -112,6 +115,22 @@ android {
}
}

flavorDimensions "backend"
productFlavors {
local {
dimension "backend"
versionNameSuffix "-local"
buildConfigField "boolean", "USE_EMULATORS", "true"
manifestPlaceholders.usesCleartextTraffic = true
}
dev {
dimension "backend"
versionNameSuffix "-dev"
buildConfigField "boolean", "USE_EMULATORS", "false"
manifestPlaceholders.usesCleartextTraffic = false
}
}

buildFeatures {
dataBinding true
viewBinding true
Expand Down
3 changes: 2 additions & 1 deletion ground/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher">
android:theme="@style/AppTheme.Launcher"
android:usesCleartextTraffic="${usesCleartextTraffic}">

<service
android:name="com.google.android.ground.persistence.remote.firebase.FirebaseMessagingService"
Expand Down
1 change: 0 additions & 1 deletion ground/src/main/java/com/google/android/ground/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ object Config {
const val DB_NAME = "ground.db"

// Firebase Cloud Firestore settings.
const val FIRESTORE_PERSISTENCE_ENABLED = false
const val FIRESTORE_LOGGING_ENABLED = true

// Photos
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package com.google.android.ground.persistence.remote

import com.google.android.ground.Config
import com.google.android.ground.BuildConfig.EMULATOR_HOST
import com.google.android.ground.BuildConfig.FIRESTORE_EMULATOR_PORT
import com.google.android.ground.BuildConfig.USE_EMULATORS
import com.google.android.ground.persistence.remote.firebase.FirebaseStorageManager
import com.google.android.ground.persistence.remote.firebase.FirestoreDataStore
import com.google.android.ground.persistence.remote.firebase.FirestoreUuidGenerator
Expand Down Expand Up @@ -51,11 +53,15 @@ abstract class RemotePersistenceModule {

companion object {
@Provides
fun firebaseFirestoreSettings(): FirebaseFirestoreSettings {
return FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(Config.FIRESTORE_PERSISTENCE_ENABLED)
.build()
}
fun firebaseFirestoreSettings(): FirebaseFirestoreSettings =
with(FirebaseFirestoreSettings.Builder()) {
if (USE_EMULATORS) {
host = "$EMULATOR_HOST:$FIRESTORE_EMULATOR_PORT"
isSslEnabled = false
}
isPersistenceEnabled = false
build()
}

/** Returns a reference to the default Storage bucket. */
@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.ground.repository

import com.google.android.ground.BuildConfig.USE_EMULATORS
import com.google.android.ground.model.Role
import com.google.android.ground.model.User
import com.google.android.ground.persistence.local.LocalValueStore
Expand Down Expand Up @@ -54,7 +55,9 @@ constructor(
Timber.d("Skipped refreshing user profile as device is offline.")
return
}
remoteDataStore.refreshUserProfile()
if (!USE_EMULATORS) {
remoteDataStore.refreshUserProfile()
}
gino-m marked this conversation as resolved.
Show resolved Hide resolved
}

suspend fun getUser(userId: String): User = localUserStore.getUser(userId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.android.ground.BuildConfig.AUTH_EMULATOR_PORT
import com.google.android.ground.BuildConfig.EMULATOR_HOST
import com.google.android.ground.BuildConfig.USE_EMULATORS
import com.google.android.ground.R
import com.google.android.ground.coroutines.ApplicationScope
import com.google.android.ground.coroutines.IoDispatcher
Expand All @@ -37,10 +40,14 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx2.asFlow
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import timber.log.Timber

private val SIGN_IN_REQUEST_CODE = AuthenticationManager::class.java.hashCode() and 0xffff
private val signInRequestCode = AuthenticationManager::class.java.hashCode() and 0xffff

/** Used when running against local Firebase Emulator Suite. */
private val anonymousUser = User("nobody", "nobody", "Test User")

class GoogleAuthenticationManager
@Inject
Expand All @@ -62,7 +69,7 @@ constructor(
.build()

externalScope.launch {
activityStreams.getActivityResults(SIGN_IN_REQUEST_CODE).asFlow().collect {
activityStreams.getActivityResults(signInRequestCode).asFlow().collect {
onActivityResult(it)
}
}
Expand Down Expand Up @@ -91,11 +98,24 @@ constructor(

override fun signIn() {
signInState.onNext(SignInState.signingIn())
if (USE_EMULATORS) {
gino-m marked this conversation as resolved.
Show resolved Hide resolved
signInAnonymously()
} else {
showSignInDialog()
}
}

private fun signInAnonymously() =
externalScope.launch {
getFirebaseAuth().signInAnonymously().await()
signInState.onNext(SignInState.signedIn(anonymousUser))
}

private fun showSignInDialog() =
activityStreams.withActivity {
val signInIntent = getGoogleSignInClient(it).signInIntent
it.startActivityForResult(signInIntent, SIGN_IN_REQUEST_CODE)
it.startActivityForResult(signInIntent, signInRequestCode)
}
}

override fun signOut() {
externalScope.launch {
Expand All @@ -105,7 +125,15 @@ constructor(
}
}

private suspend fun getFirebaseAuth() = withContext(ioDispatcher) { FirebaseAuth.getInstance() }
private suspend fun getFirebaseAuth() =
withContext(ioDispatcher) {
val auth = FirebaseAuth.getInstance()
if (USE_EMULATORS) {
// Use the auth emulator so we can sign-in anonymously during dev.
auth.useEmulator(EMULATOR_HOST, AUTH_EMULATOR_PORT)
}
gino-m marked this conversation as resolved.
Show resolved Hide resolved
auth
}

private fun getGoogleSignInClient(activity: Activity): GoogleSignInClient =
// TODO: Use app context instead of activity?
Expand Down Expand Up @@ -135,7 +163,8 @@ constructor(
private fun getFirebaseAuthCredential(googleAccount: GoogleSignInAccount): AuthCredential =
GoogleAuthProvider.getCredential(googleAccount.idToken, null)

private suspend fun getFirebaseUser(): User? = getFirebaseAuth().currentUser?.toUser()
private suspend fun getFirebaseUser(): User? =
if (USE_EMULATORS) anonymousUser else getFirebaseAuth().currentUser?.toUser()
gino-m marked this conversation as resolved.
Show resolved Hide resolved

private fun FirebaseUser.toUser(): User =
User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString())
Expand Down
9 changes: 9 additions & 0 deletions sharedTest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ android {
staging {
}
}
flavorDimensions "backend"
productFlavors {
local {
dimension "backend"
}
dev {
dimension "backend"
}
}
}

dependencies {
Expand Down