fix: ViewModel and repository error handling improvements

This commit is contained in:
null 2026-06-17 20:51:18 -05:00
parent c89e4f6bba
commit e5c734d3b2
9 changed files with 77 additions and 15 deletions

View File

@ -1,5 +1,6 @@
package app.closer.data.repository
import app.closer.core.crash.CrashReporter
import app.closer.data.remote.FirestoreCoupleDataSource
import app.closer.data.remote.FirestoreUserDataSource
import app.closer.domain.model.Couple
@ -10,12 +11,17 @@ import javax.inject.Singleton
@Singleton
class CoupleRepositoryImpl @Inject constructor(
private val coupleDataSource: FirestoreCoupleDataSource,
private val userDataSource: FirestoreUserDataSource
private val userDataSource: FirestoreUserDataSource,
private val crashReporter: CrashReporter
) : CoupleRepository {
override suspend fun getCoupleForUser(userId: String): Couple? {
val coupleId = runCatching { userDataSource.getUser(userId)?.coupleId }.getOrNull() ?: return null
return runCatching { coupleDataSource.getCoupleById(coupleId) }.getOrNull()
val coupleId = runCatching { userDataSource.getUser(userId)?.coupleId }
.onFailure { crashReporter.recordException(it) }
.getOrNull() ?: return null
return runCatching { coupleDataSource.getCoupleById(coupleId) }
.onFailure { crashReporter.recordException(it) }
.getOrNull()
}
override suspend fun createCouple(inviterUserId: String, acceptorUserId: String, inviteCode: String): Result<String> = runCatching {

View File

@ -1,5 +1,6 @@
package app.closer.data.repository
import app.closer.core.crash.CrashReporter
import app.closer.data.remote.FirestoreCollections
import app.closer.domain.model.QuestionSession
import app.closer.domain.repository.QuestionSessionRepository
@ -10,7 +11,8 @@ import javax.inject.Singleton
@Singleton
class QuestionSessionRepositoryImpl @Inject constructor(
private val firestore: FirebaseFirestore
private val firestore: FirebaseFirestore,
private val crashReporter: CrashReporter
) : QuestionSessionRepository {
override suspend fun saveSession(session: QuestionSession): Result<Unit> = runCatching {
@ -64,7 +66,9 @@ class QuestionSessionRepositoryImpl @Inject constructor(
isPremium = doc.getBoolean("isPremium") ?: false,
status = doc.getString("status") ?: "completed"
)
}.getOrNull()
}
.onFailure { crashReporter.recordException(it) }
.getOrNull()
}
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.dates
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.domain.model.DateIdea
@ -62,7 +63,9 @@ class DateMatchViewModel @Inject constructor(
val uid = authRepository.currentUserId
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
val partnerName = couple?.userIds?.firstOrNull { it != uid }?.let { partnerId ->
runCatching { userRepository.getUser(partnerId)?.displayName }.getOrNull()
runCatching { userRepository.getUser(partnerId)?.displayName }
.onFailure { Log.w(TAG, "Could not load partner display name", it) }
.getOrNull()
}
val ideas = repository.getDateIdeas()
_uiState.value = DateMatchUiState(
@ -145,4 +148,8 @@ class DateMatchViewModel @Inject constructor(
}
fun retry() = loadDateMatch()
companion object {
private const val TAG = "DateMatchViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.dates
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.data.repository.DateIdeaSeed
@ -59,7 +60,9 @@ class DateMatchesViewModel @Inject constructor(
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
val partnerId = couple?.userIds?.firstOrNull { it != uid }
val partnerName = partnerId?.let { id ->
runCatching { userRepository.getUser(id)?.displayName }.getOrNull()
runCatching { userRepository.getUser(id)?.displayName }
.onFailure { Log.w(TAG, "Could not load partner display name", it) }
.getOrNull()
}
_uiState.value = DateMatchesUiState(
@ -132,4 +135,8 @@ class DateMatchesViewModel @Inject constructor(
}
fun retry() = loadMatches()
companion object {
private const val TAG = "DateMatchesViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.home
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.domain.model.LocalAnswer
@ -106,7 +107,9 @@ class HomeViewModel @Inject constructor(
val uid = authRepository.currentUserId
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
val partnerName = couple?.userIds?.firstOrNull { it != uid }?.let { partnerId ->
runCatching { userRepository.getUser(partnerId)?.displayName }.getOrNull()
runCatching { userRepository.getUser(partnerId)?.displayName }
.onFailure { Log.w(TAG, "Could not load partner display name", it) }
.getOrNull()
}
_uiState.update {
it.copy(
@ -261,4 +264,8 @@ class HomeViewModel @Inject constructor(
split("_", "-")
.filter { part -> part.isNotBlank() }
.joinToString(" ") { part -> part.replaceFirstChar { it.uppercaseChar() } }
companion object {
private const val TAG = "HomeViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.onboarding
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.domain.model.AuthState
@ -34,7 +35,9 @@ class OnboardingViewModel @Inject constructor(
is AuthState.Loading -> _uiState.update { it.copy(isCheckingAuth = true) }
is AuthState.Unauthenticated -> _uiState.update { it.copy(isCheckingAuth = false, navigateTo = null) }
is AuthState.Authenticated -> {
val user = runCatching { userRepository.getUser(authState.userId) }.getOrNull()
val user = runCatching { userRepository.getUser(authState.userId) }
.onFailure { Log.w(TAG, "Could not load user profile during onboarding", it) }
.getOrNull()
val destination = when {
user == null || user.displayName.isBlank() -> "create_profile"
else -> "home"
@ -47,4 +50,8 @@ class OnboardingViewModel @Inject constructor(
}
fun onNavigated() = _uiState.update { it.copy(navigateTo = null) }
companion object {
private const val TAG = "OnboardingViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.pairing
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -46,7 +47,9 @@ class InviteConfirmViewModel @Inject constructor(
.onSuccess { invite ->
loadedInvite = invite
val inviterName = invite?.let {
runCatching { userRepository.getUser(it.inviterUserId)?.displayName }.getOrNull()
runCatching { userRepository.getUser(it.inviterUserId)?.displayName }
.onFailure { e -> Log.w(TAG, "Could not load inviter display name", e) }
.getOrNull()
}
_uiState.update { it.copy(isLoading = false, inviterName = inviterName ?: "your partner") }
}
@ -80,4 +83,8 @@ class InviteConfirmViewModel @Inject constructor(
fun onNavigated() = _uiState.update { it.copy(navigateTo = null) }
fun dismissError() = _uiState.update { it.copy(error = null) }
companion object {
private const val TAG = "InviteConfirmViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.settings
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.core.navigation.AppRoute
@ -45,11 +46,15 @@ class SettingsViewModel @Inject constructor(
return@launch
}
val email = authRepository.currentUserEmail ?: ""
val user = runCatching { userRepository.getUser(userId) }.getOrNull()
val user = runCatching { userRepository.getUser(userId) }
.onFailure { Log.w(TAG, "Could not load current user profile", it) }
.getOrNull()
val couple = coupleRepository.getCoupleForUser(userId)
val partnerId = couple?.userIds?.firstOrNull { it != userId }
val partnerName = partnerId?.let {
runCatching { userRepository.getUser(it)?.displayName }.getOrNull()
runCatching { userRepository.getUser(it)?.displayName }
.onFailure { e -> Log.w(TAG, "Could not load partner display name", e) }
.getOrNull()
}
_uiState.update {
it.copy(
@ -72,4 +77,8 @@ class SettingsViewModel @Inject constructor(
}
fun onNavigated() = _uiState.update { it.copy(navigateTo = null) }
companion object {
private const val TAG = "SettingsViewModel"
}
}

View File

@ -1,5 +1,6 @@
package app.closer.ui.wheel
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -40,7 +41,9 @@ class SpinWheelViewModel @Inject constructor(
private fun loadCategory() {
viewModelScope.launch {
val category = runCatching { repository.getCategoryById(categoryId) }.getOrNull()
val category = runCatching { repository.getCategoryById(categoryId) }
.onFailure { Log.w(TAG, "Could not load wheel category", it) }
.getOrNull()
_uiState.update {
it.copy(
isLoading = false,
@ -56,7 +59,9 @@ class SpinWheelViewModel @Inject constructor(
_uiState.update { it.copy(isSpinning = true, error = null) }
val questions = runCatching {
repository.getQuestionsByCategory(categoryId).shuffled().take(SESSION_SIZE)
}.getOrElse { emptyList() }
}
.onFailure { Log.w(TAG, "Could not load wheel questions", it) }
.getOrElse { emptyList() }
if (questions.isEmpty()) {
_uiState.update {
@ -65,7 +70,9 @@ class SpinWheelViewModel @Inject constructor(
return@launch
}
val category = runCatching { repository.getCategoryById(categoryId) }.getOrNull()
val category = runCatching { repository.getCategoryById(categoryId) }
.onFailure { Log.w(TAG, "Could not load wheel category for session", it) }
.getOrNull()
sessionStore.activeSession = LocalWheelSession(
categoryId = categoryId,
categoryName = category?.displayName ?: categoryId,
@ -85,5 +92,6 @@ class SpinWheelViewModel @Inject constructor(
companion object {
const val SESSION_SIZE = 10
private const val TAG = "SpinWheelViewModel"
}
}