Skip to content

Commit fef4395

Browse files
committed
refactor: fix icon cache
1 parent 8b6ac77 commit fef4395

File tree

6 files changed

+38
-42
lines changed

6 files changed

+38
-42
lines changed

app/src/main/java/com/klee/sapio/data/local/EvaluationDao.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ interface IconDao {
5353
"""
5454
SELECT * FROM IconEntity
5555
WHERE name = :iconName
56-
AND cachedAt >= :minCachedAt
5756
ORDER BY id DESC
5857
"""
5958
)
60-
suspend fun findByName(iconName: String, minCachedAt: Long): List<IconEntity>
59+
suspend fun findByName(iconName: String): List<IconEntity>
6160

6261
@Insert(onConflict = OnConflictStrategy.REPLACE)
6362
suspend fun upsertAll(items: List<IconEntity>)

app/src/main/java/com/klee/sapio/data/repository/EvaluationRepositoryImpl.kt

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ class EvaluationRepositoryImpl @Inject constructor(
3030

3131
companion object {
3232
private const val PAGE_SIZE = 10
33-
private const val ICON_TTL_MS = 24 * 60 * 60 * 1000L
3433
}
3534

3635
override suspend fun listLatestEvaluations(pageNumber: Int): Result<List<DomainEvaluation>> {
@@ -126,8 +125,7 @@ class EvaluationRepositoryImpl @Inject constructor(
126125
return Result.success(icons.map { it.toDomain() })
127126
}
128127

129-
val minCachedAt = System.currentTimeMillis() - ICON_TTL_MS
130-
val cached = iconDao.findByName("${app.packageName}.png", minCachedAt)
128+
val cached = iconDao.findByName("${app.packageName}.png")
131129
.map { it.toDomain() }
132130
return if (cached.isNotEmpty()) {
133131
Result.success(cached)
@@ -137,27 +135,21 @@ class EvaluationRepositoryImpl @Inject constructor(
137135
}
138136

139137
override suspend fun existingIcon(iconName: String): Result<List<DomainIcon>> {
140-
val minCachedAt = System.currentTimeMillis() - ICON_TTL_MS
141-
val cached = iconDao.findByName(iconName, minCachedAt)
138+
val remote = retrofitService.existingIcon(iconName)
139+
if (remote.isSuccess) {
140+
val icons = remote.getOrThrow()
141+
val now = System.currentTimeMillis()
142+
iconDao.upsertAll(icons.map { it.toEntity(now) })
143+
return Result.success(icons.map { it.toDomain() })
144+
}
145+
146+
val cached = iconDao.findByName(iconName)
142147
.map { it.toDomain() }
143-
val remote = if (cached.isEmpty()) {
144-
retrofitService.existingIcon(iconName)
148+
return if (cached.isNotEmpty()) {
149+
Result.success(cached)
145150
} else {
146-
null
147-
}
148-
val result = when {
149-
cached.isNotEmpty() -> Result.success(cached)
150-
remote?.isSuccess == true -> {
151-
val icons = remote.getOrThrow()
152-
val now = System.currentTimeMillis()
153-
iconDao.upsertAll(icons.map { it.toEntity(now) })
154-
Result.success(icons.map { it.toDomain() })
155-
}
156-
else -> Result.failure(
157-
remote?.exceptionOrNull() ?: IllegalStateException("Failed to load icon")
158-
)
151+
Result.failure(remote.exceptionOrNull() ?: IllegalStateException("Failed to load icon"))
159152
}
160-
return result
161153
}
162154

163155
override suspend fun deleteIcon(id: Int): Result<Unit> {

app/src/main/java/com/klee/sapio/ui/view/FeedAppAdapter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.recyclerview.widget.DiffUtil
1010
import androidx.recyclerview.widget.ListAdapter
1111
import androidx.recyclerview.widget.RecyclerView
1212
import com.bumptech.glide.Glide
13+
import com.bumptech.glide.load.engine.DiskCacheStrategy
1314
import com.klee.sapio.R
1415
import com.klee.sapio.data.api.EvaluationService
1516
import com.klee.sapio.data.system.Settings
@@ -100,6 +101,7 @@ class FeedAppAdapter(
100101
if (icons.isNotEmpty()) {
101102
Glide.with(mContext.applicationContext)
102103
.load(EvaluationService.BASE_URL + icons[0].url)
104+
.diskCacheStrategy(DiskCacheStrategy.ALL)
103105
.into(holder.binding.image)
104106
}
105107
}

app/src/main/java/com/klee/sapio/ui/view/SearchAppAdapter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.recyclerview.widget.DiffUtil
88
import androidx.recyclerview.widget.ListAdapter
99
import androidx.recyclerview.widget.RecyclerView
1010
import com.bumptech.glide.Glide
11+
import com.bumptech.glide.load.engine.DiskCacheStrategy
1112
import com.klee.sapio.data.api.EvaluationService
1213
import com.klee.sapio.databinding.SearchAppCardBinding
1314
import com.klee.sapio.domain.EvaluationRepository
@@ -67,6 +68,7 @@ class SearchAppAdapter(
6768
if (icons.isNotEmpty()) {
6869
Glide.with(mContext.applicationContext)
6970
.load(EvaluationService.BASE_URL + icons[0].url)
71+
.diskCacheStrategy(DiskCacheStrategy.ALL)
7072
.into(holder.binding.image)
7173
}
7274
}

app/src/test/java/com/klee/sapio/data/local/EvaluationDaoTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class EvaluationDaoTest {
134134
}
135135

136136
@Test
137-
fun findIconByName_filtersByCachedAt() = runTest {
137+
fun findIconByName_returnsAllMatchingName() = runTest {
138138
val expired = IconEntity(
139139
id = 1,
140140
name = "com.app.png",
@@ -149,9 +149,9 @@ class EvaluationDaoTest {
149149
)
150150
iconDao.upsertAll(listOf(expired, fresh))
151151

152-
val results = iconDao.findByName("com.app.png", minCachedAt = 100)
152+
val results = iconDao.findByName("com.app.png")
153153

154-
assertEquals(1, results.size)
155-
assertTrue(results.first().url.contains("fresh"))
154+
assertEquals(2, results.size)
155+
assertTrue(results.any { it.url.contains("fresh") })
156156
}
157157
}

app/src/test/java/com/klee/sapio/data/repository/EvaluationRepositoryImplTest.kt

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import org.junit.Assert.assertTrue
1717
import org.junit.Before
1818
import org.junit.Test
1919
import org.junit.runner.RunWith
20-
import org.mockito.ArgumentMatchers.anyString
2120
import org.mockito.Mockito
2221
import org.robolectric.RobolectricTestRunner
2322
import org.robolectric.RuntimeEnvironment
@@ -101,7 +100,7 @@ class EvaluationRepositoryImplTest {
101100
}
102101

103102
@Test
104-
fun existingIcon_returnsCacheWithoutNetworkWhenFresh() = runTest {
103+
fun existingIcon_networkFirstCachesWhenSuccessful() = runTest {
105104
val now = System.currentTimeMillis()
106105
iconDao.upsertAll(
107106
listOf(
@@ -112,39 +111,41 @@ class EvaluationRepositoryImplTest {
112111
).toEntity(now)
113112
)
114113
)
114+
val remote = listOf(
115+
iconAnswer(
116+
id = 13,
117+
name = "com.app.one.png",
118+
url = "/remote.png"
119+
)
120+
)
121+
Mockito.`when`(evaluationService.existingIcon("com.app.one.png"))
122+
.thenReturn(Result.success(remote))
115123

116124
val result = repository.existingIcon("com.app.one.png")
117125

118126
assertTrue(result.isSuccess)
119-
Mockito.verify(evaluationService, Mockito.never()).existingIcon(anyString())
127+
assertEquals("/remote.png", result.getOrThrow().first().url)
120128
}
121129

122130
@Test
123-
fun existingIcon_fetchesRemoteWhenCacheExpired() = runTest {
124-
val expired = System.currentTimeMillis() - (24 * 60 * 60 * 1000L) - 1
131+
fun existingIcon_fallsBackToCacheOnFailure() = runTest {
132+
val cachedAt = System.currentTimeMillis() - 1000
125133
iconDao.upsertAll(
126134
listOf(
127135
iconAnswer(
128136
id = 22,
129137
name = "com.app.two.png",
130138
url = "/old.png"
131-
).toEntity(expired)
132-
)
133-
)
134-
val remote = listOf(
135-
iconAnswer(
136-
id = 23,
137-
name = "com.app.two.png",
138-
url = "/new.png"
139+
).toEntity(cachedAt)
139140
)
140141
)
141142
Mockito.`when`(evaluationService.existingIcon("com.app.two.png"))
142-
.thenReturn(Result.success(remote))
143+
.thenReturn(Result.failure(IllegalStateException("Network error")))
143144

144145
val result = repository.existingIcon("com.app.two.png")
145146

146147
assertTrue(result.isSuccess)
147-
assertEquals("/new.png", result.getOrThrow().first().url)
148+
assertEquals("/old.png", result.getOrThrow().first().url)
148149
}
149150

150151
private fun iconAnswer(id: Int, name: String, url: String): IconAnswer {

0 commit comments

Comments
 (0)