MutableLiveData postValue не всегда уведомляет наблюдателя - PullRequest
0 голосов
/ 06 мая 2020

У меня есть погодное приложение с одним действием и архитектурой MVVM.

Я использую 3 переменные данных в реальном времени:

  1. currentWeather : для наблюдения за текущим информация о погоде.
  2. weatherForecast : для просмотра информации о прогнозе погоды.
  3. weatherResult : для наблюдения за статусом операции.

Моя проблема в том, что иногда наблюдатель weatherResult не вызывается при выполнении postValue, а иногда и происходит.

Я не знаю, почему это происходит, я сделал несколько журналов, и вот соответствующий код:

Enum class

enum class WeatherResultState {
    WRONG_CITY_NAME, NO_INTERNET, FINISHED, EXCEPTION, LOCATION_IS_OFF
}

Просмотреть модель

private val locationProvider by lazy {
    LocationServices.getFusedLocationProviderClient(application)
}

private val _weatherForecast = MutableLiveData<ForecastWeatherModel>()
val weatherForecast: LiveData<ForecastWeatherModel>
    get() = _weatherForecast

private val _currentWeather = MutableLiveData<List<Data>>()
val currentWeather: LiveData<List<Data>>
    get() = _currentWeather

    val lastLocation = MutableLiveData<String>()

 private val _weatherResult = MutableLiveData<WeatherResultState>()
val weatherResult: LiveData<WeatherResultState>
    get() = _weatherResult

 fun onFinishWeatherResult() {
    _weatherResult.value = null
}

fun getWeatherByLocation(units: String) {
    Log.i("WeatherViewModel", "getWeatherByLocation accessed")

    locationProvider.lastLocation.addOnSuccessListener { location ->
        Log.i("WeatherViewModel", " addOnSuccessListener accessed")

        if (location != null) {
            // Save latitude and longitude again in val so we can deal with them
            val latitude = location.latitude.toLong()
            val longitude = location.longitude.toLong()

            Log.i("WeatherViewModel", " latitude: $latitude, longitude: $longitude")

            // Handler for parentJob
            val handler = CoroutineExceptionHandler { _, exception ->
                when (exception.cause) {
                    is UnknownHostException -> {
                        Log.e("WeatherViewModel", "No internet!")
                        _weatherResult.postValue(WeatherResultState.NO_INTERNET)
                    }
                    is CancellationException -> {
                        Log.e("WeatherViewModel", "Job canceled!")
                    }
                    else -> {
                        if (exception.cause != null) {
                            Log.e("WeatherViewModel", "${exception.cause}!")
                            _weatherResult.postValue(WeatherResultState.EXCEPTION)
                        }
                    }
                }
            }

            val parentJob = viewModelScope.launch(Dispatchers.IO + handler) {
                Log.i("WeatherViewModel", "parentJob accessed!")
                val currentWeatherJob = launch {
                    getCurrentWeatherByLocation(latitude, longitude, units)
                }

                currentWeatherJob.invokeOnCompletion {
                    Log.i("WeatherViewModel", "invokeOnCompletion current accessed!, $it")
                    it?.let { throwable ->
                        throw throwable
                    }
                }

                val forecastWeatherJob = launch {
                    getWeatherForecastByLocation(latitude, longitude, units)
                }

                forecastWeatherJob.invokeOnCompletion {
                    Log.i("WeatherViewModel", "invokeOnCompletion forecast accessed!, $it")
                    it?.let { throwable ->
                        throw throwable
                    }
                }
            }

            parentJob.invokeOnCompletion {
                Log.i("WeatherViewModel", "invokeOnCompletion parent accessed!, $it")
                if (it == null) {
                    Log.e("WeatherViewModel", "ParentJob finished successfully")
                    _weatherResult.postValue(WeatherResultState.FINISHED)
                }
            }

        } else {
            Log.e("WeatherViewModel", "location is null")
            _weatherResult.postValue(WeatherResultState.LOCATION_IS_OFF)
        }
    }

    locationProvider.lastLocation.addOnFailureListener {
        Log.e("WeatherViewModel", "addOnFailureListener -> $it")
    }
}

