Skip to content

Address the UI/UX feedback on the universal-components SDK#15

Merged
pmathew92 merged 11 commits intomainfrom
sdk_feedback
Apr 7, 2026
Merged

Address the UI/UX feedback on the universal-components SDK#15
pmathew92 merged 11 commits intomainfrom
sdk_feedback

Conversation

@pmathew92
Copy link
Copy Markdown
Contributor

Description

This PR addressed feedbacks shared by the UI/UX team on the SDK .

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test improvement

Testing

  • Unit tests
  • Manual testing
  • Sample app testing

Checklist

Copilot AI review requested due to automatic review settings April 6, 2026 10:40
@pmathew92 pmathew92 requested a review from a team as a code owner April 6, 2026 10:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 UserInfo model + 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 MyAccountModule to UniversalComponentsModule, 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 MyAccountModule in favor of UniversalComponentsModule is a breaking API change for any SDK consumers that referenced com.auth0.universalcomponents.di.MyAccountModule. If this module is intended to be public, consider keeping a deprecated MyAccountModule wrapper/alias that forwards to UniversalComponentsModule to 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")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
TODO("Not yet implemented")
client.updateAuthenticationMethodName(authenticationMethodId, name).await()

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. this will be updated later

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmathew92 valid point!

Comment on lines +3 to +20
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()
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional . The underlying SDK handles the threading in this scenario

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or could make it async and show loader reading the message: "Fetching user..."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already async .

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmathew92 let's add a note that the threading handling is already done!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not required IMO. I am planning to remove this for all use cases so this will be consistent

Comment on lines +16 to +22
override suspend fun getUserInfo(): UserInfo {
val user = tokenProvider.fetchCredentials().user
return UserInfo(
email = user.email,
name = user.name,
pictureUrl = user.pictureURL
)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it but shouldn't it not return a User instance if all the underlying properties are null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}: ")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Log.d(TAG, "Received email ${userInfo.email}: ")
Log.d(TAG, "Email prefill succeeded")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already a debug log and won't be published in release

Sudhanshu96
Sudhanshu96 previously approved these changes Apr 7, 2026
* derived from the session credentials.
*/
data class UserInfo(
val email: String?,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we make email non empty. Without any primary key no point in creating a model. Cannot make it hashable otherwise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

@Sudhanshu96 Sudhanshu96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@pmathew92 pmathew92 merged commit 2b7e06c into main Apr 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants