Address the UI/UX feedback on the universal-components SDK#15
Address the UI/UX feedback on the universal-components SDK#15
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the universal-components SDK UI flows based on UI/UX feedback, including improved MFA email enrollment UX (email prefill), passkey error recovery UX, and some internal refactoring/cleanup.
Changes:
- Add a
UserInfomodel +UserRepository+GetUserInfoUseCase, and use it to prefill email during EMAIL authenticator enrollment. - Improve passkey UX by allowing the UI to reset passkey state from an error and show back navigation only in that case.
- Refactor DI wiring from
MyAccountModuletoUniversalComponentsModule, plus small UI import cleanups and authenticator method handling tweaks (dedup factors, email display fix).
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| universal_components/src/test/java/com/auth0/universalcomponents/TestData.kt | Adds UserInfo test fixtures for new prefill-related tests. |
| universal_components/src/test/java/com/auth0/universalcomponents/presentation/viewmodel/PasskeyViewModelTest.kt | Adds coverage for the new resetState() behavior. |
| universal_components/src/test/java/com/auth0/universalcomponents/presentation/viewmodel/EnrollmentViewModelTest.kt | Updates VM construction for new dependency and adds EMAIL prefill tests. |
| universal_components/src/test/java/com/auth0/universalcomponents/domain/usecase/GetUserInfoUseCaseTest.kt | New unit tests for GetUserInfoUseCase result mapping. |
| universal_components/src/test/java/com/auth0/universalcomponents/domain/usecase/GetEnabledAuthenticatorMethodsUseCaseTest.kt | Adds tests validating factor de-duplication behavior. |
| universal_components/src/test/java/com/auth0/universalcomponents/data/repository/UserRepositoryImplTest.kt | New tests for mapping credentials user profile into UserInfo. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/viewmodel/EnrollmentViewModel.kt | Adds prefillEmail to state and triggers prefill for EMAIL authenticator type. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/passkeys/PasskeyViewModel.kt | Adds resetState() API to return UI to Idle after errors. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/passkeys/PasskeyEnableScreen.kt | Removes unused typography imports. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/RecoveryCodeEnrollmentScreen.kt | Switches DI factory provider to UniversalComponentsModule. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/QREnrollmentScreen.kt | Switches DI factory provider to UniversalComponentsModule. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/phone/PhoneEnrollmentScreen.kt | Switches DI factory provider to UniversalComponentsModule. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/OTPVerificationScreen.kt | Switches DI factory provider to UniversalComponentsModule and removes unused import. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt | Switches DI factory providers to UniversalComponentsModule and removes unused import. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EmailEnrollmentScreen.kt | Adds LaunchedEffect to prefill the email input from VM state and switches DI module. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/AuthenticatorMethodsScreen.kt | Updates top bar back navigation behavior for passkey error recovery and switches DI module. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/SnackBar.kt | Removes unused import. |
| universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/ErrorScreen.kt | Removes unused typography imports. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/usecase/GetUserInfoUseCase.kt | Introduces new use case to retrieve identity info from session credentials. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/usecase/GetEnrolledAuthenticatorsUseCase.kt | Fixes email enrolled authenticator display mapping to use email instead of name. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/usecase/GetEnabledAuthenticatorMethodsUseCase.kt | De-duplicates factor list by type when building secondary authenticator list. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/repository/UserRepository.kt | New repository abstraction for retrieving user identity from credentials. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/repository/MyAccountRepository.kt | Adds updateAuthenticationMethodName API to the repository contract. |
| universal_components/src/main/java/com/auth0/universalcomponents/domain/model/UserInfo.kt | New domain model for user identity info used for UI prefill. |
| universal_components/src/main/java/com/auth0/universalcomponents/di/viewmodelfactory/EnrollmentViewModelFactory.kt | Wires GetUserInfoUseCase into EnrollmentViewModel. |
| universal_components/src/main/java/com/auth0/universalcomponents/di/UniversalComponentsModule.kt | Renames/expands DI module to provide UserRepository + GetUserInfoUseCase and updated factories. |
| universal_components/src/main/java/com/auth0/universalcomponents/data/repository/UserRepositoryImpl.kt | Implements UserRepository by mapping Auth0 credentials user profile to UserInfo. |
| universal_components/src/main/java/com/auth0/universalcomponents/data/repository/MyAccountRepositoryImpl.kt | Adds updateAuthenticationMethodName method stub (currently TODO). |
Comments suppressed due to low confidence (1)
universal_components/src/main/java/com/auth0/universalcomponents/di/UniversalComponentsModule.kt:25
- Renaming/removing
MyAccountModulein favor ofUniversalComponentsModuleis a breaking API change for any SDK consumers that referencedcom.auth0.universalcomponents.di.MyAccountModule. If this module is intended to be public, consider keeping a deprecatedMyAccountModulewrapper/alias that forwards toUniversalComponentsModuleto preserve source compatibility (or document this as a breaking change).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| scope = scope | ||
| ) | ||
| val client = myAccountProvider.getMyAccount(accessToken) | ||
| TODO("Not yet implemented") |
There was a problem hiding this comment.
updateAuthenticationMethodName is part of the MyAccountRepository contract but the implementation currently ends with TODO("Not yet implemented"), which will throw at runtime if invoked. Either implement the call against the MyAccount client (and add tests), or remove this method from the interface/PR until it is supported.
| TODO("Not yet implemented") | |
| client.updateAuthenticationMethodName(authenticationMethodId, name).await() |
There was a problem hiding this comment.
Intentional. this will be updated later
| import com.auth0.universalcomponents.domain.DispatcherProvider | ||
| import com.auth0.universalcomponents.domain.error.Auth0Error | ||
| import com.auth0.universalcomponents.domain.model.UserInfo | ||
| import com.auth0.universalcomponents.domain.network.Result | ||
| import com.auth0.universalcomponents.domain.network.safeCall | ||
| import com.auth0.universalcomponents.domain.repository.UserRepository | ||
|
|
||
| /** | ||
| * UseCase that retrieves identity information about the currently authenticated user | ||
| * from session credentials. | ||
| */ | ||
| class GetUserInfoUseCase( | ||
| private val userRepository: UserRepository, | ||
| ) { | ||
| suspend operator fun invoke(): Result<UserInfo, Auth0Error> = | ||
| safeCall { | ||
| userRepository.getUserInfo() | ||
| } |
There was a problem hiding this comment.
getUserInfoUseCase() is called from the ViewModel, but this use case does not switch to an IO dispatcher (unlike the other use cases in this module). Since TokenProvider.fetchCredentials() can involve disk/network work, this can run on the Main thread and cause jank. Consider injecting DispatcherProvider and wrapping the repository call in withContext(dispatcherProvider.io); also remove the currently unused DispatcherProvider import.
There was a problem hiding this comment.
Intentional . The underlying SDK handles the threading in this scenario
There was a problem hiding this comment.
Or could make it async and show loader reading the message: "Fetching user..."
There was a problem hiding this comment.
This is already async .
There was a problem hiding this comment.
@pmathew92 let's add a note that the threading handling is already done!
There was a problem hiding this comment.
Not required IMO. I am planning to remove this for all use cases so this will be consistent
| override suspend fun getUserInfo(): UserInfo { | ||
| val user = tokenProvider.fetchCredentials().user | ||
| return UserInfo( | ||
| email = user.email, | ||
| name = user.name, | ||
| pictureUrl = user.pictureURL | ||
| ) |
There was a problem hiding this comment.
tokenProvider.fetchCredentials().user is a platform type and can be null at runtime depending on available scopes/claims. Accessing user.email/name/pictureURL without a null-check can crash during email prefill. Consider handling a null user by returning UserInfo(null, null, null) (or throwing a domain error) instead of assuming it is always present.
There was a problem hiding this comment.
Is null handling done inside UserInfo, but ideally UserInfo object shouldn't be created if the critical properties are null hence can change the function signature or make it fail-able.
There was a problem hiding this comment.
The Credential's will always return a 'User' instance and is a non-nullable type. The properties are defined as null here because the underlying SDK properties are defined as null. The current use case is very small / minimal and the prsent handling should suffice for now
There was a problem hiding this comment.
Got it but shouldn't it not return a User instance if all the underlying properties are null
There was a problem hiding this comment.
Email will always be present . So a scenario of all properties being null is not possible
| viewModelScope.launch { | ||
| getUserInfoUseCase() | ||
| .onSuccess { userInfo -> | ||
| Log.d(TAG, "Received email ${userInfo.email}: ") |
There was a problem hiding this comment.
This log statement includes the user's email address, which is personally identifiable information and can end up in device logs. Prefer logging a non-PII message (or removing the log), e.g., just that prefill succeeded, without interpolating userInfo.email.
| Log.d(TAG, "Received email ${userInfo.email}: ") | |
| Log.d(TAG, "Email prefill succeeded") |
There was a problem hiding this comment.
Very critical. For the safe side always prefer to use dev logs or otherwise too, let's make it a practice to not include user info (classified or not) in logs (after raising the PR).
There was a problem hiding this comment.
This is already a debug log and won't be published in release
| * derived from the session credentials. | ||
| */ | ||
| data class UserInfo( | ||
| val email: String?, |
There was a problem hiding this comment.
Shouldn't we make email non empty. Without any primary key no point in creating a model. Cannot make it hashable otherwise.
There was a problem hiding this comment.
Depends upon the use case. The underlying SDK property is null . so making this non null will throw a compilation error. Regarding hashability, null properties are used for hashing in Kotlin
Description
This PR addressed feedbacks shared by the UI/UX team on the SDK .
Type of Change
Testing
Checklist
I have read the Auth0 general contribution guidelines
I have read the Auth0 Code of Conduct
All existing and new tests complete without errors