private suspend fun getCurrentWeatherByLocation(
    latitude: Long,
    longitude: Long,
    units: String
) {
    Log.i("WeatherViewModel", "getCurrentWeatherByLocation func accessed!")
    // Get current weather from api by location
    val currentWeather =
        repository.getCurrentWeatherByLocationFromApi(latitude, longitude, units)

    // Assume this calling as last location
    lastLocation.postValue(currentWeather.data[0].cityName)

    // Insert units manually (Because it's not returned from the api)
    currentWeather.data[0].units = units
    currentWeather.data[0].isCurrentLocation = true

    // Insert data to database
    repository.insertCurrentWeatherToRoom(currentWeather.data)

    // Getting data from database
    _currentWeather.postValue(
        repository.getCurrentWeatherByLocationFromRoom(
            latitude.toString(),
            longitude.toString()
        )
    )
}

private suspend fun getWeatherForecastByLocation(
    latitude: Long,
    longitude: Long,
    units: String
) {
    Log.i("WeatherViewModel", "getWeatherForecastByLocation func accessed!")
    // Get sixteen days forecast from api by coordinates
    val sixteenWeather =
        repository.getWeatherForecastByLocationFromApi(latitude, longitude, units)

    // Save the latitude and longitude in the object so you can look for it in the database
    sixteenWeather.lat = latitude.toString()
    sixteenWeather.lon = longitude.toString()

    // Insert data to database
    repository.insertForecastWeatherToRoom(sixteenWeather)

    // Getting data from database
    _weatherForecast.postValue(
        repository.getWeatherForecastByLocationFromRoom(
            latitude.toString(),
            longitude.toString()
        )
    )
}

WelcomeFragment (у которого есть наблюдатель для weatherResult)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // Getting last location and units from shared preferences
    // And if they haven't created yet! get the default values
    lastLocation = sharedPref?.getString(LAST_LOCATION_SHARED_PREF, "")!!
    units = sharedPref?.getString(UNITS_SHARED_PREF, "M")!!

    if (savedInstanceState == null) {
        // Check location permission Only if this is  first use of app
        ifFirstUsedJob = CoroutineScope(Dispatchers.IO).launch {
            if (weatherViewModel.isCitiesInRoomNull()) {
                withContext(Dispatchers.Main) {
                    checkLocationPermission()
                }
            } else {
                weatherViewModel.getWeatherByCityName(lastLocation, units)
            }
        }
    } else {
        // Check if dialog was showing, If so.. show it.. else dismiss it
        isDialogShowing = savedInstanceState.getBoolean(IS_DIALOG_SHOWING_KEY)

        if (isDialogShowing) {
            dialog.show()
        } else {
            dialog.dismiss()
        }
    }

    setDialog()
    observeWeatherState()
}

 private fun observeWeatherState() {
    weatherViewModel.weatherResult.observe(viewLifecycleOwner, Observer { weatherState ->
        Log.i("WelcomeFragment", "weatherResult observe accessed!, $weatherState")
        weatherState?.let {
            when (weatherState.name) {
                WeatherResultState.FINISHED.toString() -> {
                    Log.i("WelcomeFragment", "FINISHED")
                    if (dialog.isShowing) {
                        dialog.dismiss()
                        isDialogShowing = false
                    }
                    findNavController().navigate(R.id.action_welcomeFragment_to_mainFragment)
                }

                WeatherResultState.NO_INTERNET.toString() -> {
                    Log.e("WelcomeFragment", "No internet")
                    okBtn.isEnabled = true
                    this.requireContext().toast(resources.getString(R.string.error_no_internet))
                    navigateJob1 = CoroutineScope(Dispatchers.IO).launch {
                        if (!weatherViewModel.isCitiesInRoomNull()) {
                            withContext(Dispatchers.Main) {
                                findNavController().navigate(R.id.action_welcomeFragment_to_mainFragment)
                            }
                        }
                    }
                }

                WeatherResultState.WRONG_CITY_NAME.toString() -> {
                    Log.e("WelcomeFragment", "Wrong city name")
                    cityNameEditText.error = resources.getString(R.string.error_city_name)
                }

                WeatherResultState.EXCEPTION.toString() -> {
                    Log.e("WelcomeFragment", "EXCEPTION")
                    this.requireContext().toast(resources.getString(R.string.error_general))
                    navigateJob2 = CoroutineScope(Dispatchers.IO).launch {
                        if (!weatherViewModel.isCitiesInRoomNull()) {
                            withContext(Dispatchers.Main) {
                                findNavController().navigate(R.id.action_welcomeFragment_to_mainFragment)
                            }
                        }
                    }
                }

                WeatherResultState.LOCATION_IS_OFF.toString() -> {
                    enableLocationSnackBar(
                        resources.getString(R.string.enable_gps),
                        Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
                    )
                }
            }
            weatherViewModel.onFinishWeatherResult()
        }
    })
}

  override fun onDestroyView() {
    super.onDestroyView()
    Log.i("WelcomeFragment", "onDestroyView called")
    ifFirstUsedJob.cancel()
    navigateJob1.cancel()
    navigateJob2.cancel()
    weatherViewModel.weatherResult.removeObservers(viewLifecycleOwner)
}

