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.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application <application
android:name=".App" android:name=".App"

View File

@ -5,9 +5,13 @@ plugins {
kotlin { kotlin {
sourceSets { sourceSets {
all { all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") languageSettings.apply {
languageSettings.useExperimentalAnnotation("androidx.compose.animation") useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi") 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 modifier: Modifier
// placeholder: ImageVector // placeholder: ImageVector
) { ) {
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) } var pic by remember(link) {
mutableStateOf<ImageBitmap?>(null)
}
LaunchedEffect(link) { LaunchedEffect(link) {
withContext(dispatcherIO) { withContext(dispatcherIO) {

View File

@ -21,15 +21,23 @@ package com.shabinder.common.uikit
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.painterResource
import androidx.compose.ui.res.vectorResource
import com.shabinder.common.database.R import com.shabinder.common.database.R
import com.shabinder.common.translations.Strings import com.shabinder.common.translations.Strings
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@Composable
internal actual fun <T> imageVectorResource(id: T): ImageVector =
ImageVector.Companion.vectorResource(id as Int)
@Composable @Composable
actual fun DownloadImageTick() { actual fun DownloadImageTick() {
Image( Image(
painterResource(R.drawable.ic_tick), getCachedPainter(R.drawable.ic_tick),
Strings.downloadDone() Strings.downloadDone()
) )
} }
@ -37,7 +45,7 @@ actual fun DownloadImageTick() {
@Composable @Composable
actual fun DownloadImageError(modifier: Modifier) { actual fun DownloadImageError(modifier: Modifier) {
Image( Image(
painterResource(R.drawable.ic_error), getCachedPainter(R.drawable.ic_error),
Strings.downloadError(), Strings.downloadError(),
modifier = modifier modifier = modifier
) )
@ -46,44 +54,44 @@ actual fun DownloadImageError(modifier: Modifier) {
@Composable @Composable
actual fun DownloadImageArrow(modifier: Modifier) { actual fun DownloadImageArrow(modifier: Modifier) {
Image( Image(
painterResource(R.drawable.ic_arrow), getCachedPainter(R.drawable.ic_arrow),
Strings.downloadStart(), Strings.downloadStart(),
modifier modifier
) )
} }
@Composable @Composable
actual fun DownloadAllImage() = painterResource(R.drawable.ic_download_arrow) actual fun DownloadAllImage() = getCachedPainter(R.drawable.ic_download_arrow)
@Composable @Composable
actual fun ShareImage() = painterResource(R.drawable.ic_share_open) actual fun ShareImage() = getCachedPainter(R.drawable.ic_share_open)
@Composable @Composable
actual fun PlaceHolderImage() = painterResource(R.drawable.ic_song_placeholder) actual fun PlaceHolderImage() = getCachedPainter(R.drawable.ic_song_placeholder)
@Composable @Composable
actual fun SpotiFlyerLogo() = painterResource(R.drawable.ic_spotiflyer_logo) actual fun SpotiFlyerLogo() = getCachedPainter(R.drawable.ic_spotiflyer_logo)
@Composable @Composable
actual fun HeartIcon() = painterResource(R.drawable.ic_heart) actual fun HeartIcon() = painterResource(R.drawable.ic_heart)
@Composable @Composable
actual fun SpotifyLogo() = painterResource(R.drawable.ic_spotify_logo) actual fun SpotifyLogo() = getCachedPainter(R.drawable.ic_spotify_logo)
@Composable @Composable
actual fun SaavnLogo() = painterResource(R.drawable.ic_jio_saavn_logo) actual fun SaavnLogo() = getCachedPainter(R.drawable.ic_jio_saavn_logo)
@Composable @Composable
actual fun GaanaLogo() = painterResource(R.drawable.ic_gaana) actual fun GaanaLogo() = getCachedPainter(R.drawable.ic_gaana)
@Composable @Composable
actual fun YoutubeLogo() = painterResource(R.drawable.ic_youtube) actual fun YoutubeLogo() = getCachedPainter(R.drawable.ic_youtube)
@Composable @Composable
actual fun YoutubeMusicLogo() = painterResource(R.drawable.ic_youtube_music_logo) actual fun YoutubeMusicLogo() = getCachedPainter(R.drawable.ic_youtube_music_logo)
@Composable @Composable
actual fun GithubLogo() = painterResource(R.drawable.ic_github) actual fun GithubLogo() = getCachedPainter(R.drawable.ic_github)
@Composable @Composable
actual fun PaypalLogo() = painterResource(R.drawable.ic_paypal_logo) actual fun PaypalLogo() = painterResource(R.drawable.ic_paypal_logo)

View File

@ -15,11 +15,27 @@
*/ */
@file:Suppress("FunctionName") @file:Suppress("FunctionName")
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter 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 @Composable
expect fun DownloadImageTick() expect fun DownloadImageTick()

View File

@ -15,19 +15,26 @@
*/ */
@file:Suppress("FunctionName") @file:Suppress("FunctionName")
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter 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.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.loadXmlImageVector
import androidx.compose.ui.res.vectorXmlResource import androidx.compose.ui.res.vectorXmlResource
@Composable
internal actual fun <T> imageVectorResource(id: T): ImageVector =
vectorXmlResource(id as String)
@Composable @Composable
actual fun DownloadImageTick() { actual fun DownloadImageTick() {
Image( Image(
vectorXmlResource("drawable/ic_tick.xml"), getCachedPainter("drawable/ic_tick.xml"),
"Downloaded" "Downloaded"
) )
} }
@ -35,7 +42,7 @@ actual fun DownloadImageTick() {
@Composable @Composable
actual fun DownloadImageError(modifier: Modifier) { actual fun DownloadImageError(modifier: Modifier) {
Image( Image(
vectorXmlResource("drawable/ic_error.xml"), getCachedPainter("drawable/ic_error.xml"),
"Can't Download", "Can't Download",
modifier = modifier modifier = modifier
) )
@ -44,51 +51,61 @@ actual fun DownloadImageError(modifier: Modifier) {
@Composable @Composable
actual fun DownloadImageArrow(modifier: Modifier) { actual fun DownloadImageArrow(modifier: Modifier) {
Image( Image(
vectorXmlResource("drawable/ic_arrow.xml"), getCachedPainter("drawable/ic_arrow.xml"),
"Download", "Download",
modifier modifier
) )
} }
@Composable @Composable
actual fun DownloadAllImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_download_arrow.xml")) as Painter actual fun DownloadAllImage() = getCachedPainter("drawable/ic_download_arrow.xml")
@Composable @Composable
actual fun ShareImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_share_open.xml")) as Painter actual fun ShareImage() = getCachedPainter("drawable/ic_share_open.xml")
@Composable @Composable
actual fun PlaceHolderImage() = rememberVectorPainter(vectorXmlResource("drawable/music.xml")) actual fun PlaceHolderImage() = getCachedPainter("drawable/music.xml")
as Painter
@Composable
actual fun SpotiFlyerLogo() =
rememberVectorPainter(vectorXmlResource("drawable/ic_spotiflyer_logo.xml")) as Painter
@Composable @Composable
actual fun HeartIcon() = rememberVectorPainter(vectorXmlResource("drawable/ic_heart.xml")) as Painter actual fun SpotiFlyerLogo() = getCachedPainter("drawable/ic_spotiflyer_logo.xml")
@Composable @Composable
actual fun SpotifyLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_spotify_logo.xml")) as Painter actual fun HeartIcon() =
getCachedPainter("drawable/ic_heart.xml")
@Composable @Composable
actual fun SaavnLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_jio_saavn_logo.xml")) as Painter actual fun SpotifyLogo() =
getCachedPainter("drawable/ic_spotify_logo.xml")
@Composable @Composable
actual fun YoutubeLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube.xml")) as Painter actual fun SaavnLogo() =
getCachedPainter("drawable/ic_jio_saavn_logo.xml")
@Composable @Composable
actual fun GaanaLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_gaana.xml")) as Painter actual fun YoutubeLogo() =
getCachedPainter("drawable/ic_youtube.xml")
@Composable @Composable
actual fun YoutubeMusicLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube_music_logo.xml")) as Painter actual fun GaanaLogo() =
getCachedPainter("drawable/ic_gaana.xml")
@Composable @Composable
actual fun GithubLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_github.xml")) as Painter actual fun YoutubeMusicLogo() =
getCachedPainter("drawable/ic_youtube_music_logo.xml")
@Composable @Composable
actual fun PaypalLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_paypal_logo.xml")) as Painter actual fun GithubLogo() =
getCachedPainter("drawable/ic_github.xml")
@Composable @Composable
actual fun OpenCollectiveLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_opencollective_icon.xml")) as Painter actual fun PaypalLogo() =
getCachedPainter("drawable/ic_paypal_logo.xml")
@Composable @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 package com.shabinder.common.models
import android.content.SharedPreferences import android.content.SharedPreferences
import kotlinx.coroutines.CoroutineScope
actual interface PlatformActions { actual interface PlatformActions {
@ -26,3 +27,5 @@ internal actual val StubPlatformActions = object : PlatformActions {
override fun sendTracksToService(array: List<TrackDetails>) {} 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 package com.shabinder.common.caching
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource 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 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 * Associates [value] with [key] in this cache. If the cache previously contained a
* value associated with [key], the old value is replaced by [value]. * 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.collections.IsoMutableSet
import co.touchlab.stately.concurrency.AtomicReference import co.touchlab.stately.concurrency.AtomicReference
import co.touchlab.stately.concurrency.value import co.touchlab.stately.concurrency.value
import com.shabinder.common.models.runBlocking
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.TimeMark import kotlin.time.TimeMark
import kotlin.time.TimeSource 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) { override fun put(key: Key, value: Value) {
expireEntries() expireEntries()

View File

@ -1,5 +1,9 @@
package com.shabinder.common.models package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
expect interface PlatformActions expect interface PlatformActions
internal expect val StubPlatformActions: 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) fun <T : Any?> T?.requireNotNull(): T = requireNotNull(this)
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun buildString(track: TrackDetails, builderAction: StringBuilder.() -> Unit): String { inline fun buildString(track: TrackDetails, builderAction: StringBuilder.() -> Unit): String {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }

View File

@ -1,5 +1,9 @@
package com.shabinder.common.models package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
actual interface PlatformActions actual interface PlatformActions
internal actual val StubPlatformActions = object : 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 package com.shabinder.common.models
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
actual interface PlatformActions 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)