refactor(ui): remove raw Firestore listeners from Daily/Partner VMs
DailyQuestionViewModel and PartnerHomeViewModel each held a raw db.collection(...).addSnapshotListener on the partner's answer doc (just reading snapshot.exists()). Replace with the existing, proven FirestoreAnswerDataSource.observeAnswerForUser(...) Flow collected in a viewModelScope Job (auto-cancelled; previous run cancelled on restart) — dropping the FirebaseFirestore injection from both. Same behavior (exists → non-null), now through the data layer. Verified live: daily question shows the correct "waiting for your partner" state via the new Flow; compiles + unit suite green. HomeViewModel's two listeners use MetadataChanges.INCLUDE + inline analytics and drive the core reveal — left as a scoped follow-on (needs a metadata-aware data-source Flow + two-device real-time verification). See docs/standardization-review.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cce2687312
commit
0c59000065
|
|
@ -59,7 +59,6 @@ import app.closer.ui.components.LoadingState
|
|||
import app.closer.ui.theme.CloserPalette
|
||||
import app.closer.ui.theme.closerBackgroundBrush
|
||||
import app.closer.ui.theme.closerCardColor
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.functions.FirebaseFunctions
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
|
@ -67,6 +66,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import app.closer.ui.components.CloserGlyphs
|
||||
|
|
@ -91,14 +91,14 @@ class PartnerHomeViewModel @Inject constructor(
|
|||
private val coupleRepository: CoupleRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val answerDataSource: FirestoreAnswerDataSource,
|
||||
private val db: FirebaseFirestore,
|
||||
private val functions: FirebaseFunctions,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(PartnerHomeUiState())
|
||||
val uiState: StateFlow<PartnerHomeUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var partnerAnswerListener: com.google.firebase.firestore.ListenerRegistration? = null
|
||||
// Cancelled/replaced each time the observer (re)starts; auto-cancelled with viewModelScope.
|
||||
private var partnerObserverJob: Job? = null
|
||||
|
||||
init {
|
||||
load()
|
||||
|
|
@ -148,21 +148,16 @@ class PartnerHomeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun observePartnerAnswer(coupleId: String, userIds: List<String>, questionId: String?) {
|
||||
partnerAnswerListener?.remove()
|
||||
partnerObserverJob?.cancel()
|
||||
val uid = authRepository.currentUserId ?: return
|
||||
val partnerId = userIds.firstOrNull { it != uid } ?: return
|
||||
val qId = questionId ?: return
|
||||
if (questionId == null) return
|
||||
val today = FirestoreAnswerDataSource.todayLocalDateString()
|
||||
partnerAnswerListener = db.collection("couples").document(coupleId)
|
||||
.collection("daily_question").document(today)
|
||||
.collection("answers").document(partnerId)
|
||||
.addSnapshotListener { snapshot, error ->
|
||||
if (error != null) {
|
||||
Log.w(TAG, "Partner answer listener error", error)
|
||||
return@addSnapshotListener
|
||||
}
|
||||
_uiState.update { it.copy(hasPartnerAnsweredToday = snapshot?.exists() == true) }
|
||||
partnerObserverJob = viewModelScope.launch {
|
||||
answerDataSource.observeAnswerForUser(coupleId, partnerId, today).collect { partnerAnswer ->
|
||||
_uiState.update { it.copy(hasPartnerAnsweredToday = partnerAnswer != null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendReminder() {
|
||||
|
|
@ -185,7 +180,7 @@ class PartnerHomeViewModel @Inject constructor(
|
|||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
partnerAnswerListener?.remove()
|
||||
partnerObserverJob?.cancel()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ import app.closer.domain.repository.AuthRepository
|
|||
import app.closer.domain.repository.CoupleRepository
|
||||
import app.closer.domain.repository.LocalAnswerRepository
|
||||
import app.closer.domain.usecase.DailyQuestionResolver
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.firestore.ListenerRegistration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class LocalQuestionUiState(
|
||||
|
|
@ -47,19 +46,18 @@ class DailyQuestionViewModel @Inject constructor(
|
|||
private val coupleRepository: CoupleRepository,
|
||||
private val crashReporter: CrashReporter,
|
||||
private val dailyQuestionResolver: DailyQuestionResolver,
|
||||
private val retentionAnalytics: RetentionAnalytics,
|
||||
private val db: FirebaseFirestore
|
||||
private val retentionAnalytics: RetentionAnalytics
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(LocalQuestionUiState())
|
||||
val uiState: StateFlow<LocalQuestionUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var partnerAnswerListener: ListenerRegistration? = null
|
||||
// Cancelled/replaced each time the observer (re)starts; auto-cancelled with viewModelScope.
|
||||
private var partnerObserverJob: Job? = null
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
partnerAnswerListener?.remove()
|
||||
partnerAnswerListener = null
|
||||
partnerObserverJob?.cancel()
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
@ -124,21 +122,14 @@ class DailyQuestionViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun startPartnerAnswerObserver(coupleId: String, date: String) {
|
||||
viewModelScope.launch {
|
||||
partnerObserverJob?.cancel()
|
||||
partnerObserverJob = viewModelScope.launch {
|
||||
val userId = authRepository.currentUserId ?: return@launch
|
||||
val couple = runCatching { coupleRepository.getCoupleForUser(userId) }.getOrNull() ?: return@launch
|
||||
val partnerId = couple.userIds.firstOrNull { it != userId } ?: return@launch
|
||||
partnerAnswerListener?.remove()
|
||||
partnerAnswerListener = db.collection("couples")
|
||||
.document(coupleId)
|
||||
.collection("daily_question")
|
||||
.document(date)
|
||||
.collection("answers")
|
||||
.document(partnerId)
|
||||
.addSnapshotListener { snapshot, error ->
|
||||
if (error != null) return@addSnapshotListener
|
||||
_uiState.update { it.copy(partnerHasAnswered = snapshot?.exists() == true) }
|
||||
}
|
||||
firestoreAnswerDataSource.observeAnswerForUser(coupleId, partnerId, date).collect { partnerAnswer ->
|
||||
_uiState.update { it.copy(partnerHasAnswered = partnerAnswer != null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue