Переменная LiveData в ViewModel пуста и не соответствует желаемому значению из Flow

Вопрос или проблема

Я пытаюсь создать простое приложение, которое использует 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 не печатается. Я не знаю, почему. Пожалуйста, помогите мне вашим мнением и помощью.

Это мои файлы кода, я пробовал использовать лог на каждом шаге, но не смог решить проблему.

  1. Вспомогательный класс

    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
    )
    
  2. 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)
        }
    
    
    
    
    
    }
    
    
  3. 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)
            }
        }
    }
    
  4. 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.

Анализ кода

  1. Инициализация Flow:
    В вашем классе FirestoreHelperForToken вы используете channelFlow для создания потока, который подписывается на изменения в Firestore. Важно убедиться, что каждый раз, когда snapshot обновляется, вы вызываете trySend правильно.

  2. Обработка данных:
    В ViewModel вы собираете данные из Flow, но лог внутри блока collect не срабатывает, что указывает на то, что поток не активен либо не обновляется должным образом.

Потенциальные проблемы и решения

  1. Асинхронная инициализация:
    Возможно, fetchAllNames активируется прежде, чем Firestore успевает отправить данные. Убедитесь, что асинхронные операции, такие как создание addSnapshotListener, не вызывают гонок состояний.

  2. Правильное использование trySend:
    Убедитесь, что в addSnapshotListener вы вызываете trySend на каждое обновление данных, а не просто в начале. Если Firestore сообщает об изменениях, вы должны передавать данные и вызывать trySend(namesList) для обработки событий.

  3. Использование 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
        }
    }
}
  1. Изменение ViewModel:
    В вашем ViewModel вам нужно будет изменить способ сбора данных:
init {
    viewModelScope.launch {
        firestoreHelperForToken.fetchAllNames.collect { namesList ->
            _getAllNames.value = namesList
            Log.d("ChatViewModel", "Fetched names: $namesList")
        }
    }
}

Проверка логов

Обязательно отследите логи, чтобы убедиться, что каждая часть вашего кода выполняется так, как задумано. Убедитесь, что trySend() действительно вызывается, когда Firestore получает данные.

Заключение

Проблема с правами доступа и инициализацией данных должна быть решена с использованием StateFlow, который лучше подходит для отслеживания состояния в потоке данных. Проверьте, что все логические пути вашей программы работают корректно, и используйте более простую структуру данных, чтобы улучшить читаемость и надежность вашего кода. Надеюсь, это поможет вам решить вашу задачу!

Оцените материал
Добавить комментарий

Капча загружается...