Skip to content

Instantly share code, notes, and snippets.

@juliuscanute
Created December 8, 2019 10:10
Show Gist options
  • Select an option

  • Save juliuscanute/2d816dc833aba9bb4935b501b1f538b2 to your computer and use it in GitHub Desktop.

Select an option

Save juliuscanute/2d816dc833aba9bb4935b501b1f538b2 to your computer and use it in GitHub Desktop.
[Data Layer] #clean #architecture #android
package com.raywenderlich.android.creatures.cache
class CreatureCacheImpl @Inject constructor(private val creaturesDatabase: CreaturesDatabase,
private val entityMapper: CreatureEntityMapper,
private val preferencesHelper: PreferencesHelper) :
CreatureCache {
companion object {
private const val EXPIRATION_TIME = (60 * 10 * 1000).toLong() // 10 minutes
}
/**
* Retrieve an instance from the database, used for tests.
*/
internal fun getDatabase(): CreaturesDatabase {
return creaturesDatabase
}
/**
* Remove all the data from all the tables in the database.
*/
override fun clearCreatures(): Completable {
return Completable.defer {
creaturesDatabase.cachedCreatureDao().clearCreatures()
Completable.complete()
}
}
/**
* Save the given list of [CreatureEntity] instances to the database.
*/
override fun saveCreatures(creatures: List<CreatureEntity>): Completable {
return Completable.defer {
creatures.forEach {
creaturesDatabase.cachedCreatureDao().insertCreature(entityMapper.mapToCached(it))
}
Completable.complete()
}
}
/**
* Retrieve a list of [CreatureEntity] instances from the database.
*/
override fun getCreatures(): Flowable<List<CreatureEntity>> {
return Flowable.defer {
Flowable.just(creaturesDatabase.cachedCreatureDao().getCreatures())
}.map {
it.map { entityMapper.mapFromCached(it) }
}
}
/**
* Check whether there are instances of [CachedCreature] stored in the cache.
*/
override fun isCached(): Single<Boolean> {
return Single.defer {
Single.just(creaturesDatabase.cachedCreatureDao().getCreatures().isNotEmpty())
}
}
/**
* Set a point in time at when the cache was last updated.
*/
override fun setLastCacheTime(lastCache: Long) {
preferencesHelper.lastCacheTime = lastCache
}
/**
* Check whether the current cached data exceeds the defined [EXPIRATION_TIME] time.
*/
override fun isExpired(): Boolean {
val currentTime = System.currentTimeMillis()
val lastUpdateTime = this.getLastCacheUpdateTimeMillis()
return currentTime - lastUpdateTime > EXPIRATION_TIME
}
/**
* Get in millis, the last time the cache was accessed.
*/
private fun getLastCacheUpdateTimeMillis(): Long {
return preferencesHelper.lastCacheTime
}
}
package com.raywenderlich.android.creatures.data
class CreatureDataRepository @Inject constructor(private val factory: CreatureDataStoreFactory,
private val creatureMapper: CreatureMapper) : CreatureRepository {
override fun clearCreatures(): Completable {
return factory.retrieveCacheDataStore().clearCreatures()
}
override fun saveCreatures(creatures: List<Creature>): Completable {
val creatureEntities = mutableListOf<CreatureEntity>()
creatures.map { creatureEntities.add(creatureMapper.mapToEntity(it)) }
return factory.retrieveCacheDataStore().saveCreatures(creatureEntities)
}
override fun getCreatures(): Flowable<List<Creature>> {
return factory.retrieveCacheDataStore().isCached()
.flatMapPublisher {
factory.retrieveDataStore(it).getCreatures()
}
.flatMap {
Flowable.just(it.map { creatureMapper.mapFromEntity(it) })
}
.flatMap {
saveCreatures(it).toSingle { it }.toFlowable()
}
}
}
package com.raywenderlich.android.creatures.data.source
open class CreatureDataStoreFactory @Inject constructor(
private val creatureCache: CreatureCache,
private val creatureCacheDataStore: CreatureCacheDataStore,
private val creatureRemoteDataStore: CreatureRemoteDataStore) {
/**
* Returns a DataStore based on whether or not there is content in the cache and the cache
* has not expired
*/
open fun retrieveDataStore(isCached: Boolean): CreatureDataStore {
if (isCached && !creatureCache.isExpired()) {
return retrieveCacheDataStore()
}
return retrieveRemoteDataStore()
}
/**
* Return an instance of the Cache Data Store
*/
open fun retrieveCacheDataStore(): CreatureDataStore {
return creatureCacheDataStore
}
/**
* Return an instance of the Remote Data Store
*/
open fun retrieveRemoteDataStore(): CreatureDataStore {
return creatureRemoteDataStore
}
}
package com.raywenderlich.android.creatures.remote.mapper
open class CreatureEntityMapper @Inject constructor(): EntityMapper<CreatureModel, CreatureEntity> {
override fun mapFromRemote(type: CreatureModel) =
CreatureEntity(type.id, type.firstName, type.lastName, type.nickname, type.image, type.planet)
}
package com.raywenderlich.android.creatures.data.mapper
open class CreatureMapper @Inject constructor(): Mapper<CreatureEntity, Creature> {
override fun mapFromEntity(type: CreatureEntity) =
Creature(type.id, type.firstName, type.lastName, type.nickname, type.image, type.planet)
override fun mapToEntity(type: Creature) =
CreatureEntity(type.id, type.firstName, type.lastName, type.nickname, type.image, type.planet)
}
package com.raywenderlich.android.creatures.data.source
/**
* Implementation of the [CreatureDataStore] interface to provide a means of communicating
* with the remote data source
*/
open class CreatureRemoteDataStore @Inject constructor(private val creatureRemote: CreatureRemote) :
CreatureDataStore {
override fun clearCreatures(): Completable {
throw UnsupportedOperationException()
}
override fun saveCreatures(creatures: List<CreatureEntity>): Completable {
throw UnsupportedOperationException()
}
override fun getCreatures(): Flowable<List<CreatureEntity>> {
return creatureRemote.getCreatures()
}
override fun isCached(): Single<Boolean> {
throw UnsupportedOperationException()
}
}
package com.raywenderlich.android.creatures.remote
class CreatureRemoteImpl @Inject constructor(private val creatureService: CreatureService,
private val entityMapper: CreatureEntityMapper):
CreatureRemote {
/**
* Retrieve a list of [CreatureEntity] instances from the [CreatureService].
*/
override fun getCreatures(): Flowable<List<CreatureEntity>> {
return creatureService.getCreatures()
.map { it.creatures }
.map {
val entities = mutableListOf<CreatureEntity>()
it.forEach { entities.add(entityMapper.mapFromRemote(it)) }
entities
}
}
}
package com.raywenderlich.android.creatures.cache
@Database(entities = arrayOf(CachedCreature::class), version = 1)
abstract class CreaturesDatabase @Inject constructor() : RoomDatabase() {
abstract fun cachedCreatureDao(): CachedCreatureDao
private var instance: CreaturesDatabase? = null
private val lock = Any()
fun getInstance(context: Context): CreaturesDatabase {
if (instance == null) {
synchronized(lock) {
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext,
CreaturesDatabase::class.java, "creatures.db").build()
}
return instance!!
}
}
return instance!!
}
}
package com.raywenderlich.android.creatures.remote.mapper
/**
* Interface for model mappers. It provides helper methods that facilitate
* retrieving of models from outer data source layers
*
* @param <M> the remote model input type
* @param <E> the entity model output type
*/
interface EntityMapper<in M, out E> {
fun mapFromRemote(type: M): E
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment