Skip to content
This repository has been archived by the owner on Nov 29, 2020. It is now read-only.

[Research] Authenticate users using Firebase authentication #133

Open
wants to merge 4 commits into
base: feature/firebase-auth
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 14 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'


android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
Expand Down Expand Up @@ -65,7 +69,7 @@ dependencies {
implementation 'android.arch.lifecycle:extensions:1.1.1'

implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
kapt 'com.jakewharton:butterknife-compiler:8.8.1'

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
Expand All @@ -76,8 +80,8 @@ dependencies {

implementation 'com.google.dagger:dagger:2.17'
implementation 'com.google.dagger:dagger-android-support:2.17'
annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'
kapt 'com.google.dagger:dagger-compiler:2.17'
kapt 'com.google.dagger:dagger-android-processor:2.17'

implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.2.4'
Expand All @@ -87,6 +91,11 @@ dependencies {

testImplementation 'junit:junit:' + rootProject.junitVersion
testImplementation 'org.mockito:mockito-core:' + rootProject.mockitoVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

implementation 'com.google.firebase:firebase-core:16.0.8'
implementation 'com.google.firebase:firebase-auth:16.2.1'

}

android.variantFilter { variant ->
Expand All @@ -111,3 +120,5 @@ tasks.withType(Test) {
events "started", "passed", "skipped", "failed"
}
}

apply plugin: 'com.google.gms.google-services'
6 changes: 3 additions & 3 deletions app/src/dev/res/values/authorities.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="provider_authority">ro.code4.monitorizarevot.dev</string>
<string name="sync_adapter_authority">ro.code4.monitorizarevot.dev</string>
<string name="account_type">dev.code4.ro</string>
<string name="provider_authority" translatable="false">ro.code4.monitorizarevot.dev</string>
<string name="sync_adapter_authority" translatable="false">ro.code4.monitorizarevot.dev</string>
<string name="account_type" translatable="false">dev.code4.ro</string>
</resources>
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
android:screenOrientation="portrait"
android:theme="@style/AppTheme"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity
android:name=".PhoneAuthActivity"
android:noHistory="true"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Login"
android:windowSoftInputMode="adjustResize" />

<receiver android:name=".net.NetworkChangeReceiver">
<intent-filter>
Expand Down
183 changes: 183 additions & 0 deletions app/src/main/java/ro/code4/monitorizarevot/PhoneAuthActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package ro.code4.monitorizarevot

import android.annotation.SuppressLint
import android.app.ActivityOptions
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.annotation.StringRes
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.util.Pair
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import butterknife.ButterKnife
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
import com.google.firebase.auth.PhoneAuthCredential
import com.google.firebase.auth.PhoneAuthProvider
import kotlinx.android.synthetic.main.activity_phone_auth.*
import ro.code4.monitorizarevot.constants.Constants.ORGANISATION_WEB_URL
import ro.code4.monitorizarevot.presentation.LoadingMessage
import ro.code4.monitorizarevot.viewmodel.PhoneAuthViewModel
import ro.code4.monitorizarevot.R
import java.util.concurrent.TimeUnit


class PhoneAuthActivity : BaseActivity<PhoneAuthViewModel>() {


companion object {
@JvmStatic
val TAG: String = PhoneAuthActivity::class.java.simpleName
}

private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success")

val user = task.result?.user
showErrorDialog("Firebase: Successfully signed in!")
viewModel.notifyUserSignedIn(user)
// ...
} else {
// Sign in failed, display a message and update the UI
Log.w(TAG, "signInWithCredential:failure", task.exception)
if (task.exception is FirebaseAuthInvalidCredentialsException) {
// The verification code entered was invalid
showErrorDialog(task.exception?.localizedMessage)
}
}
showHideLoading(LoadingMessage(false))
}
}

lateinit var auth: FirebaseAuth
override fun setupViewModel() {
viewModel = ViewModelProviders.of(this, factory).get(PhoneAuthViewModel::class.java)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_phone_auth)
ButterKnife.bind(this)

auth = FirebaseAuth.getInstance()
auth.useAppLanguage()

app_version.text = getString(R.string.app_version, BuildConfig.VERSION_NAME)

viewModel.message().observe(this, Observer { message -> showErrorDialog(message) })
viewModel.messageId().observe(this, Observer { showErrorDialog(it) })

viewModel.loginStatus().observe(this, Observer { status ->
if (status!!) {
performNavigation()
}
})

viewModel.codeSent().observe(this, Observer {
loginBtn.text = getString(R.string.login)
codeLayout.visibility = View.VISIBLE
})

viewModel.credential().observe(this, Observer { credential ->
credential?.let {
signInWithPhoneAuthCredential(it)
}
})

viewModel.validatePhone().observe(this, Observer { phone ->
phone?.let {
validatePhoneNumber(it)
}
})

