Skip to content

Commit bb650c8

Browse files
committed
refactor(genericidp): move Firebase calls to ViewModel
1 parent bc9912a commit bb650c8

File tree

2 files changed

+159
-103
lines changed

2 files changed

+159
-103
lines changed

auth/app/src/main/java/com/google/firebase/quickstart/auth/kotlin/GenericIdpFragment.kt

+48-103
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,29 @@
1717
package com.google.firebase.quickstart.auth.kotlin
1818

1919
import android.os.Bundle
20-
import android.util.Log
2120
import android.view.LayoutInflater
2221
import android.view.View
2322
import android.view.ViewGroup
2423
import android.widget.AdapterView
2524
import android.widget.ArrayAdapter
26-
import android.widget.Toast
27-
import com.google.firebase.auth.FirebaseAuth
28-
import com.google.firebase.auth.FirebaseUser
29-
import com.google.firebase.auth.ktx.auth
30-
import com.google.firebase.auth.ktx.oAuthProvider
31-
import com.google.firebase.ktx.Firebase
25+
import androidx.core.view.isGone
26+
import androidx.fragment.app.viewModels
27+
import androidx.lifecycle.DefaultLifecycleObserver
28+
import androidx.lifecycle.Lifecycle
29+
import androidx.lifecycle.LifecycleOwner
30+
import androidx.lifecycle.lifecycleScope
31+
import androidx.lifecycle.repeatOnLifecycle
32+
import com.google.android.material.snackbar.Snackbar
3233
import com.google.firebase.quickstart.auth.R
3334
import com.google.firebase.quickstart.auth.databinding.FragmentGenericIdpBinding
34-
import java.util.ArrayList
35+
import kotlinx.coroutines.launch
3536

