Вопрос или проблема
Я пытаюсь создать простое приложение, которое использует Firestore, чтобы просто вставлять токены устройств и извлекать имена, соответствующие этим токенам. Когда я пытался получить значение “name”, в моем вспомогательном классе значение оказывается правильным, но в ViewModel это значение не получается.
Я заметил 2 наблюдения из логов->
1. Судя по логам, сначала
2024-10-05 09:32:29.658 29752-29808 FirestoreHelperForToken com.example.fcmpractice D Установка addSnapshotListener
сначала создаётся мой класс. Затем мой ViewModel пытается получить это значение, поэтому появляется этот лог
"2024-10-05 09:32:30.350 29752-29752 ChatViewModel com.example.fcmpractice D Список равен null "
затем переменная fetchAllName получает доступ к нему
"2024-10-05 09:32:30.826 29752-29752 FirestoreHelperForToken com.example.fcmpractice D Размер снимка: 10
2024-10-05 09:32:30.833 29752-29752 FirestoreHelperForToken com.example.fcmpractice D Список имен: [mona, abhijit, mahesh, ritu, jodha, madhav, babu, raghav, mamu, nammy] "
возможно, поэтому мой ViewModel не получает это значение.
2. В моем ViewModel этот фрагмент кода
viewModelScope.launch {
firestoreHelperForToken.fetchAllNames.collect { names ->
_getAllNames.value = names
Log.d("ChatViewModel", "Полученные имена из Firestore: $names")
}
}
лог внутри функции collect не печатается. Я не знаю, почему. Пожалуйста, помогите мне вашим мнением и помощью.
Это мои файлы кода, я пробовал использовать лог на каждом шаге, но не смог решить проблему.
-
Вспомогательный класс
class FirestoreHelperForToken { val scope = CoroutineScope(Dispatchers.IO) private val TAG = "FirestoreHelperForToken" private val _db = Firebase.firestore val fetchAllNames: Flow<List<String>> = channelFlow { trySend(emptyList()) Log.d(TAG, "Установка addSnapshotListener") _db.collection("tokens") .addSnapshotListener { snapshot, e -> if (e != null) { Log.w(TAG, "Ошибка прослушивания: $e") return@addSnapshotListener } if (snapshot == null || snapshot.isEmpty) { Log.d(TAG, "Снимок равен null или пуст.") trySend(emptyList()) // Отправить пустой список, если документы не найдены return@addSnapshotListener } val namesList = mutableListOf<String>() Log.d(TAG, "Размер снимка: ${snapshot.size()}") for (document in snapshot.documents) { val name = document.getString("name") name?.let { namesList.add(it) } } try { Log.d(TAG, "Список имен: $namesList") trySend(namesList) // Отправить список имен } catch (sendError: Exception) { Log.e(TAG, "Ошибка при отправке списка имен: ${sendError.message}") } } }.flowOn(Dispatchers.IO) fun insertToken(tokenData: TokenData , onSameName: () -> Unit){ _db.collection("tokens") .whereEqualTo("name", tokenData.name.lowercase()) .get() .addOnSuccessListener { result -> if (result.isEmpty) { val localToken = hashMapOf( "name" to tokenData.name.lowercase(Locale.ROOT), "token" to tokenData.token ) _db.collection("tokens") .add(localToken) .addOnSuccessListener { Log.d(TAG, "Документ успешно добавлен с ID: ${it.id}") } .addOnFailureListener { e -> Log.w(TAG, "Ошибка при добавлении документа", e) } }else{ onSameName() } } .addOnFailureListener { e -> Log.w(TAG, "Ошибка проверки документа", e) } } fun updateToken(tokenData: TokenData) { _db.collection("tokens") .whereEqualTo("name", tokenData.name.lowercase()) .get() .addOnSuccessListener { result -> if (!result.isEmpty) { val documentId = result.documents[0].id _db.collection("tokens") .document(documentId) .update("token", tokenData.token) .addOnSuccessListener { Log.d(TAG, "Токен успешно заменён для ${tokenData.name}") } .addOnFailureListener { e -> Log.w(TAG, "Ошибка при обновлении токена", e) } } } .addOnFailureListener { e -> Log.w(TAG, "Ошибка проверки документа", e) } } } data class TokenData( val name: String, val token: String )
-
ViewModel
class ChatViewModel ( private val chatRepository: ChatRepository, application: Application ) : AndroidViewModel(application) { val firestoreHelperForToken = FirestoreHelperForToken() private val _getAllNames = MutableLiveData<List<String>>() val getAllNames : LiveData<List<String>> get() = _getAllNames var state by mutableStateOf(ChatState()) private set private val api : FcmApi = Retrofit.Builder() .baseUrl("http://10.0.2.2:8080/") .addConverterFactory(MoshiConverterFactory.create()) .build() .create() private val _receivedMessageList = MutableLiveData<List<ChatData>>() val receivedMessageList: LiveData<List<ChatData>> get() = _receivedMessageList init { //подписка на тему для широковещательной рассылки сообщений viewModelScope.launch { firestoreHelperForToken.fetchAllNames.collect { namesList -> _getAllNames.value = namesList // Лог внутри блока collect для проверки получения данных Log.d("ChatViewModel", "Список из Firestore: $namesList") // Небольшая задержка для синхронизации данных при необходимости delay(100) // Проверка, что список не равен null или пуст, и логирование if (!namesList.isNullOrEmpty()) { Log.d("ChatViewModel", "Получен действительный список: $namesList") } else { Log.d("ChatViewModel", "Получен пустой список.") } } // Лог вне блока collect после завершения получения Log.d("ChatViewModel", "Список вне блока collect: ${getAllNames.value}") Firebase.messaging.subscribeToTopic("chat").await() chatRepository.getAllChats().collect{ _receivedMessageList.value = it } } } fun insertToken(tokenData: TokenData, onSameName : () -> Unit){ firestoreHelperForToken.insertToken(tokenData, onSameName) } fun sendMessage(isBroadcast : Boolean){ viewModelScope.launch { val messageDto = SendMessageDto( to = if(isBroadcast) null else state.remoteToken, notification = NotificationBody( title = "Новое сообщение", body = state.messageText ) ) try { if(isBroadcast){ api.broadcastMessage(messageDto) }else{ try { val response = api.sendMessage(messageDto) if (response.isSuccessful) { Log.d("ChatViewModel", "Сообщение успешно отправлено: ${response.message()}") } else { Log.e("ChatViewModel", "Ошибка ответа: ${response.code()} - ${response.errorBody()?.string()}") } } catch (e: HttpException) { Log.e("ChatViewModel", "HTTP Исключение: ${e.response()?.code()} - ${e.response()?.errorBody()?.string()}") } catch (e: IOException) { Log.e("ChatViewModel", "Сетевая ошибка: ${e.message}") } catch (e: Exception) { Log.e("ChatViewModel", "Неожиданная ошибка: ${e.message}") } } state = state.copy(messageText = "") }catch (e : Exception){ e.printStackTrace() } } } fun onNameChange(newName : String){ state = state.copy( nameText = newName ) } fun onMessageTextChange(text : String){ state = state.copy(messageText = text) } fun onTokenChange(newToken : String){ state = state.copy(remoteToken = newToken) } fun onSubmitToken(){ state = state.copy(isEnteringToken = false) } }
-
AppNavigation
@Composable fun AppNavigation(viewModel: ChatViewModel,context: Context){ val navController = rememberNavController() val state = viewModel.state NavHost(navController = navController, startDestination = "JoinConversation") { composable(route = "JoinConversation"){ EnteringTokenScreen( onJoinConversationClicked = { navController.navigate("ChatListScreen") }, name = state.nameText, onNameChange = viewModel::onNameChange, onSaveTokenClicked = { viewModel.insertToken(it,onSameName = { Toast.makeText(context,"Имя уже существует",Toast.LENGTH_SHORT).show() }) } ) } composable(route = "ChatListScreen"){ val nameList = viewModel.getAllNames.observeAsState(emptyList()) ChatListScreen(nameList.value) } } }
-
ChatListScreen
@Composable fun ChatListScreen(nameList : List<String>){ LazyColumn ( modifier = Modifier .fillMaxSize() .padding(16.dp) ){ items(nameList){ name-> Text(text = name) } } }
Вот логи, которые я получаю:
2024-10-05 10:24:09.915 30753-30779 FirestoreHelperForToken com.example.fcmpractice D Установка addSnapshotListener 2024-10-05 10:24:10.645 30753-30753 ChatViewModel com.example.fcmpractice D Список из Firestore: [] 2024-10-05 10:24:10.650 30753-30753 FirestoreHelperForToken com.example.fcmpractice D Размер снимка: 12 2024-10-05 10:24:10.670 30753-30753 FirestoreHelperForToken com.example.fcmpractice D Список имен: [mona, abhijit, mahesh, ritu, jodha, biru, madhav, babu, sujal, raghav, mamu, nammy] 2024-10-05 10:24:10.751 30753-30753 ChatViewModel com.example.fcmpractice D Получен пустой список. 2024-10-05 10:24:10.758 30753-30753 ChatViewModel com.example.fcmpractice D Список вне блока collect: []
Ответ или решение
Давайте разберем вашу проблему с получением данных из Firestore и их использованием в ViewModel
с помощью LiveData
. Исходя из вашей информации, мы видим, что Flow
в вашем классе FirestoreHelperForToken
вроде бы успешно получает данные, но они не доходят до вашего ViewModel
.
Анализ кода
-
Инициализация Flow:
В вашем классеFirestoreHelperForToken
вы используетеchannelFlow
для создания потока, который подписывается на изменения в Firestore. Важно убедиться, что каждый раз, когда snapshot обновляется, вы вызываетеtrySend
правильно. -
Обработка данных:
ВViewModel
вы собираете данные изFlow
, но лог внутри блокаcollect
не срабатывает, что указывает на то, что поток не активен либо не обновляется должным образом.
Потенциальные проблемы и решения
-
Асинхронная инициализация:
Возможно,fetchAllNames
активируется прежде, чем Firestore успевает отправить данные. Убедитесь, что асинхронные операции, такие как созданиеaddSnapshotListener
, не вызывают гонок состояний. -
Правильное использование trySend:
Убедитесь, что вaddSnapshotListener
вы вызываетеtrySend
на каждое обновление данных, а не просто в начале. Если Firestore сообщает об изменениях, вы должны передавать данные и вызыватьtrySend(namesList)
для обработки событий. -
Использование
MutableStateFlow
вместоchannelFlow
:
Возможно, использованиеStateFlow
будет более подходящим, так как он реализует состояние, которое всегда доступно. Вот как вы можете это сделать:
class FirestoreHelperForToken {
...
private val _fetchAllNames = MutableStateFlow<List<String>>(emptyList())
val fetchAllNames: StateFlow<List<String>> get() = _fetchAllNames.asStateFlow()
init {
setUpSnapshotListener()
}
private fun setUpSnapshotListener() {
_db.collection("tokens").addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(TAG, "Listen failed with error: $e")
return@addSnapshotListener
}
if (snapshot == null || snapshot.isEmpty) {
Log.d(TAG, "Snapshot is null or empty.")
_fetchAllNames.value = emptyList() // Emit empty list
return@addSnapshotListener
}
val namesList = snapshot.documents.mapNotNull { it.getString("name") }
Log.d(TAG, "Names list: $namesList")
_fetchAllNames.value = namesList // Update value
}
}
}
- Изменение ViewModel:
В вашемViewModel
вам нужно будет изменить способ сбора данных:
init {
viewModelScope.launch {
firestoreHelperForToken.fetchAllNames.collect { namesList ->
_getAllNames.value = namesList
Log.d("ChatViewModel", "Fetched names: $namesList")
}
}
}
Проверка логов
Обязательно отследите логи, чтобы убедиться, что каждая часть вашего кода выполняется так, как задумано. Убедитесь, что trySend()
действительно вызывается, когда Firestore получает данные.
Заключение
Проблема с правами доступа и инициализацией данных должна быть решена с использованием StateFlow
, который лучше подходит для отслеживания состояния в потоке данных. Проверьте, что все логические пути вашей программы работают корректно, и используйте более простую структуру данных, чтобы улучшить читаемость и надежность вашего кода. Надеюсь, это поможет вам решить вашу задачу!