phone.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
loginBtn.isEnabled = s.isNotEmpty()
if (s.isEmpty()) {
loginBtn.text = getString(R.string.login_send_code)
codeLayout.visibility = View.INVISIBLE
viewModel.resetCode()
}
}

})

loginBtn.setOnClickListener {
handleLoginClick()
}

loginOrganisationLink.setOnClickListener {
openOrganisationWebpage()
}

code.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
handleLoginClick()
}
return@setOnEditorActionListener false
}

}

private fun handleLoginClick() {
viewModel.handleLoginClick(phone.text.toString(), code.text.toString())

}

private fun showErrorDialog(@StringRes resId: Int?) {
showErrorDialog(getString(resId ?: -1))
}

private fun showErrorDialog(message: String?) {
if (!TextUtils.isEmpty(message)) {
Toast.makeText(App.getContext(), message, Toast.LENGTH_LONG).show()
}
}


private fun validatePhoneNumber(phoneText: String) {
PhoneAuthProvider.getInstance()
.verifyPhoneNumber(phoneText, 60, TimeUnit.SECONDS, this, viewModel.callbacks)
}

@SuppressLint("HardwareIds")
private fun login(code: String) {
viewModel.login(code)
}


private fun performNavigation() {
val intent = Intent(this, ToolbarActivity::class.java)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
val options: ActivityOptions
val sharedBackground = Pair<View, String>(findViewById(R.id.purple_background),
getString(R.string.shared_element_login_background))
val sharedLogo = Pair<View, String>(findViewById(R.id.logo), getString(R.string.shared_element_logo))
options = ActivityOptions
.makeSceneTransitionAnimation(this, sharedBackground, sharedLogo)
startActivity(intent, options.toBundle())
} else {
startActivity(intent)
}
finish() //TODO finish after transition is complete
}

private fun openOrganisationWebpage() {
val openBrowser = Intent(Intent.ACTION_VIEW, Uri.parse(ORGANISATION_WEB_URL))
startActivity(openBrowser)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private class PreferencesSubscriber extends ObservableListener<Boolean> {
public void onNext(Boolean hasCredentials) {
super.onNext(hasCredentials);
startActivity(new Intent(StartActivity.this,
hasCredentials ? ToolbarActivity.class : LoginActivity.class));
hasCredentials ? ToolbarActivity.class : PhoneAuthActivity.class));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dagger.Module;
import dagger.android.ContributesAndroidInjector;
import ro.code4.monitorizarevot.LoginActivity;
import ro.code4.monitorizarevot.PhoneAuthActivity;
import ro.code4.monitorizarevot.StartActivity;
import ro.code4.monitorizarevot.ToolbarActivity;

Expand All @@ -15,6 +16,9 @@ public abstract class ActivityBindingModule {
@ContributesAndroidInjector
abstract LoginActivity loginActivity();

@ContributesAndroidInjector
abstract PhoneAuthActivity phoneAuthActivity();

@ContributesAndroidInjector(modules = {FragmentBindingModule.class})
abstract ToolbarActivity toolbarActivity();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import ro.code4.monitorizarevot.viewmodel.*;
import ro.code4.monitorizarevot.viewmodel.AddNoteViewModel;
import ro.code4.monitorizarevot.viewmodel.BranchDetailsViewModel;
import ro.code4.monitorizarevot.viewmodel.BranchSelectionViewModel;
import ro.code4.monitorizarevot.viewmodel.FormsListViewModel;
import ro.code4.monitorizarevot.viewmodel.GuideViewModel;
import ro.code4.monitorizarevot.viewmodel.LoginViewModel;
import ro.code4.monitorizarevot.viewmodel.PhoneAuthViewModel;
import ro.code4.monitorizarevot.viewmodel.QuestionDetailsViewModel;
import ro.code4.monitorizarevot.viewmodel.QuestionOverviewViewModel;
import ro.code4.monitorizarevot.viewmodel.QuestionViewModel;
import ro.code4.monitorizarevot.viewmodel.StartViewModel;
import ro.code4.monitorizarevot.viewmodel.ToolbarViewModel;
import ro.code4.monitorizarevot.viewmodel.ViewModelFactory;

@Module
public abstract class ViewModelModule {
Expand All @@ -24,6 +36,11 @@ public abstract class ViewModelModule {
@ViewModelKey(LoginViewModel.class)
abstract ViewModel bindLoginViewModel(LoginViewModel viewModel);

@Binds
@IntoMap
@ViewModelKey(PhoneAuthViewModel.class)
abstract ViewModel bindPhoneAuthViewModel(PhoneAuthViewModel viewModel);

@Binds
@IntoMap
@ViewModelKey(ToolbarViewModel.class)
Expand Down
Loading