Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement simple cache mechanism with LruCache #78

Merged
merged 3 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
.externalNativeBuild
.cxx
local.properties
/misc.xml
/.idea/appInsightsSettings.xml
/.idea/deploymentTargetSelector.xml
/.idea/runConfigurations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.developersbreach.composeactors.core.cache

object CacheKey {
fun forPerson(id: Int): String = "${PERSON}_$id"
fun forMovie(id: String): String = "${MOVIE}_$id"

private const val PERSON = "person_"
private const val MOVIE = "movie_"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.developersbreach.composeactors.core.cache

import android.util.LruCache
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class CacheManager @Inject constructor(
cacheSize: Int = 100
) {
private val cache = object : LruCache<String, Any>(cacheSize) {
override fun sizeOf(key: String, value: Any): Int {
return 1
}
}

@Synchronized
fun <T> get(key: String): T? {
return cache[key] as? T
}

@Synchronized
fun <T> put(key: String, value: T) {
cache.put(key, value)
}

@Synchronized
fun remove(key: String) {
cache.remove(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import com.developersbreach.composeactors.core.database.dao.FavoritePersonsDao
import com.developersbreach.composeactors.core.database.dao.FavoriteMoviesDao
import com.developersbreach.composeactors.core.database.dao.PersonDetailsDao
import com.developersbreach.composeactors.core.database.entity.FavoritePersonsEntity
import com.developersbreach.composeactors.core.database.entity.FavoriteMoviesEntity
import com.developersbreach.composeactors.core.database.entity.PersonDetailEntity


@Database(
entities = [FavoritePersonsEntity::class, FavoriteMoviesEntity::class],
version = 4,
entities = [FavoritePersonsEntity::class, FavoriteMoviesEntity::class, PersonDetailEntity::class],
version = 5,
exportSchema = false,
)
abstract class AppDatabase : RoomDatabase() {
abstract val favoritePersonsDao: FavoritePersonsDao
abstract val personDetailsDao: PersonDetailsDao
abstract val favoriteMoviesDao: FavoriteMoviesDao
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.developersbreach.composeactors.core.database.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.developersbreach.composeactors.core.database.entity.PersonDetailEntity

@Dao
interface PersonDetailsDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPersonDetail(personDetail: PersonDetailEntity)

@Query("SELECT * FROM person_detail_table WHERE column_person_detail_id = :personId")
suspend fun getPersonDetail(personId: Int): PersonDetailEntity?
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.developersbreach.composeactors.data.movie.model.Movie

@Entity(tableName = "favorite_movies_table")
data class FavoriteMoviesEntity(
Expand All @@ -22,3 +23,14 @@ data class FavoriteMoviesEntity(
@ColumnInfo(name = "column_movie_banner")
val movieBanner: String?
)

fun List<FavoriteMoviesEntity>.movieAsDomainModel(): List<Movie> {
return map {
Movie(
movieId = it.movieId,
movieName = it.movieName,
posterPath = it.moviePosterUrl,
backdropPath = it.movieBanner
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.developersbreach.composeactors.data.person.model.FavoritePerson

@Entity(tableName = "favorite_persons_table")
data class FavoritePersonsEntity(
Expand All @@ -22,3 +23,14 @@ data class FavoritePersonsEntity(
@ColumnInfo(name = "column_person_placeOfBirth")
val personPlaceOfBirth: String?
)

fun List<FavoritePersonsEntity>.toFavoritePersons(): List<FavoritePerson> {
return map {
FavoritePerson(
personId = it.personId,
personName = it.personName,
profileUrl = it.personProfileUrl,
placeOfBirth = it.personPlaceOfBirth
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.developersbreach.composeactors.core.database.entity

import androidx.compose.runtime.Stable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.developersbreach.composeactors.data.person.model.PersonDetail

@Entity(tableName = "person_detail_table")
data class PersonDetailEntity(

@Stable
@PrimaryKey
@ColumnInfo(name = "column_person_detail_id")
val personId: Int,

@ColumnInfo(name = "column_person_detail_name")
val personName: String,

@ColumnInfo(name = "column_person_detail_profile_path")
val profilePath: String?,

@ColumnInfo(name = "column_person_detail_biography")
val biography: String,

@ColumnInfo(name = "column_person_detail_date_of_birth")
val dateOfBirth: String?,

@ColumnInfo(name = "column_person_detail_place_of_birth")
val placeOfBirth: String?,

@ColumnInfo(name = "column_person_detail_popularity")
val popularity: Double
)

fun PersonDetailEntity.toPersonDetail(): PersonDetail {
return PersonDetail(
personId = personId,
personName = personName,
profilePath = profilePath,
biography = biography,
dateOfBirth = dateOfBirth,
placeOfBirth = placeOfBirth,
popularity = popularity
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package com.developersbreach.composeactors.data.datasource.database

import androidx.lifecycle.LiveData
import androidx.lifecycle.map
import arrow.core.Either
import com.developersbreach.composeactors.core.database.AppDatabase
import com.developersbreach.composeactors.core.database.entity.PersonDetailEntity
import com.developersbreach.composeactors.core.database.entity.movieAsDomainModel
import com.developersbreach.composeactors.core.database.entity.toFavoritePersons
import com.developersbreach.composeactors.data.person.model.FavoritePerson
import com.developersbreach.composeactors.data.movie.model.Movie
import com.developersbreach.composeactors.data.person.model.personAsDatabaseModel
import com.developersbreach.composeactors.data.person.model.personAsDomainModel
import com.developersbreach.composeactors.data.person.model.FavoritePersonsEntity
import com.developersbreach.composeactors.data.movie.model.movieAsDatabaseModel
import com.developersbreach.composeactors.data.movie.model.movieAsDomainModel
import com.developersbreach.composeactors.data.person.model.PersonDetail
import com.developersbreach.composeactors.data.person.model.toEntity
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
Expand All @@ -29,7 +33,7 @@ class DatabaseDataSource @Inject constructor(
fun getAllFavoritePersons(): LiveData<List<FavoritePerson>> {
val allFavoritePersons = database.favoritePersonsDao.getAllFavoritePersons()
return allFavoritePersons.map { favEntityList ->
favEntityList.personAsDomainModel()
favEntityList.toFavoritePersons()
}
}

Expand All @@ -52,7 +56,7 @@ class DatabaseDataSource @Inject constructor(
suspend fun addPersonToFavorites(
favoritePerson: FavoritePerson
) = withContext(Dispatchers.IO) {
with(favoritePerson.personAsDatabaseModel()) {
with(favoritePerson.FavoritePersonsEntity()) {
database.favoritePersonsDao.addPersonToFavorites(favoritePersonsEntity = this)
}
}
Expand All @@ -68,8 +72,22 @@ class DatabaseDataSource @Inject constructor(
suspend fun deleteSelectedFavoritePerson(
favoritePerson: FavoritePerson
) = withContext(Dispatchers.IO) {
with(favoritePerson.personAsDatabaseModel()) {
with(favoritePerson.FavoritePersonsEntity()) {
database.favoritePersonsDao.deleteSelectedFavoritePerson(favoritePersonsEntity = this)
}
}

suspend fun addPersonDetail(
personDetail: PersonDetail
) {
database.personDetailsDao.insertPersonDetail(personDetail.toEntity())
}

suspend fun getPersonDetail(
personId: Int
): Either<Throwable, PersonDetailEntity?> {
return Either.catch {
database.personDetailsDao.getPersonDetail(personId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,4 @@ fun Movie.movieAsDatabaseModel(): FavoriteMoviesEntity {
moviePosterUrl = this.posterPathUrl,
movieBanner = this.bannerUrl
)
}

fun List<FavoriteMoviesEntity>.movieAsDomainModel(): List<Movie> {
return map {
Movie(
movieId = it.movieId,
movieName = it.movieName,
posterPath = it.moviePosterUrl,
backdropPath = it.movieBanner
)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package com.developersbreach.composeactors.data.person.model

import androidx.compose.runtime.Stable
import com.developersbreach.composeactors.core.database.entity.FavoritePersonsEntity

data class FavoritePerson(
@Stable val personId: Int,
val personName: String,
val profileUrl: String,
val placeOfBirth: String?
)
)

fun FavoritePerson.FavoritePersonsEntity(): FavoritePersonsEntity {
return FavoritePersonsEntity(
personId = this.personId,
personName = this.personName,
personProfileUrl = this.profileUrl,
personPlaceOfBirth = this.placeOfBirth
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.developersbreach.composeactors.data.person.model

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.developersbreach.composeactors.core.database.entity.FavoritePersonsEntity
import com.developersbreach.composeactors.core.database.entity.PersonDetailEntity
import com.developersbreach.composeactors.core.network.HIGH_RES_IMAGE
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand All @@ -28,22 +28,14 @@ fun PersonDetail.toFavoritePerson() = FavoritePerson(
placeOfBirth = this.placeOfBirth
)

fun FavoritePerson.personAsDatabaseModel(): FavoritePersonsEntity {
return FavoritePersonsEntity(
personId = this.personId,
personName = this.personName,
personProfileUrl = this.profileUrl,
personPlaceOfBirth = this.placeOfBirth
fun PersonDetail.toEntity(): PersonDetailEntity {
return PersonDetailEntity(
personId = personId,
personName = personName,
profilePath = profileUrl,
biography = biography,
dateOfBirth = dateOfBirth,
placeOfBirth = placeOfBirth,
popularity = popularity
)
}

fun List<FavoritePersonsEntity>.personAsDomainModel(): List<FavoritePerson> {
return map {
FavoritePerson(
personId = it.personId,
personName = it.personName,
profileUrl = it.personProfileUrl,
placeOfBirth = it.personPlaceOfBirth
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.developersbreach.composeactors.data.person.repository

import androidx.lifecycle.LiveData
import arrow.core.Either
import com.developersbreach.composeactors.core.cache.CacheKey
import com.developersbreach.composeactors.core.cache.CacheManager
import com.developersbreach.composeactors.core.database.entity.toPersonDetail
import com.developersbreach.composeactors.data.datasource.database.DatabaseDataSource
import com.developersbreach.composeactors.data.person.model.Person
import com.developersbreach.composeactors.data.person.model.PersonDetail
Expand All @@ -16,7 +19,8 @@ import kotlinx.coroutines.withContext
@Singleton
class PersonRepositoryImpl @Inject constructor(
private val personApi: PersonApi,
private val databaseDataSource: DatabaseDataSource
private val databaseDataSource: DatabaseDataSource,
private val cacheManager: CacheManager,
) : PersonRepository {

override suspend fun getPopularPersons(): Either<Throwable, List<Person>> = withContext(Dispatchers.IO) {
Expand All @@ -34,7 +38,25 @@ class PersonRepositoryImpl @Inject constructor(
override suspend fun getPersonDetails(
personId: Int
): Either<Throwable, PersonDetail> = withContext(Dispatchers.IO) {
personApi.getPersonDetails(personId)
val cacheKey = CacheKey.forPerson(personId)
val cachedData = cacheManager.get<PersonDetail>(cacheKey)
if (cachedData != null) {
return@withContext Either.Right(cachedData)
}

val dbData = databaseDataSource.getPersonDetail(personId)
dbData.map {
if (it != null) {
cacheManager.put(cacheKey, it.toPersonDetail())
return@withContext Either.Right(it.toPersonDetail())
}
}

personApi.getPersonDetails(personId).map {
cacheManager.put(cacheKey, it)
databaseDataSource.addPersonDetail(it)
it
}
}

override suspend fun getCastDetails(
Expand Down
Loading
Loading