refactor(ui): inject AuthRepository instead of FirebaseAuth.getInstance()

Five ViewModels reached into the FirebaseAuth.getInstance() static singleton for
the current user id; switch them to the injected AuthRepository.currentUserId
(BucketList, QuestionThread, DateBuilder, MessagesInbox, Conversation). Removes
the Firebase-in-presentation-layer coupling and makes them testable. No behavior
change; compiles + unit suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-06 22:12:47 -05:00
parent 89cbd7cb55
commit cce2687312
5 changed files with 19 additions and 15 deletions

View File

@ -4,9 +4,9 @@ import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.closer.domain.model.BucketListItem import app.closer.domain.model.BucketListItem
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.BucketListRepository import app.closer.domain.repository.BucketListRepository
import app.closer.domain.repository.CoupleRepository import app.closer.domain.repository.CoupleRepository
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -18,7 +18,8 @@ import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class BucketListViewModel @Inject constructor( class BucketListViewModel @Inject constructor(
private val repository: BucketListRepository, private val repository: BucketListRepository,
private val coupleRepository: CoupleRepository private val coupleRepository: CoupleRepository,
private val authRepository: AuthRepository
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(BucketListUiState()) private val _uiState = MutableStateFlow(BucketListUiState())
@ -33,7 +34,7 @@ class BucketListViewModel @Inject constructor(
private fun resolveCouple() { private fun resolveCouple() {
viewModelScope.launch { viewModelScope.launch {
val uid = FirebaseAuth.getInstance().currentUser?.uid ?: return@launch val uid = authRepository.currentUserId ?: return@launch
runCatching { coupleRepository.getCoupleForUser(uid) } runCatching { coupleRepository.getCoupleForUser(uid) }
.getOrNull() .getOrNull()
?.let { setCoupleId(it.id) } ?.let { setCoupleId(it.id) }
@ -69,7 +70,7 @@ class BucketListViewModel @Inject constructor(
title = title.take(MAX_TITLE_LENGTH), title = title.take(MAX_TITLE_LENGTH),
description = description.take(MAX_DESCRIPTION_LENGTH), description = description.take(MAX_DESCRIPTION_LENGTH),
category = category, category = category,
addedBy = FirebaseAuth.getInstance().currentUser?.uid ?: "", addedBy = authRepository.currentUserId ?: "",
addedAt = System.currentTimeMillis() addedAt = System.currentTimeMillis()
) )
@ -98,7 +99,7 @@ class BucketListViewModel @Inject constructor(
) )
} }
} else { } else {
repository.completeItem(coupleId, itemId, FirebaseAuth.getInstance().currentUser?.uid ?: "") repository.completeItem(coupleId, itemId, authRepository.currentUserId ?: "")
_uiState.update { _uiState.update {
it.copy( it.copy(
items = it.items.map { items = it.items.map {

View File

@ -5,9 +5,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.closer.domain.model.DatePlan import app.closer.domain.model.DatePlan
import app.closer.domain.model.DatePlanStatus import app.closer.domain.model.DatePlanStatus
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.CoupleRepository import app.closer.domain.repository.CoupleRepository
import app.closer.domain.repository.DatePlanRepository import app.closer.domain.repository.DatePlanRepository
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -19,7 +19,8 @@ import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class DateBuilderViewModel @Inject constructor( class DateBuilderViewModel @Inject constructor(
private val repository: DatePlanRepository, private val repository: DatePlanRepository,
private val coupleRepository: CoupleRepository private val coupleRepository: CoupleRepository,
private val authRepository: AuthRepository
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(DateBuilderUiState()) private val _uiState = MutableStateFlow(DateBuilderUiState())
@ -50,7 +51,7 @@ class DateBuilderViewModel @Inject constructor(
// N-002: create a real PLANNED date_plan (surfaced on Home as the upcoming date) and resolve // N-002: create a real PLANNED date_plan (surfaced on Home as the upcoming date) and resolve
// the couple ourselves. Previously this wrote a date_plan_preference keyed off a dateIdeaId // the couple ourselves. Previously this wrote a date_plan_preference keyed off a dateIdeaId
// that no entry ever set + an empty coupleId, so "Create Plan" silently saved nothing. // that no entry ever set + an empty coupleId, so "Create Plan" silently saved nothing.
val uid = FirebaseAuth.getInstance().currentUser?.uid val uid = authRepository.currentUserId
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() } val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
if (couple == null) { if (couple == null) {
_uiState.update { _uiState.update {

View File

@ -8,12 +8,12 @@ import app.closer.core.billing.CouplePremiumChecker
import app.closer.data.local.QuestionDao import app.closer.data.local.QuestionDao
import app.closer.data.local.mapper.toQuestion import app.closer.data.local.mapper.toQuestion
import app.closer.domain.model.QuestionMessage import app.closer.domain.model.QuestionMessage
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.ConversationRepository import app.closer.domain.repository.ConversationRepository
import app.closer.domain.repository.CoupleRepository import app.closer.domain.repository.CoupleRepository
import app.closer.domain.repository.UserRepository import app.closer.domain.repository.UserRepository
import app.closer.notifications.ActiveThreadMonitor import app.closer.notifications.ActiveThreadMonitor
import app.closer.ui.components.TextInputLimits import app.closer.ui.components.TextInputLimits
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -60,12 +60,13 @@ class ConversationViewModel @Inject constructor(
private val activeThreadMonitor: ActiveThreadMonitor, private val activeThreadMonitor: ActiveThreadMonitor,
private val couplePremiumChecker: CouplePremiumChecker, private val couplePremiumChecker: CouplePremiumChecker,
private val analyticsTracker: AnalyticsTracker, private val analyticsTracker: AnalyticsTracker,
authRepository: AuthRepository,
savedStateHandle: SavedStateHandle savedStateHandle: SavedStateHandle
) : ViewModel() { ) : ViewModel() {
private val coupleId: String = savedStateHandle["coupleId"] ?: "" private val coupleId: String = savedStateHandle["coupleId"] ?: ""
private val conversationId: String = savedStateHandle["conversationId"] ?: "" private val conversationId: String = savedStateHandle["conversationId"] ?: ""
val currentUserId: String = FirebaseAuth.getInstance().currentUser?.uid ?: "" val currentUserId: String = authRepository.currentUserId ?: ""
private val _uiState = MutableStateFlow(ConversationUiState()) private val _uiState = MutableStateFlow(ConversationUiState())
val uiState: StateFlow<ConversationUiState> = _uiState.asStateFlow() val uiState: StateFlow<ConversationUiState> = _uiState.asStateFlow()

View File

@ -5,10 +5,10 @@ import androidx.lifecycle.viewModelScope
import app.closer.data.local.QuestionDao import app.closer.data.local.QuestionDao
import app.closer.data.local.mapper.toQuestion import app.closer.data.local.mapper.toQuestion
import app.closer.domain.model.Conversation import app.closer.domain.model.Conversation
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.ConversationRepository import app.closer.domain.repository.ConversationRepository
import app.closer.domain.repository.CoupleRepository import app.closer.domain.repository.CoupleRepository
import app.closer.domain.repository.UserRepository import app.closer.domain.repository.UserRepository
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -30,10 +30,11 @@ class MessagesInboxViewModel @Inject constructor(
private val repository: ConversationRepository, private val repository: ConversationRepository,
private val coupleRepository: CoupleRepository, private val coupleRepository: CoupleRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val questionDao: QuestionDao private val questionDao: QuestionDao,
authRepository: AuthRepository
) : ViewModel() { ) : ViewModel() {
private val currentUserId: String = FirebaseAuth.getInstance().currentUser?.uid ?: "" private val currentUserId: String = authRepository.currentUserId ?: ""
private val _uiState = MutableStateFlow(MessagesInboxUiState()) private val _uiState = MutableStateFlow(MessagesInboxUiState())
val uiState: StateFlow<MessagesInboxUiState> = _uiState.asStateFlow() val uiState: StateFlow<MessagesInboxUiState> = _uiState.asStateFlow()

View File

@ -13,7 +13,6 @@ import app.closer.domain.model.QuestionMessage
import app.closer.domain.model.QuestionReaction import app.closer.domain.model.QuestionReaction
import app.closer.domain.repository.QuestionThreadRepository import app.closer.domain.repository.QuestionThreadRepository
import app.closer.ui.components.TextInputLimits import app.closer.ui.components.TextInputLimits
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -54,12 +53,13 @@ class QuestionThreadViewModel @Inject constructor(
private val localAnswerRepository: app.closer.domain.repository.LocalAnswerRepository, private val localAnswerRepository: app.closer.domain.repository.LocalAnswerRepository,
private val userRepository: app.closer.domain.repository.UserRepository, private val userRepository: app.closer.domain.repository.UserRepository,
private val coupleRepository: app.closer.domain.repository.CoupleRepository, private val coupleRepository: app.closer.domain.repository.CoupleRepository,
authRepository: app.closer.domain.repository.AuthRepository,
savedStateHandle: SavedStateHandle savedStateHandle: SavedStateHandle
) : ViewModel() { ) : ViewModel() {
private val coupleId: String = savedStateHandle["coupleId"] ?: "" private val coupleId: String = savedStateHandle["coupleId"] ?: ""
private val questionId: String = savedStateHandle["questionId"] ?: "" private val questionId: String = savedStateHandle["questionId"] ?: ""
val currentUserId: String = FirebaseAuth.getInstance().currentUser?.uid ?: "" val currentUserId: String = authRepository.currentUserId ?: ""
// Released-once guard for our thread reveal key. // Released-once guard for our thread reveal key.
private var threadKeyReleased = false private var threadKeyReleased = false