From 46824f44987cae16113ce6c7a63f7bfd7653fbc7 Mon Sep 17 00:00:00 2001 From: reyniersbram <55666730+reyniersbram@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:46:51 +0200 Subject: [PATCH] #44 first try at firebase use-cases --- app/build.gradle | 17 ++-- app/google-services.json | 39 +++++++++ .../sel/studeez/data/local/models/User.kt | 3 + .../java/be/ugent/sel/studeez/domain/.keep | 0 .../ugent/sel/studeez/domain/Performance.kt | 27 ++++++ .../studeez/domain/account/AccountService.kt | 34 ++++++++ .../domain/account/AccountServiceImpl.kt | 84 +++++++++++++++++++ .../domain/account/AuthenticateUseCase.kt | 13 +++ .../account/CreateAnonymousAccountUseCase.kt | 13 +++ .../domain/account/DeleteAccountUseCase.kt | 13 +++ .../domain/account/LinkAccountUseCase.kt | 22 +++++ .../account/SendRecoveryEmailUseCase.kt | 13 +++ .../studeez/domain/account/SignOutUseCase.kt | 18 ++++ .../java/be/ugent/sel/studeez/features/.keep | 0 .../{data/local/models => screens}/.keep | 0 build.gradle | 5 ++ 16 files changed, 294 insertions(+), 7 deletions(-) create mode 100644 app/google-services.json create mode 100644 app/src/main/java/be/ugent/sel/studeez/data/local/models/User.kt delete mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/.keep create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/Performance.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/AccountService.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/AccountServiceImpl.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/AuthenticateUseCase.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/CreateAnonymousAccountUseCase.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/DeleteAccountUseCase.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/LinkAccountUseCase.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/SendRecoveryEmailUseCase.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/domain/account/SignOutUseCase.kt delete mode 100644 app/src/main/java/be/ugent/sel/studeez/features/.keep rename app/src/main/java/be/ugent/sel/studeez/{data/local/models => screens}/.keep (100%) diff --git a/app/build.gradle b/app/build.gradle index 52f7cb8..e972d23 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,6 +8,9 @@ plugins { // Protobuf id 'com.google.protobuf' version '0.8.17' + + // Firebase + id 'com.google.gms.google-services' } android { @@ -108,13 +111,13 @@ dependencies { debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" //Firebase -// implementation platform('com.google.firebase:firebase-bom:30.4.1') -// implementation 'com.google.firebase:firebase-crashlytics-ktx' -// implementation 'com.google.firebase:firebase-analytics-ktx' -// implementation 'com.google.firebase:firebase-auth-ktx' -// implementation 'com.google.firebase:firebase-firestore-ktx' -// implementation 'com.google.firebase:firebase-perf-ktx' -// implementation 'com.google.firebase:firebase-config-ktx' + implementation platform('com.google.firebase:firebase-bom:31.3.0') + implementation 'com.google.firebase:firebase-crashlytics-ktx' + implementation 'com.google.firebase:firebase-analytics-ktx' + implementation 'com.google.firebase:firebase-auth-ktx' + implementation 'com.google.firebase:firebase-firestore-ktx' + implementation 'com.google.firebase:firebase-perf-ktx' + implementation 'com.google.firebase:firebase-config-ktx' } diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..843ac17 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "692936961909", + "project_id": "studeez-1cff2", + "storage_bucket": "studeez-1cff2.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:692936961909:android:28a567b1634fc3bdf3ed3c", + "android_client_info": { + "package_name": "be.ugent.sel.studeez" + } + }, + "oauth_client": [ + { + "client_id": "692936961909-vdbv9g03mt55uqes4prtbipr6vg8b48j.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCbbg5F_rTrnwUHLDgCWSx4KCECozkvgVE" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "692936961909-vdbv9g03mt55uqes4prtbipr6vg8b48j.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/User.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/User.kt new file mode 100644 index 0000000..412100a --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/User.kt @@ -0,0 +1,3 @@ +package be.ugent.sel.studeez.data.local.models + +data class User(val id: String = "", val isAnonymous: Boolean = true) diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/.keep b/app/src/main/java/be/ugent/sel/studeez/domain/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/Performance.kt b/app/src/main/java/be/ugent/sel/studeez/domain/Performance.kt new file mode 100644 index 0000000..e987b37 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/Performance.kt @@ -0,0 +1,27 @@ +/* +Copyright 2022 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +package be.ugent.sel.studeez.domain + +import com.google.firebase.perf.ktx.trace +import com.google.firebase.perf.metrics.Trace + +/** + * Trace a block with Firebase performance. + * + * Supports both suspend and regular methods. + */ +inline fun trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block) diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountService.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountService.kt new file mode 100644 index 0000000..dc068eb --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountService.kt @@ -0,0 +1,34 @@ +/* +Copyright 2022 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +package be.ugent.sel.studeez.domain.account + +import be.ugent.sel.studeez.data.local.models.User +import kotlinx.coroutines.flow.Flow + +interface AccountService { + val currentUserId: String + val hasUser: Boolean + + val currentUser: Flow + + suspend fun authenticate(email: String, password: String) + suspend fun sendRecoveryEmail(email: String) + suspend fun createAnonymousAccount() + suspend fun linkAccount(email: String, password: String) + suspend fun deleteAccount() + suspend fun signOut() +} diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountServiceImpl.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountServiceImpl.kt new file mode 100644 index 0000000..ad01080 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/AccountServiceImpl.kt @@ -0,0 +1,84 @@ +/* +Copyright 2022 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +package be.ugent.sel.studeez.domain.account + +import be.ugent.sel.studeez.data.local.models.User +import be.ugent.sel.studeez.domain.trace +import com.google.firebase.auth.EmailAuthProvider +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class AccountServiceImpl @Inject constructor( + private val auth: FirebaseAuth +) : AccountService { + + override val currentUserId: String + get() = auth.currentUser?.uid.orEmpty() + + override val hasUser: Boolean + get() = auth.currentUser != null + + override val currentUser: Flow + get() = callbackFlow { + val listener = + FirebaseAuth.AuthStateListener { auth -> + this.trySend(auth.currentUser?.let { User(it.uid, it.isAnonymous) } ?: User()) + } + auth.addAuthStateListener(listener) + awaitClose { auth.removeAuthStateListener(listener) } + } + + override suspend fun authenticate(email: String, password: String) { + auth.signInWithEmailAndPassword(email, password).await() + } + + override suspend fun sendRecoveryEmail(email: String) { + auth.sendPasswordResetEmail(email).await() + } + + override suspend fun createAnonymousAccount() { + auth.signInAnonymously().await() + } + + override suspend fun linkAccount(email: String, password: String): Unit = + trace(LINK_ACCOUNT_TRACE) { + val credential = EmailAuthProvider.getCredential(email, password) + auth.currentUser!!.linkWithCredential(credential).await() + } + + override suspend fun deleteAccount() { + auth.currentUser!!.delete().await() + } + + override suspend fun signOut() { + if (auth.currentUser!!.isAnonymous) { + auth.currentUser!!.delete() + } + auth.signOut() + + // Sign the user back in anonymously. + createAnonymousAccount() + } + + companion object { + private const val LINK_ACCOUNT_TRACE = "linkAccount" + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/AuthenticateUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/AuthenticateUseCase.kt new file mode 100644 index 0000000..02947f9 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/AuthenticateUseCase.kt @@ -0,0 +1,13 @@ +package be.ugent.sel.studeez.domain.account + +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class AuthenticateUseCase @Inject constructor( + private val auth: FirebaseAuth, +) { + suspend operator fun invoke(email: String, password: String) { + auth.signInWithEmailAndPassword(email, password).await() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/CreateAnonymousAccountUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/CreateAnonymousAccountUseCase.kt new file mode 100644 index 0000000..c5048ba --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/CreateAnonymousAccountUseCase.kt @@ -0,0 +1,13 @@ +package be.ugent.sel.studeez.domain.account + +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class CreateAnonymousAccountUseCase @Inject constructor( + private val auth: FirebaseAuth, +) { + suspend operator fun invoke() { + auth.signInAnonymously().await() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/DeleteAccountUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/DeleteAccountUseCase.kt new file mode 100644 index 0000000..68bba2d --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/DeleteAccountUseCase.kt @@ -0,0 +1,13 @@ +package be.ugent.sel.studeez.domain.account + +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class DeleteAccountUseCase @Inject constructor( + private val auth: FirebaseAuth, +) { + suspend operator fun invoke() { + auth.currentUser!!.delete().await() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/LinkAccountUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/LinkAccountUseCase.kt new file mode 100644 index 0000000..6964343 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/LinkAccountUseCase.kt @@ -0,0 +1,22 @@ +package be.ugent.sel.studeez.domain.account + +import be.ugent.sel.studeez.domain.trace +import com.google.firebase.auth.EmailAuthProvider +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class LinkAccountUseCase @Inject constructor( + private val auth: FirebaseAuth, +) { + + suspend operator fun invoke(email: String, password: String): Unit = + trace(LINK_ACCOUNT_TRACE) { + val credential = EmailAuthProvider.getCredential(email, password) + auth.currentUser!!.linkWithCredential(credential).await() + } + + companion object { + private const val LINK_ACCOUNT_TRACE = "linkAccount" + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/SendRecoveryEmailUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/SendRecoveryEmailUseCase.kt new file mode 100644 index 0000000..7e2e8d6 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/SendRecoveryEmailUseCase.kt @@ -0,0 +1,13 @@ +package be.ugent.sel.studeez.domain.account + +import com.google.firebase.auth.FirebaseAuth +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class SendRecoveryEmailUseCase @Inject constructor( + private val auth: FirebaseAuth, +) { + suspend operator fun invoke(email: String) { + auth.sendPasswordResetEmail(email).await() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/account/SignOutUseCase.kt b/app/src/main/java/be/ugent/sel/studeez/domain/account/SignOutUseCase.kt new file mode 100644 index 0000000..2d83cfd --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/account/SignOutUseCase.kt @@ -0,0 +1,18 @@ +package be.ugent.sel.studeez.domain.account + +import com.google.firebase.auth.FirebaseAuth +import javax.inject.Inject + +class SignOutUseCase @Inject constructor( + private val auth: FirebaseAuth, + private val createAnonymousAccountUseCase: CreateAnonymousAccountUseCase, +) { + suspend operator fun invoke() { + if (auth.currentUser!!.isAnonymous) { + auth.currentUser!!.delete() + } + auth.signOut() + + createAnonymousAccountUseCase() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/features/.keep b/app/src/main/java/be/ugent/sel/studeez/features/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/.keep b/app/src/main/java/be/ugent/sel/studeez/screens/.keep similarity index 100% rename from app/src/main/java/be/ugent/sel/studeez/data/local/models/.keep rename to app/src/main/java/be/ugent/sel/studeez/screens/.keep diff --git a/build.gradle b/build.gradle index e0083ef..f0452db 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,10 @@ buildscript { espressoVersion = '3.4.0' kotlinVersion = '1.6.10' } + + dependencies { + classpath 'com.google.gms:google-services:4.3.15' + } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '7.4.2' apply false @@ -15,3 +19,4 @@ plugins { // Hilt id 'com.google.dagger.hilt.android' version '2.44' apply false } +