我有一个具有单一活动和MVVM架构的气象应用程序。
我使用3个实时数据变量:
我的问题是,当postValue执行时,有时 weatherResult 的观察者不会被调用,而有时会被调用。
我不知道为什么会这样,我做了一些日志,这是相关代码:
枚举类
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