Skip to content

Commit 93c1245

Browse files
authored
feat(authenticator): Handle SignedIn events from outside of Authenticator (#236)
1 parent 8682849 commit 93c1245

File tree

2 files changed

+115
-22
lines changed

2 files changed

+115
-22
lines changed

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ internal class AuthenticatorViewModel(application: Application, private val auth
118118
)
119119
val events = _events.asSharedFlow()
120120

121+
// Is there a current Amplify call in progress that could result in a signed in event?
122+
private var expectingSignInEvent: Boolean = false
123+
121124
fun start(configuration: AuthenticatorConfiguration) {
122125
if (::configuration.isInitialized) {
123126
return
@@ -242,24 +245,30 @@ internal class AuthenticatorViewModel(application: Application, private val auth
242245
}
243246

244247
private suspend fun handleAutoSignIn(username: String, password: String) {
245-
when (val result = authProvider.autoSignIn()) {
246-
is AmplifyResult.Error -> {
247-
// If auto sign in fails then proceed with manually trying to sign in the user. If this also fails the
248-
// user will end up back on the sign in screen.
249-
logger.warn("Unable to complete auto-signIn")
250-
handleSignedUp(username, password)
248+
startSignInJob {
249+
when (val result = authProvider.autoSignIn()) {
250+
is AmplifyResult.Error -> {
251+
// If auto sign in fails then proceed with manually trying to sign in the user. If this also fails the
252+
// user will end up back on the sign in screen.
253+
logger.warn("Unable to complete auto-signIn")
254+
handleSignedUp(username, password)
255+
}
256+
257+
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
251258
}
252-
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
253259
}
254260
}
255261

256262
private suspend fun handleSignedUp(username: String, password: String) {
257-
when (val result = authProvider.signIn(username, password)) {
258-
is AmplifyResult.Error -> {
259-
moveTo(AuthenticatorStep.SignIn)
260-
handleSignInFailure(username, password, result.error)
263+
startSignInJob {
264+
when (val result = authProvider.signIn(username, password)) {
265+
is AmplifyResult.Error -> {
266+
moveTo(AuthenticatorStep.SignIn)
267+
handleSignInFailure(username, password, result.error)
268+
}
269+
270+
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
261271
}
262-
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
263272
}
264273
}
265274

@@ -268,31 +277,31 @@ internal class AuthenticatorViewModel(application: Application, private val auth
268277

269278
@VisibleForTesting
270279
suspend fun signIn(username: String, password: String) {
271-
viewModelScope.launch {
280+
startSignInJob {
272281
when (val result = authProvider.signIn(username, password)) {
273282
is AmplifyResult.Error -> handleSignInFailure(username, password, result.error)
274283
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
275284
}
276-
}.join()
285+
}
277286
}
278287

279288
private suspend fun confirmSignIn(username: String, password: String, challengeResponse: String) {
280-
viewModelScope.launch {
289+
startSignInJob {
281290
when (val result = authProvider.confirmSignIn(challengeResponse)) {
282291
is AmplifyResult.Error -> handleSignInFailure(username, password, result.error)
283292
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
284293
}
285-
}.join()
294+
}
286295
}
287296

288297
private suspend fun setNewSignInPassword(username: String, password: String) {
289-
viewModelScope.launch {
298+
startSignInJob {
290299
when (val result = authProvider.confirmSignIn(password)) {
291300
// an error here is more similar to a sign up error
292301
is AmplifyResult.Error -> handleSignUpFailure(result.error)
293302
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
294303
}
295-
}.join()
304+
}
296305
}
297306

298307
private suspend fun handleSignInFailure(username: String, password: String, error: AuthException) {
@@ -520,9 +529,11 @@ internal class AuthenticatorViewModel(application: Application, private val auth
520529
logger.debug("Password reset complete")
521530
sendMessage(PasswordResetMessage)
522531
if (username != null && password != null) {
523-
when (val result = authProvider.signIn(username, password)) {
524-
is AmplifyResult.Error -> moveTo(stateFactory.newSignInState(this::signIn))
525-
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
532+
startSignInJob {
533+
when (val result = authProvider.signIn(username, password)) {
534+
is AmplifyResult.Error -> moveTo(stateFactory.newSignInState(this::signIn))
535+
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
536+
}
526537
}
527538
} else {
528539
moveTo(stateFactory.newSignInState(this::signIn))
@@ -636,9 +647,27 @@ internal class AuthenticatorViewModel(application: Application, private val auth
636647
}
637648
}
638649

650+
private suspend fun startSignInJob(body: suspend () -> Unit) {
651+
expectingSignInEvent = true
652+
viewModelScope.launch { body() }.join()
653+
expectingSignInEvent = false
654+
}
655+
639656
// Amplify has told us the user signed in.
640657
private suspend fun handleSignedInEvent() {
641-
// TODO : move the user to signedInState *if* we are not in the process of signing in or verifying the user
658+
if (!expectingSignInEvent && !inPostSignInState()) {
659+
handleSignedIn()
660+
}
661+
}
662+
663+
private fun inPostSignInState(): Boolean {
664+
val step = currentState.step
665+
return when (step) {
666+
is AuthenticatorStep.VerifyUser,
667+
is AuthenticatorStep.VerifyUserConfirm,
668+
is AuthenticatorStep.SignedIn -> true
669+
else -> false
670+
}
642671
}
643672

644673
private fun handleSignedOut() {

authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package com.amplifyframework.ui.authenticator
1717

1818
import android.app.Application
19+
import androidx.lifecycle.viewmodel.compose.viewModel
1920
import aws.smithy.kotlin.runtime.http.HttpException
21+
import com.amplifyframework.auth.AuthChannelEventName
2022
import com.amplifyframework.auth.AuthUserAttributeKey.email
2123
import com.amplifyframework.auth.AuthUserAttributeKey.emailVerified
2224
import com.amplifyframework.auth.MFAType
@@ -28,6 +30,7 @@ import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep
2830
import com.amplifyframework.auth.result.step.AuthResetPasswordStep
2931
import com.amplifyframework.auth.result.step.AuthSignInStep
3032
import com.amplifyframework.auth.result.step.AuthSignUpStep
33+
import com.amplifyframework.hub.HubEvent
3134
import com.amplifyframework.ui.authenticator.auth.VerificationMechanism
3235
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
3336
import com.amplifyframework.ui.authenticator.util.AmplifyResult
@@ -45,7 +48,11 @@ import io.mockk.every
4548
import io.mockk.mockk
4649
import java.net.UnknownHostException
4750
import kotlinx.coroutines.ExperimentalCoroutinesApi
51+
import kotlinx.coroutines.delay
52+
import kotlinx.coroutines.flow.MutableSharedFlow
53+
import kotlinx.coroutines.launch
4854
import kotlinx.coroutines.test.advanceUntilIdle
55+
import kotlinx.coroutines.test.runCurrent
4956
import kotlinx.coroutines.test.runTest
5057
import org.junit.Before
5158
import org.junit.Rule
@@ -65,10 +72,13 @@ class AuthenticatorViewModelTest {
6572

6673
private val viewModel = AuthenticatorViewModel(application, authProvider)
6774

75+
private val hubFlow = MutableSharedFlow<HubEvent<*>>(replay = 0)
76+
6877
@Before
6978
fun setup() {
7079
coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration()
7180
coEvery { authProvider.getCurrentUser() } returns Success(mockUser())
81+
coEvery { authProvider.authStatusEvents() } returns hubFlow
7282
}
7383

7484
//region start tests
@@ -441,6 +451,60 @@ class AuthenticatorViewModelTest {
441451
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
442452
}
443453

454+
@Test
455+
fun `moves to SignedInState when receiving SignedIn event`() = runTest {
456+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
457+
458+
viewModel.start(mockAuthenticatorConfiguration())
459+
runCurrent()
460+
461+
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
462+
hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))
463+
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
464+
}
465+
466+
@Test
467+
fun `does not advance to signed in if sign in is in progress when SignedIn event is received`() = runTest {
468+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
469+
coEvery { authProvider.signIn(any(), any()) } coAnswers {
470+
delay(1000) // delay so that the sign in does not complete until the clock is advanced
471+
Success(mockSignInResult())
472+
}
473+
474+
viewModel.start(mockAuthenticatorConfiguration())
475+
runCurrent()
476+
477+
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
478+
479+
backgroundScope.launch { viewModel.signIn("username", "password") }
480+
481+
hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))
482+
483+
// Since sign in is in progress we should not move to SignedIn until after it completes
484+
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
485+
advanceUntilIdle() // advance the clock to complete sign in
486+
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
487+
}
488+
489+
@Test
490+
fun `does not advance to SignedIn when SignedIn event is received in a post-sign-in state`() = runTest {
491+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
492+
coEvery { authProvider.signIn(any(), any()) } returns Success(mockSignInResult())
493+
coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
494+
verificationMechanisms = setOf(VerificationMechanism.Email)
495+
)
496+
coEvery { authProvider.fetchUserAttributes() } returns Success(
497+
mockUserAttributes(email() to "email", emailVerified() to "false")
498+
)
499+
500+
viewModel.start(mockAuthenticatorConfiguration())
501+
viewModel.signIn("username", "password")
502+
503+
viewModel.currentStep shouldBe AuthenticatorStep.VerifyUser
504+
hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))
505+
viewModel.currentStep shouldBe AuthenticatorStep.VerifyUser // stay in current state
506+
}
507+
444508
//endregion
445509
//region sign up tests
446510

0 commit comments

Comments
 (0)