Cache ImageVectors to Improve App Performance

This commit is contained in:
shabinder 2021-09-25 21:50:59 +05:30
parent e4c22c5d0c
commit dbaaa0816f
13 changed files with 113 additions and 42 deletions

View File

@ -40,7 +40,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name=".App"

View File

@ -5,9 +5,13 @@ plugins {
kotlin {
sourceSets {
all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.useExperimentalAnnotation("androidx.compose.animation")
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
languageSettings.apply {
useExperimentalAnnotation("kotlin.RequiresOptIn")
useExperimentalAnnotation("kotlin.Experimental")
useExperimentalAnnotation("kotlin.time.ExperimentalTime")
useExperimentalAnnotation("androidx.compose.animation")
useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
}
}
}
}

View File

@ -23,7 +23,9 @@ actual fun ImageLoad(
modifier: Modifier
// placeholder: ImageVector
) {
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
var pic by remember(link) {
mutableStateOf<ImageBitmap?>(null)
}
LaunchedEffect(link) {
withContext(dispatcherIO) {

View File

@ -21,15 +21,23 @@ package com.shabinder.common.uikit
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import com.shabinder.common.database.R
import com.shabinder.common.translations.Strings
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
internal actual fun <T> imageVectorResource(id: T): ImageVector =
ImageVector.Companion.vectorResource(id as Int)
@Composable
actual fun DownloadImageTick() {
Image(
painterResource(R.drawable.ic_tick),
getCachedPainter(R.drawable.ic_tick),
Strings.downloadDone()
)
}
@ -37,7 +45,7 @@ actual fun DownloadImageTick() {
@Composable
actual fun DownloadImageError(modifier: Modifier) {
Image(
painterResource(R.drawable.ic_error),
getCachedPainter(R.drawable.ic_error),
Strings.downloadError(),
modifier = modifier
)
@ -46,44 +54,44 @@ actual fun DownloadImageError(modifier: Modifier) {
@Composable
actual fun DownloadImageArrow(modifier: Modifier) {
Image(
painterResource(R.drawable.ic_arrow),
getCachedPainter(R.drawable.ic_arrow),
Strings.downloadStart(),
modifier
)
}
@Composable
actual fun DownloadAllImage() = painterResource(R.drawable.ic_download_arrow)
actual fun DownloadAllImage() = getCachedPainter(R.drawable.ic_download_arrow)
@Composable
actual fun ShareImage() = painterResource(R.drawable.ic_share_open)
actual fun ShareImage() = getCachedPainter(R.drawable.ic_share_open)
@Composable
actual fun PlaceHolderImage() = painterResource(R.drawable.ic_song_placeholder)
actual fun PlaceHolderImage() = getCachedPainter(R.drawable.ic_song_placeholder)
@Composable
actual fun SpotiFlyerLogo() = painterResource(R.drawable.ic_spotiflyer_logo)
actual fun SpotiFlyerLogo() = getCachedPainter(R.drawable.ic_spotiflyer_logo)
@Composable
actual fun HeartIcon() = painterResource(R.drawable.ic_heart)
@Composable
actual fun SpotifyLogo() = painterResource(R.drawable.ic_spotify_logo)
actual fun SpotifyLogo() = getCachedPainter(R.drawable.ic_spotify_logo)
@Composable
actual fun SaavnLogo() = painterResource(R.drawable.ic_jio_saavn_logo)
actual fun SaavnLogo() = getCachedPainter(R.drawable.ic_jio_saavn_logo)
@Composable
actual fun GaanaLogo() = painterResource(R.drawable.ic_gaana)
actual fun GaanaLogo() = getCachedPainter(R.drawable.ic_gaana)
@Composable
actual fun YoutubeLogo() = painterResource(R.drawable.ic_youtube)
actual fun YoutubeLogo() = getCachedPainter(R.drawable.ic_youtube)
@Composable
actual fun YoutubeMusicLogo() = painterResource(R.drawable.ic_youtube_music_logo)
actual fun YoutubeMusicLogo() = getCachedPainter(R.drawable.ic_youtube_music_logo)
@Composable
actual fun GithubLogo() = painterResource(R.drawable.ic_github)
actual fun GithubLogo() = getCachedPainter(R.drawable.ic_github)
@Composable
actual fun PaypalLogo() = painterResource(R.drawable.ic_paypal_logo)
@ -100,4 +108,4 @@ actual fun Toast(
duration: ToastDuration
) {
// We Have Android's Implementation of Toast so its just Empty
}
}

View File

@ -15,11 +15,27 @@
*/
@file:Suppress("FunctionName")
package com.shabinder.common.uikit
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import com.shabinder.common.caching.Cache
private val ImageCache = Cache.Builder.newBuilder()
.maximumCacheSize(15).build<Any, ImageVector>()
@Composable
internal expect fun <T> imageVectorResource(id: T): ImageVector
@Composable
fun <K : Any> getCachedPainter(key: K): Painter {
return rememberVectorPainter(
ImageCache.get(key) ?: imageVectorResource(key).also { ImageCache.put(key, it) })
}
@Composable
expect fun DownloadImageTick()

View File

@ -15,19 +15,26 @@
*/
@file:Suppress("FunctionName")
package com.shabinder.common.uikit
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.loadXmlImageVector
import androidx.compose.ui.res.vectorXmlResource
@Composable
internal actual fun <T> imageVectorResource(id: T): ImageVector =
vectorXmlResource(id as String)
@Composable
actual fun DownloadImageTick() {
Image(
vectorXmlResource("drawable/ic_tick.xml"),
getCachedPainter("drawable/ic_tick.xml"),
"Downloaded"
)
}
@ -35,7 +42,7 @@ actual fun DownloadImageTick() {
@Composable
actual fun DownloadImageError(modifier: Modifier) {
Image(
vectorXmlResource("drawable/ic_error.xml"),
getCachedPainter("drawable/ic_error.xml"),
"Can't Download",
modifier = modifier
)
@ -44,51 +51,61 @@ actual fun DownloadImageError(modifier: Modifier) {
@Composable
actual fun DownloadImageArrow(modifier: Modifier) {
Image(
vectorXmlResource("drawable/ic_arrow.xml"),
getCachedPainter("drawable/ic_arrow.xml"),
"Download",
modifier
)
}
@Composable
actual fun DownloadAllImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_download_arrow.xml")) as Painter
actual fun DownloadAllImage() = getCachedPainter("drawable/ic_download_arrow.xml")
@Composable
actual fun ShareImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_share_open.xml")) as Painter
actual fun ShareImage() = getCachedPainter("drawable/ic_share_open.xml")
@Composable
actual fun PlaceHolderImage() = rememberVectorPainter(vectorXmlResource("drawable/music.xml"))
as Painter
@Composable
actual fun SpotiFlyerLogo() =
rememberVectorPainter(vectorXmlResource("drawable/ic_spotiflyer_logo.xml")) as Painter
actual fun PlaceHolderImage() = getCachedPainter("drawable/music.xml")
@Composable
actual fun HeartIcon() = rememberVectorPainter(vectorXmlResource("drawable/ic_heart.xml")) as Painter
actual fun SpotiFlyerLogo() = getCachedPainter("drawable/ic_spotiflyer_logo.xml")
@Composable
actual fun SpotifyLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_spotify_logo.xml")) as Painter
actual fun HeartIcon() =
getCachedPainter("drawable/ic_heart.xml")
@Composable
actual fun SaavnLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_jio_saavn_logo.xml")) as Painter
actual fun SpotifyLogo() =
getCachedPainter("drawable/ic_spotify_logo.xml")
@Composable
actual fun YoutubeLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube.xml")) as Painter
actual fun SaavnLogo() =
getCachedPainter("drawable/ic_jio_saavn_logo.xml")
@Composable
actual fun GaanaLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_gaana.xml")) as Painter
actual fun YoutubeLogo() =
getCachedPainter("drawable/ic_youtube.xml")
@Composable
actual fun YoutubeMusicLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube_music_logo.xml")) as Painter
actual fun GaanaLogo() =
getCachedPainter("drawable/ic_gaana.xml")
@Composable
actual fun GithubLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_github.xml")) as Painter
actual fun YoutubeMusicLogo() =
getCachedPainter("drawable/ic_youtube_music_logo.xml")
@Composable
actual fun PaypalLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_paypal_logo.xml")) as Painter
actual fun GithubLogo() =
getCachedPainter("drawable/ic_github.xml")
@Composable
actual fun OpenCollectiveLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_opencollective_icon.xml")) as Painter
actual fun PaypalLogo() =
getCachedPainter("drawable/ic_paypal_logo.xml")
@Composable
actual fun RazorPay() = rememberVectorPainter(vectorXmlResource("drawable/ic_indian_rupee.xml")) as Painter
actual fun OpenCollectiveLogo() =
getCachedPainter("drawable/ic_opencollective_icon.xml")
@Composable
actual fun RazorPay() =
getCachedPainter("drawable/ic_indian_rupee.xml")

View File

@ -1,6 +1,7 @@
package com.shabinder.common.models
import android.content.SharedPreferences
import kotlinx.coroutines.CoroutineScope
actual interface PlatformActions {
@ -26,3 +27,5 @@ internal actual val StubPlatformActions = object : PlatformActions {
override fun sendTracksToService(array: List<TrackDetails>) {}
}
actual fun <T> runBlocking(block: suspend CoroutineScope.() -> T): T = kotlinx.coroutines.runBlocking { block() }

View File

@ -1,6 +1,7 @@
package com.shabinder.common.caching
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
/**
@ -23,6 +24,8 @@ public interface Cache<in Key : Any, Value : Any> {
*/
public suspend fun get(key: Key, loader: suspend () -> Value): Value
public fun getBlocking(key: Key, loader: suspend () -> Value): Value
/**
* Associates [value] with [key] in this cache. If the cache previously contained a
* value associated with [key], the old value is replaced by [value].

View File

@ -4,6 +4,7 @@ import co.touchlab.stately.collections.IsoMutableMap
import co.touchlab.stately.collections.IsoMutableSet
import co.touchlab.stately.concurrency.AtomicReference
import co.touchlab.stately.concurrency.value
import com.shabinder.common.models.runBlocking
import kotlin.time.Duration
import kotlin.time.TimeMark
import kotlin.time.TimeSource
@ -110,6 +111,12 @@ internal class RealCache<Key : Any, Value : Any>(
}
}
override fun getBlocking(key: Key, loader: suspend () -> Value): Value =
runBlocking {
get(key, loader)
}
override fun put(key: Key, value: Value) {
expireEntries()
@ -185,7 +192,7 @@ internal class RealCache<Key : Any, Value : Any>(
*/
private fun CacheEntry<Key, Value>.isExpired(): Boolean {
return expiresAfterAccess && (accessTimeMark.get() + expireAfterAccessDuration).hasPassedNow() ||
expiresAfterWrite && (writeTimeMark.get() + expireAfterWriteDuration).hasPassedNow()
expiresAfterWrite && (writeTimeMark.get() + expireAfterWriteDuration).hasPassedNow()
}
/**

View File

@ -1,5 +1,9 @@
package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
expect interface PlatformActions
internal expect val StubPlatformActions: PlatformActions
expect fun <T> runBlocking(block: suspend CoroutineScope.() -> T): T

View File

@ -7,7 +7,6 @@ import kotlin.contracts.contract
fun <T : Any?> T?.requireNotNull(): T = requireNotNull(this)
@OptIn(ExperimentalContracts::class)
inline fun buildString(track: TrackDetails, builderAction: StringBuilder.() -> Unit): String {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }

View File

@ -1,5 +1,9 @@
package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
actual interface PlatformActions
internal actual val StubPlatformActions = object : PlatformActions {}
actual fun <T> runBlocking(block: suspend CoroutineScope.() -> T): T = kotlinx.coroutines.runBlocking { block() }

View File

@ -1,5 +1,10 @@
package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
actual interface PlatformActions
internal actual val StubPlatformActions = object : PlatformActions {}
internal actual val StubPlatformActions = object : PlatformActions {}
actual fun <T> runBlocking(block: suspend CoroutineScope.() -> T): dynamic = GlobalScope.promise(block = block)