From 049529daabf4195e610ae79c706e99f9c4696ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Ch=C3=AD=20D=C5=A9ng?= Date: Sat, 31 Aug 2024 22:37:18 +0700 Subject: [PATCH] Setup Room Database --- app/build.gradle | 10 ++++ .../sun/weather/data/model/CurrentWeather.kt | 40 +++++++++++++++ .../sun/weather/data/model/HourlyForecast.kt | 29 +++++++++++ .../sun/weather/data/model/WeatherBasic.kt | 17 +++++++ .../sun/weather/data/model/WeeklyForecast.kt | 29 +++++++++++ .../data/model/entity/WeatherEntity.kt | 47 ++++++++++++++++++ .../data/repository/WeatherRepository.kt | 29 ++++++++--- .../repository/source/WeatherDataSource.kt | 22 +++------ .../repository/source/local/AppDatabase.kt | 31 ++++++++++++ .../source/local/WeatherLocalDataSource.kt | 49 +++++-------------- .../local/converter/WeatherBasicConverter.kt | 17 +++++++ .../converter/WeatherBasicListConverter.kt | 19 +++++++ .../repository/source/local/dao/WeatherDao.kt | 21 ++++++++ .../com/sun/weather/di/RepositoryModule.kt | 3 +- .../java/com/sun/weather/ui/MainActivity.kt | 1 - .../java/com/sun/weather/utils/Constant.kt | 3 ++ .../com/sun/weather/utils/DateTimeUtils.kt | 1 + 17 files changed, 304 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/sun/weather/data/model/WeatherBasic.kt create mode 100644 app/src/main/java/com/sun/weather/data/model/entity/WeatherEntity.kt create mode 100644 app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt create mode 100644 app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicConverter.kt create mode 100644 app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicListConverter.kt diff --git a/app/build.gradle b/app/build.gradle index 1a25d6c..fe2f571 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,16 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'io.gitlab.arturbosch.detekt' version '1.22.0-RC2' + id 'kotlin-parcelize' + id 'kotlin-kapt' +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { stream -> + localProperties.load(stream) + } } def localProperties = new Properties() diff --git a/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt b/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt index fd98bba..a092b58 100644 --- a/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt +++ b/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt @@ -1,6 +1,8 @@ package com.sun.weather.data.model +import com.example.weather.utils.ext.combineWithCountry import com.google.gson.annotations.SerializedName +import com.sun.weather.data.model.entity.WeatherEntity data class CurrentWeather( @SerializedName("main") @@ -62,3 +64,41 @@ data class Sys( @SerializedName("country") var country: String, ) + +fun CurrentWeather.toWeatherEntity(): WeatherEntity { + val country: String? = sys.country + val id = nameCity.combineWithCountry(country) + val latitude = coord.lat + val longitude = coord.lon + val timeZone = dt + val city = nameCity + val weatherCurrent: WeatherBasic? = this.toWeatherBasic() + val weatherHourlyList: List? = null + val weatherDailyList: List? = null + + return WeatherEntity( + id = id, + latitude = latitude, + longitude = longitude, + timeZone = timeZone, + city = city, + country = country, + weatherCurrent =weatherCurrent, + weatherHourlyList = weatherHourlyList, + weatherDailyList = weatherDailyList + ) +} + +fun CurrentWeather.toWeatherBasic(): WeatherBasic { + return WeatherBasic( + dateTime = dt, + currentTemperature = main.currentTemperature, + maxTemperature = main.tempMax, + minTemperature = main.tempMin, + iconWeather = weathers.firstOrNull()?.iconWeather, + weatherDescription = weathers.firstOrNull()?.description, + humidity = main.humidity, + percentCloud = clouds.percentCloud, + windSpeed = wind.windSpeed + ) +} diff --git a/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt b/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt index dee5aef..4f8be3a 100644 --- a/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt +++ b/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt @@ -1,6 +1,8 @@ package com.sun.weather.data.model +import com.example.weather.utils.ext.combineWithCountry import com.google.gson.annotations.SerializedName +import com.sun.weather.data.model.entity.WeatherEntity data class HourlyForecast( @SerializedName("cnt") val cnt: Int, @@ -24,3 +26,30 @@ data class HourlyForecastItem( @SerializedName("dt_txt") val dtTxt: String, val iconWeather: String = "", ) +fun HourlyForecast.toWeather(): WeatherEntity{ + return WeatherEntity( + id = city.name.combineWithCountry(city.country), + latitude = city.coord.lat, + longitude = city.coord.lon, + city = city.name, + country = city.country, + weatherCurrent = null, + weatherHourlyList = forecastList.map { it.toWeatherBasic() }, + weatherDailyList = null + ) +} + + +fun HourlyForecastItem.toWeatherBasic(): WeatherBasic{ + return WeatherBasic( + dateTime = dt, + currentTemperature = main.currentTemperature, + maxTemperature = main.tempMax, + minTemperature = main.tempMin, + iconWeather = weather.firstOrNull()?.iconWeather, + weatherDescription = weather.firstOrNull()?.description, + humidity = main.humidity, + percentCloud = clouds.percentCloud, + windSpeed = wind.windSpeed + ) +} diff --git a/app/src/main/java/com/sun/weather/data/model/WeatherBasic.kt b/app/src/main/java/com/sun/weather/data/model/WeatherBasic.kt new file mode 100644 index 0000000..db2b3c1 --- /dev/null +++ b/app/src/main/java/com/sun/weather/data/model/WeatherBasic.kt @@ -0,0 +1,17 @@ +package com.sun.weather.data.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class WeatherBasic( + var dateTime: Long? = 0, + var currentTemperature: Double? = 0.0, + var maxTemperature: Double? = 0.0, + var minTemperature: Double? = 0.0, + var iconWeather: String? = "", + var weatherDescription: String? = "", + var humidity: Int? = 0, + var percentCloud: Int? =0, + var windSpeed: Double? = 0.0 +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt b/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt index 5ae0daa..b5a50d3 100644 --- a/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt +++ b/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt @@ -1,6 +1,9 @@ package com.sun.weather.data.model +import com.example.weather.utils.ext.combineWithCountry import com.google.gson.annotations.SerializedName +import com.sun.weather.data.model.entity.WeatherEntity + data class WeeklyForecast( @SerializedName("city") val city: City, @@ -27,3 +30,29 @@ data class Temp( @SerializedName("eve") val eve: Double, @SerializedName("morn") val morn: Double, ) + +fun WeeklyForecast.toWeatherEntity(): WeatherEntity { + return WeatherEntity( + id = city.name.combineWithCountry(city.country), + latitude = city.coord.lat, + longitude = city.coord.lon, + city = city.name, + country = city.country, + weatherDailyList = forecastList.map { it.toWeatherBasic() }, + weatherHourlyList = null, + weatherCurrent = null + ) +} + +fun WeeklyForecastItem.toWeatherBasic(): WeatherBasic { + return WeatherBasic( + dateTime = dt, + currentTemperature = temp.day, + maxTemperature = temp.max, + minTemperature = temp.min, + iconWeather = weather.firstOrNull()?.iconWeather, + humidity = humidity, + percentCloud = clouds, + windSpeed = speed + ) +} diff --git a/app/src/main/java/com/sun/weather/data/model/entity/WeatherEntity.kt b/app/src/main/java/com/sun/weather/data/model/entity/WeatherEntity.kt new file mode 100644 index 0000000..d25813e --- /dev/null +++ b/app/src/main/java/com/sun/weather/data/model/entity/WeatherEntity.kt @@ -0,0 +1,47 @@ +package com.sun.weather.data.model.entity + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.sun.weather.data.model.WeatherBasic +import com.sun.weather.data.model.entity.WeatherEntry.CURRENTLY_OBJECT +import com.sun.weather.data.model.entity.WeatherEntry.DAILY_OBJECT +import com.sun.weather.data.model.entity.WeatherEntry.HOURLY_OBJECT +import com.sun.weather.data.model.entity.WeatherEntry.TBL_WEATHER_NAME +import kotlinx.parcelize.Parcelize + +@Entity(tableName = TBL_WEATHER_NAME) +@Parcelize +data class WeatherEntity( + @PrimaryKey + val id: String = "", + val latitude: Double? = 0.0, + val longitude: Double? = 0.0, + val timeZone: Long? = 0, + var city: String? = "", + var country: String? = "", + @ColumnInfo(name = CURRENTLY_OBJECT) + val weatherCurrent: WeatherBasic?, + @ColumnInfo(name = HOURLY_OBJECT) + var weatherHourlyList: List?, + @ColumnInfo(name = DAILY_OBJECT) + var weatherDailyList: List? +) : Parcelable { + + fun getLocation(): String { + return if (!city.isNullOrEmpty() && !country.isNullOrEmpty()) { + "$city, $country" + } else { + "Unknown" + } + } +} + +object WeatherEntry { + // Local database entries + const val TBL_WEATHER_NAME = "weather_forecasts" + const val CURRENTLY_OBJECT = "currently" + const val HOURLY_OBJECT = "hourly" + const val DAILY_OBJECT = "daily" +} \ No newline at end of file diff --git a/app/src/main/java/com/sun/weather/data/repository/WeatherRepository.kt b/app/src/main/java/com/sun/weather/data/repository/WeatherRepository.kt index f776f55..93c5527 100644 --- a/app/src/main/java/com/sun/weather/data/repository/WeatherRepository.kt +++ b/app/src/main/java/com/sun/weather/data/repository/WeatherRepository.kt @@ -1,13 +1,26 @@ -package com.sun.weather.data.repository.source +package com.sun.weather.data.repository -import com.sun.weather.data.model.CurrentWeather -import com.sun.weather.data.model.HourlyForecast -import com.sun.weather.data.model.WeeklyForecast +import com.sun.weather.data.model.FavouriteLocation +import com.sun.weather.data.model.Weather +import com.sun.weather.data.model.entity.WeatherEntity import kotlinx.coroutines.flow.Flow interface WeatherRepository { - fun getCurrentWeather(city: String, language: String): Flow - fun getCurrentLocationWeather(lat: Double, lon: Double, language: String): Flow - fun getHourlyForecast(city: String, language: String): Flow - fun getWeeklyForecast(city: String, language: String): Flow + fun getSelectedLocation(key: String): String + fun isFavoriteLocationExists( + cityName: String, + countryName: String, + ): Boolean + + fun saveCurrentWeather(currentWeather: WeatherEntity) + fun saveWeeklyForecastLocal(weeklyForecast: WeatherEntity) + fun getLocalWeather(id: String): Weather? + fun saveHourlyForecastLocal(hourlyForecast: WeatherEntity) + fun insertFavoriteWeather(favouriteLocation: FavouriteLocation) + fun getAllFavorite(): List + fun removeFavoriteItem(id: Long) + fun getCurrentWeather(city: String, language: String): Flow + fun getCurrentLocationWeather(lat: Double, lon: Double, language: String): Flow + fun getHourlyForecast(city: String, language: String): Flow + fun getWeeklyForecast(city: String, language: String): Flow } diff --git a/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt b/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt index f8a720d..e814901 100644 --- a/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt +++ b/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt @@ -1,28 +1,18 @@ package com.sun.weather.data.repository.source import com.sun.weather.data.model.CurrentWeather -import com.sun.weather.data.model.FavouriteLocation import com.sun.weather.data.model.HourlyForecast +import com.sun.weather.data.model.Weather import com.sun.weather.data.model.WeeklyForecast interface WeatherDataSource { interface Local { - suspend fun getSelectedLocation(key: String): String - suspend fun isFavoriteLocationExists( - cityName: String, - countryName: String, - ): Boolean - - suspend fun getCurrentWeatherLocal() - suspend fun saveCurrentWeather(currentWeather: CurrentWeather) - suspend fun getWeeklyForecastLocal(city: String) - suspend fun saveWeeklyForecastLocal(weeklyForecast: WeeklyForecast) - suspend fun getHourlyForecastLocal(city: String) - suspend fun saveHourlyForecastLocal(hourlyForecast: HourlyForecast) - suspend fun insertFavoriteWeather(favouriteLocation: FavouriteLocation) - suspend fun getAllFavorite(): List - suspend fun removeFavoriteItem(id: Long) + suspend fun insertCurrentWeather(current: Weather, hourly: Weather, daily: Weather) + suspend fun insertCurrentWeather(weather: Weather) + suspend fun getAllLocalWeathers(): List + suspend fun getLocalWeather(id: String): Weather? + suspend fun deleteWeather(id: String) } interface Remote { diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt new file mode 100644 index 0000000..9621caa --- /dev/null +++ b/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt @@ -0,0 +1,31 @@ +package com.sun.weather.data.repository.source.local + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.sun.weather.data.model.Weather +import com.sun.weather.data.repository.source.local.converter.WeatherBasicConverter +import com.sun.weather.data.repository.source.local.converter.WeatherBasicListConverter +import com.sun.weather.data.repository.source.local.dao.WeatherDao + +@Database(entities = [Weather::class], version = 1, exportSchema = false) +@TypeConverters(WeatherBasicConverter::class, WeatherBasicListConverter::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun weatherDao(): WeatherDao + companion object { + private const val DB_NAME = "Weather.db" + @Volatile + private var INSTANCE: AppDatabase? = null + fun getInstance(context: Context) = INSTANCE ?: synchronized(this) { + INSTANCE ?: buildDatabase(context).also { INSTANCE = it } + } + private fun buildDatabase(context: Context) = + Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + DB_NAME + ).build() + } +} diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/WeatherLocalDataSource.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/WeatherLocalDataSource.kt index cb4d395..cbc874e 100644 --- a/app/src/main/java/com/sun/weather/data/repository/source/local/WeatherLocalDataSource.kt +++ b/app/src/main/java/com/sun/weather/data/repository/source/local/WeatherLocalDataSource.kt @@ -1,55 +1,30 @@ package com.sun.weather.data.repository.source.local -import com.sun.weather.data.model.CurrentWeather -import com.sun.weather.data.model.FavouriteLocation -import com.sun.weather.data.model.HourlyForecast -import com.sun.weather.data.model.WeeklyForecast +import com.sun.weather.data.model.Weather import com.sun.weather.data.repository.source.WeatherDataSource import com.sun.weather.data.repository.source.local.dao.WeatherDao -class WeatherLocalDataSource( private val weatherDao: WeatherDao): WeatherDataSource.Local { - override suspend fun getSelectedLocation(key: String): String { - TODO("Not yet implemented") - } - - override suspend fun isFavoriteLocationExists(cityName: String, countryName: String): Boolean { - TODO("Not yet implemented") - } - - override suspend fun getCurrentWeatherLocal() { - TODO("Not yet implemented") - } +class WeatherLocalDataSource( + private val weatherDao: WeatherDao +) : WeatherDataSource.Local{ - override suspend fun saveCurrentWeather(currentWeather: CurrentWeather) { - TODO("Not yet implemented") - } + override suspend fun insertCurrentWeather(current: Weather, hourly: Weather, daily: Weather) { - override suspend fun getWeeklyForecastLocal(city: String) { - TODO("Not yet implemented") } - override suspend fun saveWeeklyForecastLocal(weeklyForecast: WeeklyForecast) { - TODO("Not yet implemented") - } + override suspend fun insertCurrentWeather(weather: Weather) { - override suspend fun getHourlyForecastLocal(city: String) { - TODO("Not yet implemented") } - override suspend fun saveHourlyForecastLocal(hourlyForecast: HourlyForecast) { - TODO("Not yet implemented") + override suspend fun getAllLocalWeathers(): List { + return weatherDao.getAllData() } - override suspend fun insertFavoriteWeather(favouriteLocation: FavouriteLocation) { - TODO("Not yet implemented") + override suspend fun getLocalWeather(id: String): Weather? { + return weatherDao.getWeather(id) } - override suspend fun getAllFavorite(): List { - TODO("Not yet implemented") + override suspend fun deleteWeather(id: String) { + weatherDao.deleteWeather(id) } - - override suspend fun removeFavoriteItem(id: Long) { - TODO("Not yet implemented") - } - } diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicConverter.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicConverter.kt new file mode 100644 index 0000000..d7b3ee4 --- /dev/null +++ b/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicConverter.kt @@ -0,0 +1,17 @@ +package com.sun.weather.data.repository.source.local.converter + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.sun.weather.data.model.WeatherBasic + +class WeatherBasicConverter { + @TypeConverter + fun fromWeatherBasic(weatherBasic: WeatherBasic?): String? { + return weatherBasic?.let { Gson().toJson(it) } + } + + @TypeConverter + fun toWeatherBasic(json: String?): WeatherBasic? { + return json?.let { Gson().fromJson(it, WeatherBasic::class.java) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicListConverter.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicListConverter.kt new file mode 100644 index 0000000..1173562 --- /dev/null +++ b/app/src/main/java/com/sun/weather/data/repository/source/local/converter/WeatherBasicListConverter.kt @@ -0,0 +1,19 @@ +package com.sun.weather.data.repository.source.local.converter + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.sun.weather.data.model.WeatherBasic + +class WeatherBasicListConverter { + @TypeConverter + fun fromWeatherBasicList(weatherBasicList: List?): String? { + return weatherBasicList?.let { Gson().toJson(it) } + } + + @TypeConverter + fun toWeatherBasicList(json: String?): List? { + val type = object : TypeToken>() {}.type + return Gson().fromJson(json, type) + } +} diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt index 0b89963..aee036c 100644 --- a/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt +++ b/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt @@ -1,5 +1,26 @@ package com.sun.weather.data.repository.source.local.dao +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.sun.weather.data.model.Weather +import com.sun.weather.data.model.entity.WeatherEntry.TBL_WEATHER_NAME + +@Dao interface WeatherDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertWeather(weather: Weather) + + @Transaction + @Query("SELECT * FROM $TBL_WEATHER_NAME") + suspend fun getAllData(): List + + @Transaction + @Query("SELECT * FROM $TBL_WEATHER_NAME WHERE id = :idWeather") + suspend fun getWeather(idWeather: String): Weather? + @Query("DELETE FROM $TBL_WEATHER_NAME WHERE id = :idWeather") + suspend fun deleteWeather(idWeather: String) } diff --git a/app/src/main/java/com/sun/weather/di/RepositoryModule.kt b/app/src/main/java/com/sun/weather/di/RepositoryModule.kt index 03a2af6..5486d84 100644 --- a/app/src/main/java/com/sun/weather/di/RepositoryModule.kt +++ b/app/src/main/java/com/sun/weather/di/RepositoryModule.kt @@ -1,9 +1,8 @@ package com.sun.weather.di - import com.sun.weather.data.repository.WeatherRepositoryImpl import com.sun.weather.data.repository.source.WeatherDataSource -import com.sun.weather.data.repository.source.WeatherRepository +import com.sun.weather.data.repository.WeatherRepository import com.sun.weather.data.repository.source.local.WeatherLocalDataSource import com.sun.weather.data.repository.source.remote.WeatherRemoteDataSource import org.koin.dsl.module diff --git a/app/src/main/java/com/sun/weather/ui/MainActivity.kt b/app/src/main/java/com/sun/weather/ui/MainActivity.kt index b782c9a..0fda193 100644 --- a/app/src/main/java/com/sun/weather/ui/MainActivity.kt +++ b/app/src/main/java/com/sun/weather/ui/MainActivity.kt @@ -1,4 +1,3 @@ - package com.sun.weather.ui import android.os.Bundle diff --git a/app/src/main/java/com/sun/weather/utils/Constant.kt b/app/src/main/java/com/sun/weather/utils/Constant.kt index 430ce29..8a5e44d 100644 --- a/app/src/main/java/com/sun/weather/utils/Constant.kt +++ b/app/src/main/java/com/sun/weather/utils/Constant.kt @@ -2,6 +2,9 @@ package com.sun.weather.utils import com.sun.weather.BuildConfig + +//import com.sun.weather.BuildConfig + object Constant { const val BASE_URL = "https://api.openweathermap.org/data/2.5/" const val PRO_URL = "https://pro.openweathermap.org/data/2.5/" diff --git a/app/src/main/java/com/sun/weather/utils/DateTimeUtils.kt b/app/src/main/java/com/sun/weather/utils/DateTimeUtils.kt index 6bedde3..8bb2c1e 100644 --- a/app/src/main/java/com/sun/weather/utils/DateTimeUtils.kt +++ b/app/src/main/java/com/sun/weather/utils/DateTimeUtils.kt @@ -1,6 +1,7 @@ package com.sun.weather.utils object DateTimeUtils { + const val TIME_ZONE_UTC = "UTC" const val DATE_TIME_FORMAT_UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'" }