Reputation: 623
I have followed this course trying to implement online caching in my app. Although everything looks similar, when I turn on airplane mode, data doesn't show. Here is my code:
Repository
class WeatherRepository(private val database: WeatherDatabase) {
var weather: LiveData<CurrentWeather> = Transformations.map(database.weatherDao.getWeather()){
it.asDomainModel()
}
suspend fun refreshWeather(city: String){
withContext(Dispatchers.IO){
val weather = WeatherNetwork.service.getCurrentWeather("London", "metric")
database.weatherDao.insert(weather.asDatabaseModel())
Log.i("INSERTING: ", weather.asDatabaseModel().toString())
}
}
//I've created this function only for debugging
fun getDataFromDB(){
weather = Transformations.map(database.weatherDao.getWeather()){
it.asDomainModel()
}
val data = database.weatherDao.getWeather()
Log.i("DB VALUES: ", data.value.toString())
Log.i("VAR WEATHER IN REPO: ", weather.value.toString())
}
}
Room Database
@Dao
interface WeatherDao{
@Query("select * from current_weather")
fun getWeather(): LiveData<DatabaseWeather>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(weather: DatabaseWeather)
}
@Database(entities = [DatabaseWeather::class], version = 1)
abstract class WeatherDatabase: RoomDatabase(){
abstract val weatherDao: WeatherDao
}
private lateinit var INSTANCE: WeatherDatabase
fun getDatabase(context: Context): WeatherDatabase{
synchronized(WeatherDatabase::class.java){
if (!::INSTANCE.isInitialized){
INSTANCE = Room.databaseBuilder(context.applicationContext,
WeatherDatabase::class.java, "weather_db").build()
}
}
return INSTANCE
}
Entity
const val WEATHER_ID = 0
@Entity(tableName = "current_weather")
data class DatabaseWeather constructor(
val name: String,
val lon: Double,
val lat: Double,
val description: String,
val icon: String,
val temp: Double,
val tempFeelsLike: Double,
val humidity: Int,
val pressure: Int
){
@PrimaryKey(autoGenerate = false)
var id: Int = WEATHER_ID
}
fun DatabaseWeather.asDomainModel(): CurrentWeather{
return CurrentWeather(
name = this.name,
lon = this.lon,
lat = this.lat,
description = this.description,
icon = this.icon,
temp = this.temp,
tempFeelsLike = this.tempFeelsLike,
humidity = this.humidity,
pressure = this.pressure
)
}
Network
const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
const val API_KEY = "xxx"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
interface WeatherApiService {
@GET("weather")
suspend fun getCurrentWeather(
@Query("q") cityName: String,
@Query("units") units: String
): WeatherResponse
}
object WeatherNetwork{
private val requestInterceptor = Interceptor { chain ->
val url = chain.request()
.url()
.newBuilder()
.addQueryParameter("appid", API_KEY)
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return@Interceptor chain.proceed(request)
}
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.build()
private val retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val service = retrofit.create(WeatherApiService::class.java)
}
ViewModel
enum class WeatherApiStatus { LOADING, ERROR, DONE }
class CurrentWeatherViewModel(application: Application) : AndroidViewModel(application) {
val place = MutableLiveData<String>()
private val _status = MutableLiveData<WeatherApiStatus>()
val status: LiveData<WeatherApiStatus>
get() = _status
private val weatherRepository = WeatherRepository(getDatabase(application))
val weather = weatherRepository.weather
init {
refreshDataFromRepository()
}
fun refreshDataFromRepository(){
viewModelScope.launch {
try{
weatherRepository.refreshWeather(place.value.toString())
_status.value = WeatherApiStatus.DONE
} catch (e: Exception){
Log.i("FAILURE: ", e.printStackTrace().toString())
_status.value = WeatherApiStatus.ERROR
}
}
}
fun checkDB(){
weatherRepository.getDataFromDB()
}
}
It is working, when I don't call method refreshWeather in WeatherRepository (the data is downloaded from db). I don't know why, but logs "DB VALUES" and "VAR WEATHER IN REPO" always show null ("INSERTING" shows data properly)
Upvotes: 0
Views: 562
Reputation: 96
The problem you're getting when trying to log the values retrieved from room DB and it aways shows null
, is because the database retrieving process always happens asynchronously and the data is not ready yet when you try to log it.
As you can see, DAO's getWeather()
method returns a LiveData
, and you try to log its value right after asking for it, not giving enough time to fetch it from disk, so resulting in a still null
value.
You should observe this LiveData
in order to be notified when the value has been retrieved with success from the database, because of the IO nature of this kind of operation (disk access is considerably very slow when compared to the speeds of CPU and RAM memory, where code and variables reside).
Only for debugging purposes (this code would cause many troubles, such as performance and memory issues because it keeps listening the LiveData
forever, it's advised to observe with the observe
method passing a LifecycleOwner
instead of observing forever), you could do it like this:
fun getDataFromDB(){
weather = Transformations.map(database.weatherDao.getWeather()){
it.asDomainModel()
}
val data = database.weatherDao.getWeather()
data.observeForever { value ->
Log.i("DB VALUES: ", value.toString())
}
weather.observeForever { value ->
Log.i("VAR WEATHER IN REPO: ", value.toString())
}
}
Another way to achieve what you want is by changing the return type of the DAO function to return DatabaseWeather
directly, making it a suspend fun
, like so:
suspend fun getWeather(): DatabaseWeather
but then you would need to change a bunch of other code, just to "wait" for the value to be fetched and ready before being logged (like in the "INSERTING" case, where the code waits on the Coroutine for the WeatherNetwork
call, because of the "suspend" functions in there).
Upvotes: 1