3637
/**
3738
* Demonstrate Firebase Authentication using a Generic Identity Provider (IDP).
3839
*/
3940
class GenericIdpFragment : BaseFragment() {
4041

41-
private lateinit var auth: FirebaseAuth
42+
private val viewModel by viewModels<GenericIdpViewModel>()
4243

4344
private var _binding: FragmentGenericIdpBinding? = null
4445
private val binding: FragmentGenericIdpBinding
@@ -53,112 +54,56 @@ class GenericIdpFragment : BaseFragment() {
5354

5455
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
5556
super.onViewCreated(view, savedInstanceState)
56-
// Initialize Firebase Auth
57-
auth = Firebase.auth
5857

5958
// Set up button click listeners
60-
binding.genericSignInButton.setOnClickListener { signIn() }
61-
binding.signOutButton.setOnClickListener {
62-
auth.signOut()
63-
updateUI(null)
64-
}
65-
66-
// Spinner
67-
val providers = ArrayList(PROVIDER_MAP.keys)
68-
spinnerAdapter = ArrayAdapter(requireContext(), R.layout.item_spinner_list, providers)
69-
binding.providerSpinner.adapter = spinnerAdapter
70-
binding.providerSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
71-
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
72-
binding.genericSignInButton.text =
73-
getString(R.string.generic_signin_fmt, spinnerAdapter.getItem(position))
59+
binding.genericSignInButton.setOnClickListener {
60+
val providerName = spinnerAdapter.getItem(binding.providerSpinner.selectedItemPosition)
61+
if (providerName != null) {
62+
viewModel.signIn(requireActivity(), providerName)
63+
} else {
64+
Snackbar.make(requireView(), "No provider selected", Snackbar.LENGTH_SHORT).show()
7465
}
75-
76-
override fun onNothingSelected(parent: AdapterView<*>) {}
7766
}
78-
binding.providerSpinner.setSelection(0)
79-
}
80-
81-
override fun onStart() {
82-
super.onStart()
83-
// Check if user is signed in (non-null) and update UI accordingly.
84-
val currentUser = auth.currentUser
85-
updateUI(currentUser)
86-
87-
// Look for a pending auth result
88-
val pending = auth.pendingAuthResult
89-
if (pending != null) {
90-
pending.addOnSuccessListener { authResult ->
91-
Log.d(TAG, "checkPending:onSuccess:$authResult")
92-
updateUI(authResult.user)
93-
}.addOnFailureListener { e ->
94-
Log.w(TAG, "checkPending:onFailure", e)
95-
}
96-
} else {
97-
Log.d(TAG, "checkPending: null")
67+
binding.signOutButton.setOnClickListener {
68+
viewModel.signOut()
9869
}
99-
}
10070

101-
private fun signIn() {
102-
// Could add custom scopes here
103-
val customScopes = ArrayList<String>()
104-
105-
// Examples of provider ID: apple.com (Apple), microsoft.com (Microsoft), yahoo.com (Yahoo)
106-
val providerId = getProviderId()
107-
108-
auth.startActivityForSignInWithProvider(requireActivity(),
109-
oAuthProvider(providerId, auth) {
110-
scopes = customScopes
111-
})
112-
.addOnSuccessListener { authResult ->
113-
Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
114-
updateUI(authResult.user)
115-
}
116-
.addOnFailureListener { e ->
117-
Log.w(TAG, "activitySignIn:onFailure", e)
118-
showToast(getString(R.string.error_sign_in_failed))
71+
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
72+
override fun onStart(owner: LifecycleOwner) {
73+
super.onStart(owner)
74+
viewModel.showSignedInUser()
75+
}
76+
})
77+
78+
lifecycleScope.launch {
79+
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
80+
viewModel.uiState.collect { uiState ->
81+
binding.status.text = uiState.status
82+
binding.detail.text = uiState.detail
83+
84+
binding.genericSignInButton.isGone = !uiState.isSignInVisible
85+
binding.signOutButton.isGone = uiState.isSignInVisible
86+
binding.spinnerLayout.isGone = !uiState.isSignInVisible
87+
88+
// Spinner
89+
spinnerAdapter = ArrayAdapter(requireContext(), R.layout.item_spinner_list, uiState.providerNames)
90+
binding.providerSpinner.adapter = spinnerAdapter
91+
binding.providerSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
92+
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
93+
binding.genericSignInButton.text =
94+
getString(R.string.generic_signin_fmt, spinnerAdapter.getItem(position))
95+
}
96+
97+
override fun onNothingSelected(parent: AdapterView<*>) {}
98+
}
99+
binding.providerSpinner.setSelection(0)
119100
}
120-
}
121-
122-
private fun getProviderId(): String {
123-
val providerName = spinnerAdapter.getItem(binding.providerSpinner.selectedItemPosition)
124-
return PROVIDER_MAP[providerName!!] ?: error("No provider selected")
125-
}
126-
127-
private fun updateUI(user: FirebaseUser?) {
128-
hideProgressBar()
129-
if (user != null) {
130-
binding.status.text = getString(R.string.generic_status_fmt, user.displayName, user.email)
131-
binding.detail.text = getString(R.string.firebase_status_fmt, user.uid)
132-
133-
binding.spinnerLayout.visibility = View.GONE
134-
binding.genericSignInButton.visibility = View.GONE
135-
binding.signOutButton.visibility = View.VISIBLE
136-
} else {
137-
binding.status.setText(R.string.signed_out)
138-
binding.detail.text = null
139-
140-
binding.spinnerLayout.visibility = View.VISIBLE
141-
binding.genericSignInButton.visibility = View.VISIBLE
142-
binding.signOutButton.visibility = View.GONE
101+
}
143102
}
144103
}
145104

146-
private fun showToast(message: String) {
147-
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
148-
}
149-
150105
override fun onDestroyView() {
151106
super.onDestroyView()
152107
_binding = null
153108
}
154-
155-
companion object {
156-
private const val TAG = "GenericIdp"
157-
private val PROVIDER_MAP = mapOf(
158-
"Apple" to "apple.com",
159-
"Microsoft" to "microsoft.com",
160-
"Yahoo" to "yahoo.com",
161-
"Twitter" to "twitter.com"
162-
)
163-
}
164109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.google.firebase.quickstart.auth.kotlin
2+
3+
import android.app.Activity
4+
import android.util.Log
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import com.google.firebase.auth.FirebaseAuth
8+
import com.google.firebase.auth.FirebaseUser
9+
import com.google.firebase.auth.ktx.auth
10+
import com.google.firebase.auth.ktx.oAuthProvider
11+
import com.google.firebase.ktx.Firebase
12+
import java.util.ArrayList
13+
import kotlinx.coroutines.flow.MutableStateFlow
14+
import kotlinx.coroutines.flow.StateFlow
15+
import kotlinx.coroutines.flow.update
16+
import kotlinx.coroutines.launch
17+
import kotlinx.coroutines.tasks.await
18+
19+
class GenericIdpViewModel(
20+
private val firebaseAuth: FirebaseAuth = Firebase.auth
21+
) : ViewModel() {
22+
private val _uiState = MutableStateFlow(UiState())
23+
val uiState: StateFlow<UiState> = _uiState
24+
25+
data class UiState(
26+
var status: String = "",
27+
var detail: String? = null,
28+
var isSignInVisible: Boolean = true,
29+
val providerNames: List<String> = ArrayList(PROVIDER_MAP.keys)
30+
)
31+
32+
fun showSignedInUser() {
33+
// Check if user is signed in (non-null) and update UI accordingly.
34+
val firebaseUser = firebaseAuth.currentUser
35+
updateUiState(firebaseUser)
36+
37+
// Look for a pending auth result
38+
val pendingAuthResult = firebaseAuth.pendingAuthResult
39+
if (pendingAuthResult != null) {
40+
viewModelScope.launch {
41+
try {
42+
val authResult = pendingAuthResult.await()
43+
Log.d(TAG, "checkPending:onSuccess:$authResult")
44+
updateUiState(authResult.user)
45+
} catch (e: Exception) {
46+
Log.w(TAG, "checkPending:onFailure", e)
47+
}
48+
}
49+
} else {
50+
Log.d(TAG, "checkPending: null")
51+
}
52+
}
53+
54+
fun signIn(activity: Activity, providerName: String) {
55+
// Could add custom scopes here
56+
val customScopes = listOf<String>()
57+
58+
// Examples of provider ID: apple.com (Apple), microsoft.com (Microsoft), yahoo.com (Yahoo)
59+
val providerId = PROVIDER_MAP[providerName]!!
60+
61+
val oAuthProvider = oAuthProvider(providerId) {
62+
scopes = customScopes
63+
}
64+
65+
viewModelScope.launch {
66+
try {
67+
val authResult = firebaseAuth.startActivityForSignInWithProvider(activity, oAuthProvider).await()
68+
Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
69+
updateUiState(authResult.user)
70+
} catch (e: Exception) {
71+
Log.w(TAG, "activitySignIn:onFailure", e)
72+
// TODO(thatfiredev): Snackbar Sign in failed, see logs for details.
73+
}
74+
}
75+
}
76+
77+
fun signOut() {
78+
firebaseAuth.signOut()
79+
updateUiState(null)
80+
}
81+
82+
private fun updateUiState(user: FirebaseUser?) {
83+
if (user != null) {
84+
_uiState.update { currentUiState ->
85+
currentUiState.copy(
86+
status = "User: ${user.displayName} ${user.email}",
87+
detail = "Firebase UID: ${user.uid}",
88+
isSignInVisible = false
89+
)
90+
}
91+
} else {
92+
_uiState.update { currentUiState ->
93+
currentUiState.copy(
94+
status = "Signed out",
95+
detail = null,
96+
isSignInVisible = true
97+
)
98+
}
99+
}
100+
}
101+
102+
companion object {
103+
const val TAG = "GenericIdpViewModel"
104+
private val PROVIDER_MAP = mapOf(
105+
"Apple" to "apple.com",
106+
"Microsoft" to "microsoft.com",
107+
"Yahoo" to "yahoo.com",
108+
"Twitter" to "twitter.com"
109+
)
110+
}
111+
}

0 commit comments

Comments
 (0)