Refactor SignInViewModel for better state management here#12
Refactor SignInViewModel for better state management here#12
Conversation
Refactor SignInViewModel to improve state management and error handling.
Summary of ChangesHello @junior-od, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request initiates a refactoring effort within the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors the SignInViewModel with changes to state management and business logic. While the goal is to improve the ViewModel, several changes introduce significant issues that go against modern Android development best practices. My review focuses on correcting these issues to ensure the state is managed safely and robustly. Key areas of feedback include:
- State Encapsulation: Several
MutableStateFlowproperties are exposed publicly and mutably, breaking encapsulation. They should be private with immutableStateFlows exposed. - Error Handling: Critical functions for signing in and saving user data lack proper exception handling (
try-catch), which can lead to app crashes. - Null Safety: The code uses forced unwrapping (
!!) on nullable objects, which is unsafe and can causeNullPointerExceptions. - Coroutine Usage: There are instances of redundant
suspendmodifiers and nested coroutine launches that should be simplified. - Input Validation: The sign-in logic lacks basic input validation.
I've provided specific suggestions to address these points and align the code with recommended patterns for state management and error handling in a ViewModel.
|
|
||
| private var _signInUiState = MutableStateFlow<SignInUi?>(null) | ||
| // Issue 1: Mutable state exposed directly | ||
| var _signInUiState = MutableStateFlow<SignInUi?>(null) |
There was a problem hiding this comment.
Exposing MutableStateFlow as a public var breaks encapsulation. It allows any part of your app to not only change the state directly but also reassign the StateFlow object itself, leading to unpredictable behavior and bugs that are hard to trace. The backing property should be private val. Additionally, the public property signInUiState should expose an immutable StateFlow to prevent external modifications.
| var _signInUiState = MutableStateFlow<SignInUi?>(null) | |
| private val _signInUiState = MutableStateFlow<SignInUi?>(null) |
| ) | ||
|
|
||
| // Issue 2: No backing field protection | ||
| var _email = MutableStateFlow("") |
There was a problem hiding this comment.
The _email MutableStateFlow is exposed as a public var. This breaks encapsulation and is not a safe way to handle UI state. It should be a private val, and the public email property should expose an immutable StateFlow.
| var _email = MutableStateFlow("") | |
| private val _email = MutableStateFlow("") |
| saveUser( | ||
| userId = user?.uid.toString(), | ||
| userId = user!!.uid.toString(), | ||
| user = User( | ||
| id = "", | ||
| firstName = user?.displayName.toString(), | ||
| email = user?.email.toString() | ||
| firstName = user.displayName!!, | ||
| email = user.email!! | ||
| ) | ||
| ) | ||
|
|
||
| // observe changes and send to ui | ||
| // update state as sign up successful | ||
| _signInUiState.value = SignInUi.Success |
There was a problem hiding this comment.
Using the not-null assertion operator (!!) is unsafe and can cause a NullPointerException if the user object from Google Sign-In is null, or if its displayName or email properties are null. This will crash your app. You should handle the nullable values safely using safe calls and null checks.
user?.let { firebaseUser ->
saveUser(
userId = firebaseUser.uid,
user = User(
id = "",
firstName = firebaseUser.displayName ?: "",
email = firebaseUser.email ?: ""
)
)
_signInUiState.value = SignInUi.Success
} ?: run {
_signInUiState.value = SignInUi.Error("Failed to retrieve user information.")
}| * sign in user | ||
| * */ | ||
| // Issue 3: Unnecessary suspend function + nested viewModelScope.launch | ||
| suspend fun signInUser(){ |
There was a problem hiding this comment.
The signInUser function is marked as suspend, but it immediately launches a new coroutine on viewModelScope. This is redundant. A ViewModel function that starts a fire-and-forget coroutine should not typically be suspend. The caller in SignInScreen also wraps this in another launch, creating an unnecessary nested coroutine.
| suspend fun signInUser(){ | |
| fun signInUser(){ |
| signInUseCase( | ||
| email = _email.value, | ||
| password = _password.value, |
There was a problem hiding this comment.
The sign-in process is initiated without validating the email and password. This can lead to unnecessary network requests with invalid data. You should add checks to ensure the fields are not empty before proceeding.
if (_email.value.isBlank() || _password.value.isBlank()) {
_signInUiState.value = SignInUi.Error("Email and password cannot be empty.")
return@launch
}
signInUseCase(
email = _email.value,
password = _password.value,| // Issue 8: No error handling for database operations | ||
| private suspend fun saveUser(userId: String, user: User){ | ||
| withContext(Dispatchers.IO){ | ||
| createUserUseCase( |
There was a problem hiding this comment.
This call to createUserUseCase performs a database operation without any error handling. If the operation fails (e.g., due to a database constraint), it will throw an exception that will propagate up and crash the coroutine. You should wrap this call in a try-catch block to handle potential exceptions gracefully, at least by logging them.
| "" | ||
| ) | ||
|
|
||
| private var _password = MutableStateFlow("") |
There was a problem hiding this comment.
| false | ||
| ) | ||
|
|
||
| private var _passwordVisible = MutableStateFlow(false) |
There was a problem hiding this comment.
| viewModelScope.launch { | ||
| // observe changes and send to ui | ||
| // update state as sign up successful | ||
| _signInUiState.value = SignInUi.Success | ||
| } |
|
|
||
| } | ||
| } No newline at end of file | ||
| } |
Refactor SignInViewModel to improve state management and error handling.