Skip to content

Commit

Permalink
Facebook auth
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaeltoledo committed Aug 28, 2019
1 parent 0fa6bd8 commit 92aac34
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 35 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ references:
- image: circleci/android:api-28
working_directory: *workspace
enviroment:
JAVA_TOOL_OPTIONS: "-Xmx1536m -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2 -Dkotlin.incremental=false"
JAVA_TOOL_OPTIONS: "-Xmx2048m -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
GRADLE_OPTS: "-Xms512m -Xmx2048m -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2 -Djava.util.concurrent.ForkJoinPool.common.parallelism=2 -Dkotlin.incremental=false"
CIRCLE_JDK_VERSION: oraclejdk8
TERM: dumb

Expand Down
13 changes: 10 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ android {
debug {
buildConfigField 'String', 'GOOGLE_REQUEST_ID_TOKEN', "\"$System.env.GOOGLE_REQUEST_ID_TOKEN_DEVELOPMENT\""

resValue 'string', 'fb_app_id', "\"$System.env.FB_APP_ID_DEVELOPMENT\""
resValue 'string', 'fb_login_protocol_scheme', "\"$System.env.FB_LOGIN_PROTOCOL_SCHEME_DEVELOPMENT\""

multiDexEnabled true

applicationIdSuffix '.dev'
Expand All @@ -64,6 +67,9 @@ android {
release {
buildConfigField 'String', 'GOOGLE_REQUEST_ID_TOKEN', "\"$System.env.GOOGLE_REQUEST_ID_TOKEN_RELEASE\""

resValue 'string', 'fb_app_id', "\"$System.env.FB_APP_ID_RELEASE\""
resValue 'string', 'fb_login_protocol_scheme', "\"$System.env.FB_LOGIN_PROTOCOL_SCHEME_RELEASE\""

signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
Expand Down Expand Up @@ -93,12 +99,13 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$versions.androidx.lifecycle"

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

implementation 'com.google.firebase:firebase-core:17.1.0'
implementation 'com.google.firebase:firebase-core:17.2.0'
implementation 'com.google.firebase:firebase-auth:19.0.0'

implementation 'com.google.android.gms:play-services-auth:17.0.0'
implementation 'com.facebook.android:facebook-login:5.4.0'

implementation "org.koin:koin-android:$versions.koin"
implementation "org.koin:koin-androidx-viewmodel:$versions.koin"
Expand All @@ -117,7 +124,7 @@ dependencies {
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation "io.mockk:mockk:$versions.mockk"

androidTestImplementation 'com.github.tmurakami:dexopener:2.0.2'
androidTestImplementation 'com.github.tmurakami:dexopener:2.0.3'
androidTestImplementation "io.mockk:mockk-android:$versions.mockk"

androidTestUtil "androidx.test:orchestrator:$versions.androidx.testcore"
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,27 @@
</intent-filter>
</activity>
<activity android:name=".ui.feature.signin.SignInActivity" />

<activity
android:name="com.facebook.FacebookActivity"
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />

<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter><action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>

<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/fb_app_id" />

</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface AuthManager {
}

enum class SocialProvider {
GOOGLE
GOOGLE, FACEBOOK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.rafaeltoledo.social.data.auth

import android.app.Activity
import android.content.Intent
import android.util.Log
import com.facebook.CallbackManager
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import java.util.concurrent.CountDownLatch

/**
* Authenticates a user using the Facebook Login SDK.
*/
class FacebookAuth : DelegatedAuth {

private lateinit var callbackManager: CallbackManager
private lateinit var status: AuthResult
private lateinit var countDownLatch: CountDownLatch

private val callback = object : FacebookCallback<LoginResult> {
override fun onSuccess(result: LoginResult?) {
status = AuthResult(Status.SUCCESS, result?.accessToken?.token)
countDownLatch.countDown()
}

override fun onCancel() {
status = AuthResult(Status.CANCELED)
countDownLatch.countDown()
}

override fun onError(error: FacebookException?) {
Log.e("FacebookAuth", "Could not complete Facebook signin", error)
status = AuthResult(Status.FAILURE)
countDownLatch.countDown()
}
}

override fun <T : DelegatedAuth> build(activity: Activity): T {
callbackManager = CallbackManager.Factory.create()
LoginManager.getInstance().registerCallback(callbackManager, callback)
@Suppress("UNCHECKED_CAST") return this as T
}

override fun signIn(activity: Activity) {
countDownLatch = CountDownLatch(1)
LoginManager.getInstance().logInWithReadPermissions(activity, listOf("email", "public_profile"))
}

override fun onResult(requestCode: Int, resultCode: Int, data: Intent?): AuthResult {
callbackManager.onActivityResult(requestCode, resultCode, data)
countDownLatch.await()
return status
}

override fun signOut(callback: (Status) -> Unit) {
LoginManager.getInstance().logOut()
callback(Status.SUCCESS)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import net.rafaeltoledo.social.BuildConfig

/**
* Authenticates a user using Google Auth.
*/
class GoogleAuth : DelegatedAuth {

private var client: GoogleSignInClient? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package net.rafaeltoledo.social.data.firebase

import com.google.android.gms.tasks.Task
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FacebookAuthProvider
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import java.lang.Exception
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
Expand All @@ -16,23 +20,26 @@ class FirebaseAuthManager : AuthManager {

override suspend fun socialSignIn(token: String, provider: SocialProvider): User =
suspendCoroutine {
when (provider) {
SocialProvider.GOOGLE -> googleSignIn(token, it)
auth.signInWithCredential(
when (provider) {
SocialProvider.GOOGLE -> GoogleAuthProvider.getCredential(token, null)
SocialProvider.FACEBOOK -> FacebookAuthProvider.getCredential(token)
}
).addOnCompleteListener { task ->
handleResult(task, it)
}
}

private fun googleSignIn(
token: String,
continuation: Continuation<User>
) {
auth.signInWithCredential(GoogleAuthProvider.getCredential(token, null))
.addOnCompleteListener {
if (it.isSuccessful.not()) {
continuation.resumeWithException(it.exception!!)
} else {
continuation.resume(User(it.result!!.user!!.uid))
}
}
private fun handleResult(task: Task<AuthResult>, continuation: Continuation<User>) {
if (task.isSuccessful.not()) {
continuation.resumeWithException(
task.exception ?: Exception("No exception was thrown by Firebase")
)
} else {
continuation.resume(
User(task.result?.user?.uid ?: throw IllegalStateException("Expected a user ID"))
)
}
}

override fun isUserLoggedIn() = auth.currentUser != null
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/kotlin/net/rafaeltoledo/social/di/AuthModule.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package net.rafaeltoledo.social.di

import net.rafaeltoledo.social.data.auth.DelegatedAuth
import net.rafaeltoledo.social.data.auth.FacebookAuth
import net.rafaeltoledo.social.data.auth.GoogleAuth
import net.rafaeltoledo.social.data.auth.SocialProvider
import org.koin.core.qualifier.named
import org.koin.dsl.module

val authModule = module {
single<DelegatedAuth> { GoogleAuth() }
single<DelegatedAuth>(named(SocialProvider.GOOGLE.name)) { GoogleAuth() }
single<DelegatedAuth>(named(SocialProvider.FACEBOOK.name)) { FacebookAuth() }
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package net.rafaeltoledo.social.di

import net.rafaeltoledo.social.data.auth.SocialProvider
import net.rafaeltoledo.social.ui.feature.main.MainViewModel
import net.rafaeltoledo.social.ui.feature.signin.SignInViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named
import org.koin.dsl.module

val viewModelModule = module {
viewModel { MainViewModel(get()) }
viewModel { SignInViewModel(get(), get()) }
viewModel { SignInViewModel(get(), get(named(SocialProvider.GOOGLE.name)), get(named(
SocialProvider.FACEBOOK.name))) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SignInActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_sign_in)

binding.setLifecycleOwner(this)
binding.lifecycleOwner = this
binding.viewModel = signInViewModel

observeAuthClient()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import androidx.lifecycle.MutableLiveData
import net.rafaeltoledo.social.R
import net.rafaeltoledo.social.data.auth.AuthManager
import net.rafaeltoledo.social.data.auth.DelegatedAuth
import net.rafaeltoledo.social.data.auth.FacebookAuth
import net.rafaeltoledo.social.data.auth.GoogleAuth
import net.rafaeltoledo.social.data.auth.SocialProvider
import net.rafaeltoledo.social.data.auth.Status
import net.rafaeltoledo.social.data.model.User
import net.rafaeltoledo.social.ui.BaseViewModel

class SignInViewModel(private val auth: AuthManager, private val googleAuth: DelegatedAuth) : BaseViewModel() {
/**
* Handles UI state and orchestrates AuthManager implementations
* based on user selection.
*/
class SignInViewModel(
private val auth: AuthManager,
private val googleAuth: DelegatedAuth,
private val facebookAuth: DelegatedAuth
) : BaseViewModel() {

val authClient = MutableLiveData<DelegatedAuth>()
val user = MutableLiveData<User>()
Expand All @@ -22,14 +32,29 @@ class SignInViewModel(private val auth: AuthManager, private val googleAuth: Del
authClient.value = googleAuth.build(activity)
}

fun facebookSignIn(activity: Activity) {
loading.value = true

authClient.value = facebookAuth.build(activity)
}

fun onResult(requestCode: Int, resultCode: Int, data: Intent?) {
launchDataLoad {
val result = authClient.value?.onResult(requestCode, resultCode, data)
if (result?.status == Status.SUCCESS) {
user.postValue(auth.socialSignIn(result.token!!, SocialProvider.GOOGLE))
user.postValue(auth.socialSignIn(
token = result.token ?: throw IllegalStateException("Empty token"),
provider = authClient.value?.provider() ?: throw IllegalStateException("Empty provider"))
)
} else {
error.postValue(R.string.error_sign_in)
}
}
}

private fun DelegatedAuth.provider() = when (this) {
is GoogleAuth -> SocialProvider.GOOGLE
is FacebookAuth -> SocialProvider.FACEBOOK
else -> throw IllegalArgumentException("unknown social provider")
}
}
12 changes: 11 additions & 1 deletion app/src/main/res/layout/activity_sign_in.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/app_logo" />

<Button
android:id="@+id/button_facebook_sign_in"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:enabled="@{!safeUnbox(viewModel.loading)}"
android:onClick="@{v -> viewModel.facebookSignIn((Activity) context)}"
android:text="@string/button_facebook_sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_google_sign_in" />

<ProgressBar
android:id="@+id/progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
Expand All @@ -55,4 +66,3 @@

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="button_google_sign_in">Sign in with Google</string>
<string name="button_facebook_sign_in">Sign in with Facebook</string>

<string name="error_sign_in">Failed to perform sign in</string>
<string name="error_default_message">Oops… something went wrong!</string>
Expand Down
15 changes: 12 additions & 3 deletions app/src/sharedTest/kotlin/net/rafaeltoledo/social/TestSetup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package net.rafaeltoledo.social

import net.rafaeltoledo.social.data.auth.AuthManager
import net.rafaeltoledo.social.data.auth.DelegatedAuth
import net.rafaeltoledo.social.data.auth.FacebookAuth
import net.rafaeltoledo.social.data.auth.GoogleAuth
import net.rafaeltoledo.social.data.auth.SocialProvider
import net.rafaeltoledo.social.data.model.User
import org.koin.core.context.loadKoinModules
import org.koin.core.context.stopKoin
import org.koin.core.qualifier.named
import org.koin.dsl.module

class TestSocialApp : SocialApp() {
Expand All @@ -15,7 +17,8 @@ class TestSocialApp : SocialApp() {
super.onCreate()
loadKoinModules(listOf(
module(override = true) {
single { delegatedAuth }
single(named(SocialProvider.GOOGLE.name)) { googleAuth }
single(named(SocialProvider.FACEBOOK.name)) { fbAuth }
single { authManager }
single { stringValue }
}
Expand All @@ -27,9 +30,15 @@ class TestSocialApp : SocialApp() {
super.onTerminate()
}

var delegatedAuth: DelegatedAuth = GoogleAuth()
var googleAuth: DelegatedAuth = GoogleAuth()
set(value) {
loadKoinModules(module { single(override = true) { value } })
loadKoinModules(module { single(named(SocialProvider.GOOGLE.name), override = true) { value } })
field = value
}

var fbAuth: DelegatedAuth = FacebookAuth()
set(value) {
loadKoinModules(module { single(named(SocialProvider.FACEBOOK.name), override = true) { value } })
field = value
}

Expand Down

0 comments on commit 92aac34

Please sign in to comment.