From 20a1dea2cd65d1d30004aace81800746cfbc1136 Mon Sep 17 00:00:00 2001 From: Johan von Forstner Date: Fri, 17 Apr 2020 20:16:55 +0200 Subject: [PATCH] Build a Room database for favorites --- app/build.gradle | 6 +++ .../api/goingelectric/GoingElectricModel.kt | 32 +++++++----- .../vonforst/evmap/fragment/MapFragment.kt | 7 ++- .../evmap/storage/ChargeLocationsDao.kt | 17 ++++++ .../net/vonforst/evmap/storage/Database.kt | 26 ++++++++++ .../vonforst/evmap/storage/TypeConverters.kt | 52 +++++++++++++++++++ .../vonforst/evmap/viewmodel/MapViewModel.kt | 25 ++++++--- 7 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt create mode 100644 app/src/main/java/net/vonforst/evmap/storage/Database.kt create mode 100644 app/src/main/java/net/vonforst/evmap/storage/TypeConverters.kt diff --git a/app/build.gradle b/app/build.gradle index 7c4deb50..e26912d2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,6 +90,12 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + // room library + def room_version = "2.2.5" + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-ktx:$room_version" + // debug tools implementation 'com.facebook.stetho:stetho:1.5.1' implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' diff --git a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt index 1b27cf66..64281e75 100644 --- a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt +++ b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricModel.kt @@ -3,6 +3,9 @@ package net.vonforst.evmap.api.goingelectric import android.content.Context import android.os.Parcelable import androidx.core.text.HtmlCompat +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import kotlinx.android.parcel.Parcelize @@ -21,11 +24,12 @@ data class ChargepointList( sealed class ChargepointListItem @JsonClass(generateAdapter = true) +@Entity data class ChargeLocation( - @Json(name = "ge_id") val id: Long, + @Json(name = "ge_id") @PrimaryKey val id: Long, val name: String, - val coordinates: Coordinate, - val address: Address, + @Embedded val coordinates: Coordinate, + @Embedded val address: Address, val chargepoints: List, @JsonObjectOrFalse val network: String?, val url: String, @@ -38,8 +42,8 @@ data class ChargeLocation( @JsonObjectOrFalse @Json(name = "location_description") val locationDescription: String?, val photos: List?, //val chargecards: Boolean? - val openinghours: OpeningHours?, - val cost: Cost? + @Embedded val openinghours: OpeningHours?, + @Embedded val cost: Cost? ) : ChargepointListItem() { val maxPower: Double get() { @@ -75,7 +79,7 @@ data class Cost( data class OpeningHours( @Json(name = "24/7") val twentyfourSeven: Boolean, @JsonObjectOrFalse val description: String?, - val days: OpeningHoursDays? + @Embedded val days: OpeningHoursDays? ) { fun getStatusText(ctx: Context): CharSequence { if (twentyfourSeven) { @@ -114,14 +118,14 @@ data class OpeningHours( @JsonClass(generateAdapter = true) data class OpeningHoursDays( - val monday: Hours, - val tuesday: Hours, - val wednesday: Hours, - val thursday: Hours, - val friday: Hours, - val saturday: Hours, - val sunday: Hours, - val holiday: Hours + @Embedded(prefix = "mo") val monday: Hours, + @Embedded(prefix = "tu") val tuesday: Hours, + @Embedded(prefix = "we") val wednesday: Hours, + @Embedded(prefix = "th") val thursday: Hours, + @Embedded(prefix = "fr") val friday: Hours, + @Embedded(prefix = "sa") val saturday: Hours, + @Embedded(prefix = "su") val sunday: Hours, + @Embedded(prefix = "ho") val holiday: Hours ) { fun getHoursForDate(date: LocalDate): Hours { // TODO: check for holidays diff --git a/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt index 12aa8a42..c5870997 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt @@ -58,7 +58,12 @@ const val REQUEST_AUTOCOMPLETE = 2 class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback { private lateinit var binding: FragmentMapBinding private val vm: MapViewModel by viewModels(factoryProducer = { - viewModelFactory { MapViewModel(getString(R.string.goingelectric_key)) } + viewModelFactory { + MapViewModel( + requireActivity().application, + getString(R.string.goingelectric_key) + ) + } }) private val galleryVm: GalleryViewModel by activityViewModels() private var map: GoogleMap? = null diff --git a/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt b/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt new file mode 100644 index 00000000..dfbf02ef --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt @@ -0,0 +1,17 @@ +package net.vonforst.evmap.storage + +import androidx.lifecycle.LiveData +import androidx.room.* +import net.vonforst.evmap.api.goingelectric.ChargeLocation + +@Dao +interface ChargeLocationsDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg locations: ChargeLocation) + + @Delete + fun delete(vararg locations: ChargeLocation) + + @Query("SELECT * FROM chargelocation") + fun getAllChargeLocations(): LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/storage/Database.kt b/app/src/main/java/net/vonforst/evmap/storage/Database.kt new file mode 100644 index 00000000..bc17cb33 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/storage/Database.kt @@ -0,0 +1,26 @@ +package net.vonforst.evmap.storage + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import net.vonforst.evmap.api.goingelectric.ChargeLocation + +@Database(entities = [ChargeLocation::class], version = 1) +@TypeConverters(Converters::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun chargeLocationsDao(): ChargeLocationsDao + + companion object { + private lateinit var context: Context + private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + Room.databaseBuilder(context, AppDatabase::class.java, "evmap.db").build() + } + + fun getInstance(context: Context): AppDatabase { + this.context = context.applicationContext + return database + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/storage/TypeConverters.kt b/app/src/main/java/net/vonforst/evmap/storage/TypeConverters.kt new file mode 100644 index 00000000..aae1f246 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/storage/TypeConverters.kt @@ -0,0 +1,52 @@ +package net.vonforst.evmap.storage + +import androidx.room.TypeConverter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import net.vonforst.evmap.api.goingelectric.Chargepoint +import net.vonforst.evmap.api.goingelectric.ChargerPhoto +import java.time.LocalTime + +class Converters { + val moshi = Moshi.Builder().build() + private val chargepointListAdapter by lazy { + val type = Types.newParameterizedType(List::class.java, Chargepoint::class.java) + moshi.adapter>(type) + } + private val chargerPhotoListAdapter by lazy { + val type = Types.newParameterizedType(List::class.java, ChargerPhoto::class.java) + moshi.adapter>(type) + } + + @TypeConverter + fun fromChargepointList(value: List): String { + return chargepointListAdapter.toJson(value) + } + + @TypeConverter + fun toChargepointList(value: String): List { + return chargepointListAdapter.fromJson(value)!! + } + + @TypeConverter + fun fromChargerPhotoList(value: List): String { + return chargerPhotoListAdapter.toJson(value) + } + + @TypeConverter + fun toChargerPhotoList(value: String): List { + return chargerPhotoListAdapter.fromJson(value)!! + } + + @TypeConverter + fun fromLocalTime(value: LocalTime?): String? { + return value?.toString() + } + + @TypeConverter + fun toLocalTime(value: String?): LocalTime? { + return value.let { + LocalTime.parse(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt index ee22009c..5878ddb9 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/MapViewModel.kt @@ -1,9 +1,7 @@ package net.vonforst.evmap.viewmodel -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import android.app.Application +import androidx.lifecycle.* import com.google.android.gms.maps.model.LatLngBounds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -15,6 +13,7 @@ import net.vonforst.evmap.api.goingelectric.ChargeLocation import net.vonforst.evmap.api.goingelectric.ChargepointList import net.vonforst.evmap.api.goingelectric.ChargepointListItem import net.vonforst.evmap.api.goingelectric.GoingElectricApi +import net.vonforst.evmap.storage.AppDatabase import retrofit2.Call import retrofit2.Callback import retrofit2.HttpException @@ -23,9 +22,9 @@ import java.io.IOException data class MapPosition(val bounds: LatLngBounds, val zoom: Float) -class MapViewModel(geApiKey: String) : ViewModel() { - private var api: GoingElectricApi = - GoingElectricApi.create(geApiKey) +class MapViewModel(application: Application, geApiKey: String) : AndroidViewModel(application) { + private var api = GoingElectricApi.create(geApiKey) + private var db = AppDatabase.getInstance(application) val bottomSheetState: MutableLiveData by lazy { MutableLiveData() @@ -87,6 +86,18 @@ class MapViewModel(geApiKey: String) : ViewModel() { MutableLiveData() } + val favorites: LiveData> by lazy { + db.chargeLocationsDao().getAllChargeLocations() + } + + fun insertFavorite(charger: ChargeLocation) { + db.chargeLocationsDao().insert(charger) + } + + fun deleteFavorite(charger: ChargeLocation) { + db.chargeLocationsDao().delete(charger) + } + private fun loadChargepoints(mapPosition: MapPosition) { chargepoints.value = Resource.loading(chargepoints.value?.data) val bounds = mapPosition.bounds