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

Adding Firebase Auth #222

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
10 changes: 9 additions & 1 deletion android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,23 @@ android {
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "co.touchlab.droidcon.london"
applicationId = "co.touchlab.droidconauthtest"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 20201
versionName = "2.2.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

val clientId = properties.getProperty("clientId", "")
buildConfigField("String", "CLIENT_ID", clientId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the readme with instructions on how to set your clientID for both android and iOS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the readme

}
packaging {
resources.excludes.add("META-INF/*.kotlin_module")
}
if (releaseEnabled) {
signingConfigs {
create("release") {

keyAlias = "key0"
keyPassword = releasePassword
storeFile = file("./release.jks")
Expand Down Expand Up @@ -65,6 +69,7 @@ android {

buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
Expand All @@ -89,6 +94,9 @@ dependencies {
implementation(libs.firebase.analytics)
implementation(libs.firebase.crashlytics)

implementation(libs.firebase.auth)
implementation(libs.playservices.auth)

implementation(libs.hyperdrive.multiplatformx.api)

implementation(libs.bundles.androidx.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package co.touchlab.droidcon.android

import android.app.Activity
import android.content.IntentSender
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import co.touchlab.droidcon.BuildConfig
import co.touchlab.droidcon.domain.service.AuthenticationService
import co.touchlab.kermit.Logger
import com.google.android.gms.auth.api.identity.BeginSignInRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import org.brightify.hyperdrive.utils.WeakReference

class FirebaseService : AuthenticationService(FirebaseAuth.getInstance().currentUser != null) {

private val logger = Logger.withTag("AuthenticationService")
private val clientId = BuildConfig.CLIENT_ID

private lateinit var weakActivity: WeakReference<Activity>
private lateinit var weakLauncher: WeakReference<ActivityResultLauncher<IntentSenderRequest>>

fun setActivity(
activity: Activity,
launcher: ActivityResultLauncher<IntentSenderRequest>,
) {
weakActivity = WeakReference(activity)
weakLauncher = WeakReference(launcher)
}

override fun performGoogleLogin(): Boolean {
weakActivity.get()?.let { activity ->
logger.i { "Performing Google Login" }
val oneTapClient = Identity.getSignInClient(activity)
val signInRequest = BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
.setServerClientId(clientId)
.setFilterByAuthorizedAccounts(false)
.build()
)
.build()
logger.v { "Beginning Sign In" }
oneTapClient.beginSignIn(signInRequest)
.addOnSuccessListener(activity) { result ->
logger.v { "Success! Starting Intent Sender" }
try {
val request = IntentSenderRequest
.Builder(result.pendingIntent.intentSender)
.build()
weakLauncher.get()?.launch(request)
} catch (e: IntentSender.SendIntentException) {
logger.e(e) { "Couldn't Start Intent" }
}
}
.addOnFailureListener(activity) { e ->
logger.e(e) { "Failed to Sign in" }
}
return true
}
return false
}

override fun performLogout(): Boolean {
Firebase.auth.signOut()
return super.performLogout()
}
}
69 changes: 63 additions & 6 deletions android/src/main/java/co/touchlab/droidcon/android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand All @@ -24,6 +27,7 @@ import co.touchlab.droidcon.R
import co.touchlab.droidcon.application.service.NotificationSchedulingService
import co.touchlab.droidcon.application.service.NotificationService
import co.touchlab.droidcon.domain.service.AnalyticsService
import co.touchlab.droidcon.domain.service.AuthenticationService
import co.touchlab.droidcon.domain.service.SyncService
import co.touchlab.droidcon.service.AndroidNotificationService
import co.touchlab.droidcon.ui.theme.Colors
Expand All @@ -32,6 +36,12 @@ import co.touchlab.droidcon.util.AppChecker
import co.touchlab.droidcon.util.NavigationController
import co.touchlab.droidcon.viewmodel.ApplicationViewModel
import co.touchlab.kermit.Logger
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.common.api.ApiException
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import org.brightify.hyperdrive.multiplatformx.LifecycleGraph
Expand All @@ -45,16 +55,59 @@ class MainActivity : ComponentActivity(), KoinComponent {
private val analyticsService: AnalyticsService by inject()

private val applicationViewModel: ApplicationViewModel by inject()

private val root = LifecycleGraph.Root(this)
private val firebaseService: AuthenticationService by inject()

private lateinit var auth: FirebaseAuth

private val firebaseAuthListener = FirebaseAuth.AuthStateListener { firebaseAuth ->
firebaseAuth.currentUser?.let { user ->
firebaseService.setCredentials(
id = user.uid,
name = user.displayName,
email = user.email,
pictureUrl = user.photoUrl?.toString(),
)
} ?: run { firebaseService.clearCredentials() }
}

private val firebaseIntentResultLauncher: ActivityResultLauncher<IntentSenderRequest> =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
val logger = Logger.withTag("Authentication")
try {
val oneTapClient = Identity.getSignInClient(baseContext)
val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
val firebaseCredential =
GoogleAuthProvider.getCredential(credential.googleIdToken, null)
auth.signInWithCredential(firebaseCredential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
logger.d { "signInWithCredential:success" }
auth.currentUser?.let { user ->
firebaseService.setCredentials(
id = user.uid,
name = user.displayName,
email = user.email,
pictureUrl = user.photoUrl?.toString(),
)
}
} else {
logger.e(task.exception) { "signInWithCredential:failure" }
}
}
} catch (e: ApiException) {
logger.e(e) { "NO ID Token" }
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

auth = Firebase.auth
auth.addAuthStateListener(firebaseAuthListener)
installSplashScreen()

AppChecker.checkTimeZoneHash()

(firebaseService as FirebaseService).setActivity(this, firebaseIntentResultLauncher)
analyticsService.logEvent(AnalyticsService.EVENT_STARTED)

applicationViewModel.lifecycle.removeFromParent()
Expand All @@ -70,7 +123,6 @@ class MainActivity : ComponentActivity(), KoinComponent {

setContent {
MainView(viewModel = applicationViewModel)

val showSplashScreen by applicationViewModel.showSplashScreen.collectAsState()
Crossfade(targetState = showSplashScreen) { shouldShowSplashScreen ->
if (shouldShowSplashScreen) {
Expand Down Expand Up @@ -111,8 +163,12 @@ class MainActivity : ComponentActivity(), KoinComponent {
}

private fun handleNotificationDeeplink(intent: Intent) {
val type = intent.getStringExtra(AndroidNotificationService.NOTIFICATION_TYPE_EXTRA_KEY) ?: return
val sessionId = intent.getStringExtra(AndroidNotificationService.NOTIFICATION_SESSION_ID_EXTRA_KEY) ?: return
val type =
intent.getStringExtra(AndroidNotificationService.NOTIFICATION_TYPE_EXTRA_KEY)
?: return
val sessionId =
intent.getStringExtra(AndroidNotificationService.NOTIFICATION_SESSION_ID_EXTRA_KEY)
?: return
applicationViewModel.notificationReceived(
sessionId,
when (type) {
Expand All @@ -133,6 +189,7 @@ class MainActivity : ComponentActivity(), KoinComponent {

override fun onDestroy() {
super.onDestroy()
auth.removeAuthStateListener(firebaseAuthListener)
// Workaround for a crash we could not reproduce: https://console.firebase.google.com/project/droidcon-148cc/crashlytics/app/android:co.touchlab.droidcon.london/issues/8c559569e69164d7109bd6b1be99ade5
if (root.hasChild(applicationViewModel.lifecycle)) {
root.removeChild(applicationViewModel.lifecycle)
Expand Down
10 changes: 9 additions & 1 deletion android/src/main/java/co/touchlab/droidcon/android/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import co.touchlab.droidcon.android.service.impl.DefaultParseUrlViewService
import co.touchlab.droidcon.android.util.NotificationLocalizedStringFactory
import co.touchlab.droidcon.application.service.NotificationSchedulingService
import co.touchlab.droidcon.domain.service.AnalyticsService
import co.touchlab.droidcon.domain.service.AuthenticationService
import co.touchlab.droidcon.domain.service.impl.ResourceReader
import co.touchlab.droidcon.initKoin
import co.touchlab.droidcon.service.ParseUrlViewService
Expand All @@ -31,7 +32,10 @@ class MainApp : Application() {
single<Context> { this@MainApp }
single<Class<out Activity>> { MainActivity::class.java }
single<SharedPreferences> {
get<Context>().getSharedPreferences("DROIDCON_SETTINGS_2023", Context.MODE_PRIVATE)
get<Context>().getSharedPreferences(
"DROIDCON_SETTINGS_2023",
Context.MODE_PRIVATE
)
}
single<ObservableSettings> { SharedPreferencesSettings(delegate = get()) }

Expand All @@ -50,6 +54,10 @@ class MainApp : Application() {
single<AnalyticsService> {
AndroidAnalyticsService(firebaseAnalytics = Firebase.analytics)
}

single<AuthenticationService> {
FirebaseService()
}
} + uiModule
)
}
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ accompanist-navigationAnimation = { module = "com.google.accompanist:accompanist
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx", version = "_" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx", version = "_" }
firebase-auth = { module = "com.google.firebase:firebase-auth", version = "_" }
playservices-auth = { module = "com.google.android.gms:play-services-auth", version = "21.0.0" }

hyperdrive-multiplatformx-api = { module = "org.brightify.hyperdrive:multiplatformx-api", version.ref = "hyperdrive" }
android-desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugaring" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
Expand Down
10 changes: 7 additions & 3 deletions ios/Droidcon/Droidcon.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -49,6 +49,7 @@
A35DC2E328AB6C6F00C7B298 /* ComposeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35DC2E228AB6C6F00C7B298 /* ComposeController.swift */; };
A35DEF2228AA265C0072605A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A35DEF2128AA265C0072605A /* Settings.bundle */; };
A35DEF2428AA26C80072605A /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35DEF2328AA26C80072605A /* SettingsBundleHelper.swift */; };
F127D3592BB46A0A00E08281 /* FirebaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F127D3582BB46A0A00E08281 /* FirebaseService.swift */; };
F1465F0123AA94BF0055F7C3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1465F0023AA94BF0055F7C3 /* AppDelegate.swift */; };
F1465F0A23AA94BF0055F7C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F1465F0923AA94BF0055F7C3 /* Assets.xcassets */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -97,6 +98,7 @@
A35DEF2328AA26C80072605A /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = "<group>"; };
DD90C0C0A4D331CEBDBE69E8 /* Pods-Droidcon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Droidcon.release.xcconfig"; path = "Target Support Files/Pods-Droidcon/Pods-Droidcon.release.xcconfig"; sourceTree = "<group>"; };
EBA278C64D82609BA00FE2A5 /* Pods_Droidcon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Droidcon.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F127D3582BB46A0A00E08281 /* FirebaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseService.swift; sourceTree = "<group>"; };
F1465EFD23AA94BF0055F7C3 /* Droidcon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Droidcon.app; sourceTree = BUILT_PRODUCTS_DIR; };
F1465F0023AA94BF0055F7C3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
F1465F0923AA94BF0055F7C3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -254,6 +256,7 @@
F1465F0023AA94BF0055F7C3 /* AppDelegate.swift */,
46B5284C249C5CF400A7725D /* Koin.swift */,
8404D80D26C64B9E00AE200F /* IOSAnalyticsService.swift */,
F127D3582BB46A0A00E08281 /* FirebaseService.swift */,
F1465F0923AA94BF0055F7C3 /* Assets.xcassets */,
F1465F0B23AA94BF0055F7C3 /* LaunchScreen.storyboard */,
F1465F0E23AA94BF0055F7C3 /* Info.plist */,
Expand Down Expand Up @@ -447,6 +450,7 @@
684FAA7726B2A4EA00673AFF /* SettingsView.swift in Sources */,
689DD2FB26B40F1800A9B009 /* LazyView.swift in Sources */,
689DD30526B438CA00A9B009 /* Avatar.swift in Sources */,
F127D3592BB46A0A00E08281 /* FirebaseService.swift in Sources */,
681C95A126C555D90011330B /* VisualEffectView.swift in Sources */,
1833221026B0CF5600D79482 /* DroidconApp.swift in Sources */,
684FAA7426B2A4D400673AFF /* ScheduleView.swift in Sources */,
Expand Down Expand Up @@ -645,7 +649,7 @@
"\"DroidconKit\"",
"-lsqlite3",
);
PRODUCT_BUNDLE_IDENTIFIER = co.touchlab.droidcon.ios.london;
PRODUCT_BUNDLE_IDENTIFIER = co.touchlab.droidconauthtest;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -682,7 +686,7 @@
"\"DroidconKit\"",
"-lsqlite3",
);
PRODUCT_BUNDLE_IDENTIFIER = co.touchlab.droidcon.ios.london;
PRODUCT_BUNDLE_IDENTIFIER = co.touchlab.droidconauthtest;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
Expand Down
32 changes: 32 additions & 0 deletions ios/Droidcon/Droidcon/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import UIKit
import FirebaseAuth
import GoogleSignIn
import Firebase
import DroidconKit

Expand All @@ -7,7 +9,11 @@ class AppDelegate: NSObject, UIApplicationDelegate {
lazy var log = koin.get(objCClass: Logger.self, parameter: "AppDelegate") as! Logger
lazy var analytics = koin.get(objCProtocol: AnalyticsService.self, qualifier: nil) as! AnalyticsService
lazy var appChecker = koin.get(objCClass: AppChecker.self) as! AppChecker
lazy var firebaseService = koin.get(objCClass: AuthenticationService.self, qualifier: nil) as! AuthenticationService

var firebaseAuthListener:AuthStateDidChangeListenerHandle?


func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
Expand All @@ -21,7 +27,33 @@ class AppDelegate: NSObject, UIApplicationDelegate {

analytics.logEvent(name: AnalyticsServiceCompanion().EVENT_STARTED, params: [:])

firebaseAuthListener = Auth.auth().addStateDidChangeListener() { auth, user in
if let user {
self.firebaseService.setCredentials(
id: user.uid,
name: user.displayName,
email: user.email,
pictureUrl: user.photoURL?.absoluteString
)
} else {
self.firebaseService.clearCredentials()
}
}

log.v(message: { "App Started" })
return true
}

func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance.handle(url)
}

func applicationWillTerminate(_ application: UIApplication) {
if let firebaseAuthListener {
Auth.auth().removeStateDidChangeListener(firebaseAuthListener)
}
}

}
Loading
Loading