Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/208.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 房间实体';s数据存储正确,但检索为空_Android_Kotlin_Android Room_Android Livedata - Fatal编程技术网

Android 房间实体';s数据存储正确,但检索为空

Android 房间实体';s数据存储正确,但检索为空,android,kotlin,android-room,android-livedata,Android,Kotlin,Android Room,Android Livedata,我想做的事 当我的应用程序启动时,我正在使用一个片段,该片段使用一个AutoCompleteTextView和Places SDK在用户做出选择时获取一个Place对象。当这种情况发生时,我通过调用Repository.storeWeatherLocation(context,placeId)并在需要时再次获取天气详细信息,通过我的存储库类将所选地点(作为WeatherLocation实体)存储在我的房间数据库中 发生了什么事 suspend fun storeWeatherLocationAs

我想做的事

当我的应用程序启动时,我正在使用一个片段,该片段使用一个
AutoCompleteTextView
和Places SDK在用户做出选择时获取一个
Place
对象。当这种情况发生时,我通过调用
Repository.storeWeatherLocation(context,placeId)
并在需要时再次获取天气详细信息,通过我的存储库类将所选地点(作为WeatherLocation实体)存储在我的房间数据库中

发生了什么事

suspend fun storeWeatherLocationAsync
正在调用
fetchCurrentWeather()
&
fetchWeeklyWeather()
,因为根据我的日志记录,
previousLocation
变量为空,尽管数据库检查器显示旧的天气位置数据已经存在

碰撞详细信息

我的应用程序正在崩溃,说明我的LocationProvider的
getCustomLocationLat()
返回空值(发生在
fetchCurrentWeather()
中)。问题是,用户选择的位置已成功存储在my Room数据库中(使用数据库检查器进行检查),因此该函数如何返回null

更新

在使用调试器和logcat进行了更多测试之后,我发现当应用程序运行时,
WeatherLocation
数据被保存在房间中。一旦它崩溃,我重新打开它,该数据再次为空。我错过了什么?我是否以某种方式删除了以前的数据?我不是在房间里正确地缓存了它吗

数据库类:

@Database(
    entities = [CurrentWeatherEntry::class,WeekDayWeatherEntry::class,WeatherLocation::class],
    version = 16
)
abstract class ForecastDatabase : RoomDatabase() {
    abstract fun currentWeatherDao() : CurrentWeatherDao
    abstract fun weekDayWeatherDao() : WeekDayWeatherDao
    abstract fun weatherLocationDao() : WeatherLocationDao

    // Used to make sure that the ForecastDatabase class will be a singleton
    companion object {
        // Volatile == all of the threads will have immediate access to this property
        @Volatile private var instance:ForecastDatabase? = null
        private val LOCK = Any() // dummy object for thread monitoring

        operator fun invoke(context:Context) = instance ?: synchronized(LOCK) {
            // If the instance var hasn't been initialized, call buildDatabase()
            // and assign it the returned object from the function call (it)
            instance ?: buildDatabase(context).also { instance = it }
        }

        /**
         * Creates an instance of the ForecastDatabase class
         * using Room.databaseBuilder().
         */
        private fun buildDatabase(context: Context) =
            Room.databaseBuilder(context.applicationContext,
                ForecastDatabase::class.java, "forecast.db")
                //.addMigrations(MIGRATION_2_3) // specify an explicit Migration Technique
                .fallbackToDestructiveMigration()
                .build()
    }
}
以下是存储库类:

class ForecastRepositoryImpl(
    private val currentWeatherDao: CurrentWeatherDao,
    private val weekDayWeatherDao: WeekDayWeatherDao,
    private val weatherLocationDao: WeatherLocationDao,
    private val locationProvider: LocationProvider,
    private val weatherNetworkDataSource: WeatherNetworkDataSource
) : ForecastRepository {

    init {
        weatherNetworkDataSource.apply {
            // Persist downloaded data
            downloadedCurrentWeatherData.observeForever { newCurrentWeather: CurrentWeatherResponse? ->
                persistFetchedCurrentWeather(newCurrentWeather!!)
            }
            downloadedWeeklyWeatherData.observeForever { newWeeklyWeather: WeeklyWeatherResponse? ->
                persistFetchedWeeklyWeather(newWeeklyWeather!!)
            }
        }
    }

    override suspend fun getCurrentWeather(): LiveData<CurrentWeatherEntry> {
        return withContext(Dispatchers.IO) {
            initWeatherData()
            return@withContext currentWeatherDao.getCurrentWeather()
        }
    }

    override suspend fun getWeekDayWeatherList(time: Long): LiveData<out List<WeekDayWeatherEntry>> {
        return withContext(Dispatchers.IO) {
            initWeatherData()
            return@withContext weekDayWeatherDao.getFutureWeather(time)
        }
    }

    override suspend fun getWeatherLocation(): LiveData<WeatherLocation> {
        return withContext(Dispatchers.IO) {
            return@withContext weatherLocationDao.getWeatherLocation()
        }
    }

    private suspend fun initWeatherData() {
        // retrieve the last weather location from room
        val lastWeatherLocation = weatherLocationDao.getWeatherLocation().value

        if (lastWeatherLocation == null ||
            locationProvider.hasLocationChanged(lastWeatherLocation)
        ) {
            fetchCurrentWeather()
            fetchWeeklyWeather()
            return
        }

        val lastFetchedTime = currentWeatherDao.getCurrentWeather().value?.zonedDateTime
        if (isFetchCurrentNeeded(lastFetchedTime!!))
            fetchCurrentWeather()

        if (isFetchWeeklyNeeded())
            fetchWeeklyWeather()
    }

    /**
     * Checks if the current weather data should be re-fetched.
     * @param lastFetchedTime The time at which the current weather data were last fetched
     * @return True or false respectively
     */
    private fun isFetchCurrentNeeded(lastFetchedTime: ZonedDateTime): Boolean {
        val thirtyMinutesAgo = ZonedDateTime.now().minusMinutes(30)
        return lastFetchedTime.isBefore(thirtyMinutesAgo)
    }

    /**
     * Fetches the Current Weather data from the WeatherNetworkDataSource.
     */
    private suspend fun fetchCurrentWeather() {
        weatherNetworkDataSource.fetchCurrentWeather(
            locationProvider.getPreferredLocationLat(),
            locationProvider.getPreferredLocationLong()
        )
    }

    private fun isFetchWeeklyNeeded(): Boolean {
        val todayEpochTime = LocalDate.now().toEpochDay()
        val futureWeekDayCount = weekDayWeatherDao.countFutureWeekDays(todayEpochTime)
        return futureWeekDayCount < WEEKLY_FORECAST_DAYS_COUNT
    }

    private suspend fun fetchWeeklyWeather() {
        weatherNetworkDataSource.fetchWeeklyWeather(
            locationProvider.getPreferredLocationLat(),
            locationProvider.getPreferredLocationLong()
        )
    }

    override fun storeWeatherLocation(context:Context,placeId: String) {
        GlobalScope.launch(Dispatchers.IO) {
            storeWeatherLocationAsync(context,placeId)
        }
    }

    override suspend fun storeWeatherLocationAsync(context: Context,placeId: String) {
        var isFetchNeeded: Boolean // a flag variable

        // Specify the fields to return.
        val placeFields: List<Place.Field> =
            listOf(Place.Field.ID, Place.Field.NAME,Place.Field.LAT_LNG)

        // Construct a request object, passing the place ID and fields array.
        val request = FetchPlaceRequest.newInstance(placeId, placeFields)

        // Create the client
        val placesClient = Places.createClient(context)

        placesClient.fetchPlace(request).addOnSuccessListener { response ->
            // Get the retrieved place object
            val place = response.place
            // Create a new WeatherLocation object using the place details
            val newWeatherLocation = WeatherLocation(place.latLng!!.latitude,
                place.latLng!!.longitude,place.name!!,place.id!!)

            val previousLocation = weatherLocationDao.getWeatherLocation().value
            if(previousLocation == null || ((newWeatherLocation.latitude != previousLocation.latitude) &&
                (newWeatherLocation.longitude != previousLocation.longitude))) {
                isFetchNeeded = true
                // Store the weatherLocation in the database
                persistWeatherLocation(newWeatherLocation)
                // fetch the data
                GlobalScope.launch(Dispatchers.IO) {
                    // fetch the weather data and wait for it to finish
                    withContext(Dispatchers.Default) {
                        if (isFetchNeeded) {
                            // fetch the weather data using the new location
                            fetchCurrentWeather()
                            fetchWeeklyWeather()
                        }
                    }
                }
            }
            Log.d("REPOSITORY","storeWeatherLocationAsync : inside task called")
        }.addOnFailureListener { exception ->
            if (exception is ApiException) {
                // Handle error with given status code.
                Log.e("Repository", "Place not found: ${exception.statusCode}")
            }
        }
    }

    /**
     * Caches the downloaded current weather data to the local
     * database.
     * @param fetchedCurrentWeather The most recently fetched current weather data
     */
    private fun persistFetchedCurrentWeather(fetchedCurrentWeather: CurrentWeatherResponse) {
        fetchedCurrentWeather.currentWeatherEntry.setTimezone(fetchedCurrentWeather.timezone)
        // Using a GlobalScope since a Repository class doesn't have a lifecycle
        GlobalScope.launch(Dispatchers.IO) {
            currentWeatherDao.upsert(fetchedCurrentWeather.currentWeatherEntry)
        }
    }

    /**
     * Caches the selected location data to the local
     * database.
     * @param fetchedLocation The most recently fetched location data
     */
    private fun persistWeatherLocation(fetchedLocation: WeatherLocation) {
        GlobalScope.launch(Dispatchers.IO) {
            weatherLocationDao.upsert(fetchedLocation)
        }
    }

    /**
     * Caches the downloaded weekly weather data to the local
     * database.
     * @param fetchedWeeklyWeather  The most recently fetched weekly weather data
     */
    private fun persistFetchedWeeklyWeather(fetchedWeeklyWeather: WeeklyWeatherResponse) {

        fun deleteOldData() {
            val time = LocalDate.now().toEpochDay()
            weekDayWeatherDao.deleteOldEntries(time)
        }

        GlobalScope.launch(Dispatchers.IO) {
            deleteOldData()
            val weekDayEntriesList = fetchedWeeklyWeather.weeklyWeatherContainer.weekDayEntries
            weekDayWeatherDao.insert(weekDayEntriesList)
        }
    }
}
class ForecastRepositoryImpl(
private val currentWeatherDao:currentWeatherDao,
私人val weekDayWeatherDao:weekDayWeatherDao,
私人val weatherLocationDao:weatherLocationDao,
私有val locationProvider:locationProvider,
专用val weatherNetworkDataSource:weatherNetworkDataSource
):前庭{
初始化{
weatherNetworkDataSource.apply{
//保存下载的数据
下载的CurrentWeatherData.observeForever{NewCurrentWeathers:CurrentWeatherResponse?->
persistFetchedCurrentWeather(newCurrentWeather!!)
}
下载的WeeklyWeatherData.observeForever{newWeeklyWeather:WeeklyWeatherResponse?->
PersisteFetchedWeeklyWeather(newWeeklyWeather!!)
}
}
}
覆盖getCurrentWeather():LiveData{
返回withContext(Dispatchers.IO){
initWeatherData()
return@withContextcurrentWeatherDao.getCurrentWeather()
}
}
覆盖getWeekDayWeatherList(时间:长):LiveData{
返回withContext(Dispatchers.IO){
initWeatherData()
return@withContextweekDayWeatherDao.getFutureWeather(时间)
}
}
重写getWeatherLocation():LiveData{
返回withContext(Dispatchers.IO){
return@withContextweatherLocationDao.getWeatherLocation()的
}
}
private suspend initWeatherData(){
//从房间中检索最后一个天气位置
val lastWeatherLocation=weatherLocationDao.getWeatherLocation().value
if(lastWeatherLocation==null||
locationProvider.hasLocationChanged(lastWeatherLocation)
) {
fetchCurrentWeather()
获取每周天气信息()
返回
}
val lastFetchedTime=currentWeatherDao.getCurrentWeather().value?.zonedDateTime
如果(IsFetchCurrentRequired(lastFetchedTime!!))
fetchCurrentWeather()
如果(IsFetchWeeklyNeed())
获取每周天气信息()
}
/**
*检查是否应重新获取当前天气数据。
*@param lastFetchedTime上次获取当前天气数据的时间
*@分别返回True或false
*/
private fun IsFetchCurrentRequired(lastFetchedTime:ZonedDateTime):布尔值{
val thirtyMinutesAgo=ZonedDateTime.now().minusMinutes(30)
返回lastFetchedTime.isBefore(三十分钟前)
}
/**
*从WeatherNetworkDataSource获取当前天气数据。
*/
private挂起当前天气(){
weatherNetworkDataSource.fetchCurrentWeather(
locationProvider.getPreferredLocationLat(),
locationProvider.getPreferredLocationLong()
)
}
private fun IsFetchWeeklyNeed():布尔值{
val todaypochtime=LocalDate.now().toEpochDay()
val futureWeekDayCount=weekDayWeatherDao.countFutureWeekDays(TodayPochTime)
return futureWeekDayCount<每周\预测\天数\计数
}
私人娱乐周天气(){
weatherNetworkDataSource.fetchWeeklyWeather(
locationProvider.getPreferredLocationLat(),
locationProvider.getPreferredLocationLong()
)
}
覆盖有趣的storeWeatherLocation(上下文:上下文,placeId:字符串){
GlobalScope.launch(Dispatchers.IO){
storeWeatherLocationAsync(上下文,placeId)
}
}
重写suspend fun storeWeatherLocationAsync(上下文:context,placeId:String){
var isFetchNeeded:Boolean//a标志变量
//指定要返回的字段。
val placeFields:列表=
列表(Place.Field.ID、Place.Field.NAME、Place.Field.LAT_LNG)
//构造一个请求对象,传递位置ID和字段数组。
val request=FetchPlaceRequest.newInstance(placeId,placeFields)
//创建客户端
val placesClient=Places.createClient(上下文)
placesClient.fetchPlace(请求).addOnSuccessListener{response->
//获取检索到的place对象
val place=response.place
//创建一个新的WeatherLocati
class LocationProviderImpl(
    private val fusedLocationProviderClient: FusedLocationProviderClient,
    context: Context,
    private val locationDao: WeatherLocationDao
) : PreferenceProvider(context), LocationProvider {
    private val appContext = context.applicationContext

    override suspend fun hasLocationChanged(lastWeatherLocation: WeatherLocation): Boolean {
        return try {
            hasDeviceLocationChanged(lastWeatherLocation)
        } catch (e:LocationPermissionNotGrantedException) {
            false
        }
    }

    /**
     * Makes the required checks to determine whether the device's location has
     * changed or not.
     * @param lastWeatherLocation The last known user selected location
     * @return true if the device location has changed or false otherwise
     */
    private suspend fun hasDeviceLocationChanged(lastWeatherLocation: WeatherLocation): Boolean {
        if(!isUsingDeviceLocation()) return false // we don't have location permissions or setting's disabled

        val currentDeviceLocation = getLastDeviceLocationAsync().await()
            ?: return false

        // Check if the old and new locations are far away enough that an update is needed
        val comparisonThreshold = 0.03
        return abs(currentDeviceLocation.latitude - lastWeatherLocation.latitude) > comparisonThreshold
                && abs(currentDeviceLocation.longitude - lastWeatherLocation.longitude) > comparisonThreshold
    }

    /**
     * Checks if the app has the location permission, and if that's the case
     * it will fetch the device's last saved location.
     * @return The device's last saved location as a Deferred<Location?>
     */
    @SuppressLint("MissingPermission")
    private fun getLastDeviceLocationAsync(): Deferred<Location?> {
        return if(hasLocationPermission())
            fusedLocationProviderClient.lastLocation.asDeferredAsync()
         else
            throw LocationPermissionNotGrantedException()
    }

    /**
     * Checks if the user has granted the location
     * permission.
     */
    private fun hasLocationPermission(): Boolean {
        return ContextCompat.checkSelfPermission(appContext,
            Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Returns the sharedPrefs value for the USE_DEVICE_LOCATION
     * preference with a default value of "true".
     */
    private fun isUsingDeviceLocation(): Boolean {
        return preferences.getBoolean(USE_DEVICE_LOCATION_KEY,false)
    }

    private fun getCustomLocationLat() : Double {
        val lat:Double? = locationDao.getWeatherLocation().value?.latitude
        if(lat == null) Log.d("LOCATION_PROVIDER","lat is null = $lat")
        return lat!!
    }

    private fun getCustomLocationLong():Double {
        return locationDao.getWeatherLocation().value!!.longitude
    }

    override suspend fun getPreferredLocationLat(): Double {
        if(isUsingDeviceLocation()) {
            try {
                val deviceLocation = getLastDeviceLocationAsync().await()
                    ?: return getCustomLocationLat()
                return deviceLocation.latitude
            } catch (e:LocationPermissionNotGrantedException) {
                return getCustomLocationLat()
            }
        } else {
            return getCustomLocationLat()
        }
    }

    override suspend fun getPreferredLocationLong(): Double {
        if(isUsingDeviceLocation()) {
            try {
                val deviceLocation = getLastDeviceLocationAsync().await()
                    ?: return getCustomLocationLong()
                return deviceLocation.longitude
            } catch (e:LocationPermissionNotGrantedException) {
                return getCustomLocationLong()
            }
        } else {
            return getCustomLocationLong()
        }
    }
}