Результат журнала при вызове наблюдателя

2020-05-06 17:45:46.164 16159-16159/com.hraa.worldweather I/WeatherViewModel: getWeatherByLocation accessed
2020-05-06 17:45:46.252 16159-16159/com.hraa.worldweather I/WeatherViewModel:  addOnSuccessListener accessed
2020-05-06 17:45:46.252 16159-16159/com.hraa.worldweather I/WeatherViewModel:  latitude: 33, longitude: 44
2020-05-06 17:45:46.255 16159-16269/com.hraa.worldweather I/WeatherViewModel: parentJob accessed!
2020-05-06 17:45:46.257 16159-16271/com.hraa.worldweather I/WeatherViewModel: getCurrentWeatherByLocation func accessed!
2020-05-06 17:45:46.258 16159-16270/com.hraa.worldweather I/WeatherViewModel: getWeatherForecastByLocation func accessed!
2020-05-06 17:45:47.259 16159-16270/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion current accessed!, null
2020-05-06 17:45:47.326 16159-16270/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion forecast accessed!, null
2020-05-06 17:45:47.326 16159-16270/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion parent accessed!, null
2020-05-06 17:45:47.326 16159-16270/com.hraa.worldweather E/WeatherViewModel: ParentJob finished successfully
2020-05-06 17:45:47.327 16159-16159/com.hraa.worldweather I/WelcomeFragment: weatherResult observe accessed!, FINISHED
2020-05-06 17:45:47.327 16159-16159/com.hraa.worldweather I/WelcomeFragment: FINISHED
2020-05-06 17:45:47.357 16159-16159/com.hraa.worldweather I/WelcomeFragment: weatherResult observe accessed!, null

записать результат, когда наблюдатель не

2020-05-06 17:50:19.794 18732-18732/com.hraa.worldweather I/WeatherViewModel: getWeatherByLocation accessed
2020-05-06 17:50:19.876 18732-18732/com.hraa.worldweather I/WeatherViewModel:  addOnSuccessListener accessed
2020-05-06 17:50:19.876 18732-18732/com.hraa.worldweather I/WeatherViewModel:  latitude: 33, longitude: 44
2020-05-06 17:50:19.877 18732-18772/com.hraa.worldweather I/WeatherViewModel: parentJob accessed!
2020-05-06 17:50:19.879 18732-18774/com.hraa.worldweather I/WeatherViewModel: getCurrentWeatherByLocation func accessed!
2020-05-06 17:50:19.880 18732-18773/com.hraa.worldweather I/WeatherViewModel: getWeatherForecastByLocation func accessed!
2020-05-06 17:50:20.989 18732-18774/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion current accessed!, null
2020-05-06 17:50:21.008 18732-18774/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion forecast accessed!, null
2020-05-06 17:50:21.008 18732-18774/com.hraa.worldweather I/WeatherViewModel: invokeOnCompletion parent accessed!, null
2020-05-06 17:50:21.008 18732-18774/com.hraa.worldweather E/WeatherViewModel: ParentJob finished successfully
...