Last active
March 30, 2024 19:26
-
-
Save ElianFabian/8b6bcb0d2ed5dff1e30ea64d15f0fd76 to your computer and use it in GitHub Desktop.
Class to access resources inside ViewModels without worrying about Context.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.graphics.drawable.Drawable | |
import android.os.Build | |
import androidx.annotation.ArrayRes | |
import androidx.annotation.BoolRes | |
import androidx.annotation.ColorRes | |
import androidx.annotation.DimenRes | |
import androidx.annotation.DrawableRes | |
import androidx.annotation.IntegerRes | |
import androidx.annotation.PluralsRes | |
import androidx.annotation.RequiresApi | |
import androidx.annotation.StringRes | |
import kotlinx.coroutines.flow.StateFlow | |
/** | |
* This interface is used to provide resources to the application. | |
* | |
* It can safely be used in classes like ViewModels. | |
*/ | |
interface ResourceHelper { | |
fun getInt(@IntegerRes id: Int): Int | |
fun getBoolean(@BoolRes id: Int): Boolean | |
fun getColor(@ColorRes id: Int): Int | |
fun getDimension(@DimenRes id: Int): Float | |
fun getDimensionPixelOffset(@DimenRes id: Int): Int | |
fun getDimensionPixelSize(@DimenRes id: Int): Int | |
@RequiresApi(Build.VERSION_CODES.Q) | |
fun getFloat(@DimenRes id: Int): Float | |
fun getString(@StringRes id: Int): String | |
fun getQuantityString(@PluralsRes id: Int, quantity: Int): String | |
fun getText(@StringRes id: Int): CharSequence | |
fun getQuantityText(@PluralsRes id: Int, quantity: Int): CharSequence | |
fun getIntArray(@ArrayRes id: Int): IntArray | |
fun getStringArray(@ArrayRes id: Int): Array<String> | |
fun getTextArray(@ArrayRes id: Int): Array<CharSequence> | |
fun getDrawable(@DrawableRes id: Int): Drawable? | |
fun getIntStateFlow(@IntegerRes id: Int): StateFlow<Int> | |
fun getBooleanStateFlow(@BoolRes id: Int): StateFlow<Boolean> | |
fun getColorStateFlow(@ColorRes id: Int): StateFlow<Int> | |
fun getDimensionStateFlow(@DimenRes id: Int): StateFlow<Float> | |
fun getDimensionPixelOffsetStateFlow(@DimenRes id: Int): StateFlow<Int> | |
fun getDimensionPixelSizeStateFlow(@DimenRes id: Int): StateFlow<Int> | |
@RequiresApi(Build.VERSION_CODES.Q) | |
fun getFloatStateFlow(@DimenRes id: Int): StateFlow<Float> | |
fun getStringStateFlow(@StringRes id: Int): StateFlow<String> | |
fun getQuantityStringStateFlow(@PluralsRes id: Int, quantity: Int): StateFlow<String> | |
fun getTextStateFlow(@StringRes id: Int): StateFlow<CharSequence> | |
fun getQuantityTextStateFlow(@PluralsRes id: Int, quantity: Int): StateFlow<CharSequence> | |
fun getIntArrayStateFlow(@ArrayRes id: Int): StateFlow<IntArray> | |
fun getStringArrayStateFlow(@ArrayRes id: Int): StateFlow<Array<String>> | |
fun getTextArrayStateFlow(@ArrayRes id: Int): StateFlow<Array<CharSequence>> | |
fun getDrawableStateFlow(@DrawableRes id: Int): StateFlow<Drawable?> | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.ComponentCallbacks | |
import android.content.Context | |
import android.content.res.Configuration | |
import android.graphics.drawable.Drawable | |
import android.os.Build | |
import androidx.annotation.ArrayRes | |
import androidx.annotation.BoolRes | |
import androidx.annotation.ColorRes | |
import androidx.annotation.DimenRes | |
import androidx.annotation.DrawableRes | |
import androidx.annotation.IntegerRes | |
import androidx.annotation.PluralsRes | |
import androidx.annotation.RequiresApi | |
import androidx.annotation.StringRes | |
import androidx.core.content.ContextCompat | |
import kotlinx.coroutines.flow.MutableStateFlow | |
import kotlinx.coroutines.flow.StateFlow | |
class ResourceHelperImpl( | |
private val context: Context, | |
) : ResourceHelper { | |
private val _stateFlowByResource = mutableMapOf<TypedResource, MutableStateFlow<in Any?>>() | |
init { | |
context.registerComponentCallbacks(object : ComponentCallbacks { | |
override fun onConfigurationChanged(newConfig: Configuration) { | |
update() | |
} | |
override fun onLowMemory() {} | |
}) | |
} | |
override fun getInt(@IntegerRes id: Int): Int { | |
return context.resources.getInteger(id) | |
} | |
override fun getBoolean(@BoolRes id: Int): Boolean { | |
return context.resources.getBoolean(id) | |
} | |
override fun getColor(@ColorRes id: Int): Int { | |
return ContextCompat.getColor(context, id) | |
} | |
override fun getDimension(@DimenRes id: Int): Float { | |
return context.resources.getDimension(id) | |
} | |
override fun getDimensionPixelOffset(@DimenRes id: Int): Int { | |
return context.resources.getDimensionPixelOffset(id) | |
} | |
override fun getDimensionPixelSize(@DimenRes id: Int): Int { | |
return context.resources.getDimensionPixelSize(id) | |
} | |
@RequiresApi(Build.VERSION_CODES.Q) | |
override fun getFloat(@DimenRes id: Int): Float { | |
return context.resources.getFloat(id) | |
} | |
override fun getString(@StringRes id: Int): String { | |
return context.resources.getString(id) | |
} | |
override fun getQuantityString( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
): String { | |
return context.resources.getQuantityString(id, quantity) | |
} | |
override fun getText(@StringRes id: Int): CharSequence { | |
return context.resources.getText(id) | |
} | |
override fun getQuantityText( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
): CharSequence { | |
return context.resources.getQuantityText(id, quantity) | |
} | |
override fun getIntArray(@ArrayRes id: Int): IntArray { | |
return context.resources.getIntArray(id) | |
} | |
override fun getStringArray(@ArrayRes id: Int): Array<String> { | |
return context.resources.getStringArray(id) | |
} | |
override fun getTextArray(@ArrayRes id: Int): Array<CharSequence> { | |
return context.resources.getTextArray(id) | |
} | |
override fun getDrawable(@DrawableRes id: Int): Drawable? { | |
return ContextCompat.getDrawable(context, id) | |
} | |
override fun getIntStateFlow(@IntegerRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Integer(id), | |
getResource = ::getInt, | |
) | |
override fun getBooleanStateFlow(@BoolRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Boolean(id), | |
getResource = ::getBoolean, | |
) | |
override fun getColorStateFlow(@ColorRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Color(id), | |
getResource = ::getColor, | |
) | |
override fun getDimensionStateFlow(@DimenRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Dimension(id), | |
getResource = ::getDimension, | |
) | |
override fun getDimensionPixelOffsetStateFlow(@DimenRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.DimensionPixelOffset(id), | |
getResource = ::getDimensionPixelOffset, | |
) | |
override fun getDimensionPixelSizeStateFlow(@DimenRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.DimensionPixelSize(id), | |
getResource = ::getDimensionPixelSize, | |
) | |
@RequiresApi(Build.VERSION_CODES.Q) | |
override fun getFloatStateFlow(@DimenRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Float(id), | |
getResource = ::getFloat, | |
) | |
override fun getStringStateFlow(@StringRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.String(id), | |
getResource = ::getString, | |
) | |
override fun getQuantityStringStateFlow( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.QuantityString(id, quantity), | |
getResource = { getQuantityString(id, quantity) }, | |
) | |
override fun getTextStateFlow(@StringRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Text(id), | |
getResource = ::getText, | |
) | |
override fun getQuantityTextStateFlow( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.QuantityText(id, quantity), | |
getResource = { getQuantityText(id, quantity) }, | |
) | |
override fun getIntArrayStateFlow(@ArrayRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.IntArray(id), | |
getResource = ::getIntArray, | |
) | |
override fun getStringArrayStateFlow(@ArrayRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.StringArray(id), | |
getResource = ::getStringArray, | |
) | |
override fun getTextArrayStateFlow(@ArrayRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.TextArray(id), | |
getResource = ::getTextArray, | |
) | |
override fun getDrawableStateFlow(@DrawableRes id: Int) = getOrCreateResourceStateFlow( | |
typedResource = TypedResource.Drawable(id), | |
getResource = ::getDrawable, | |
) | |
private fun update() { | |
_stateFlowByResource.forEach { (typedResource, stateFlow) -> | |
val id = typedResource.id | |
stateFlow.value = when (typedResource) { | |
is TypedResource.Integer -> getInt(id) | |
is TypedResource.Boolean -> getBoolean(id) | |
is TypedResource.Color -> getColor(id) | |
is TypedResource.Dimension -> getDimension(id) | |
is TypedResource.DimensionPixelOffset -> getDimensionPixelOffset(id) | |
is TypedResource.DimensionPixelSize -> getDimensionPixelSize(id) | |
is TypedResource.Float -> { | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { | |
return | |
} | |
getFloat(id) | |
} | |
is TypedResource.String -> getString(id) | |
is TypedResource.QuantityString -> getQuantityString(id, typedResource.quantity) | |
is TypedResource.Text -> getText(id) | |
is TypedResource.QuantityText -> getQuantityText(id, typedResource.quantity) | |
is TypedResource.IntArray -> getIntArray(id) | |
is TypedResource.StringArray -> getStringArray(id) | |
is TypedResource.TextArray -> getTextArray(id) | |
is TypedResource.Drawable -> getDrawable(id) | |
} | |
} | |
} | |
private inline fun <T> getOrCreateResourceStateFlow( | |
typedResource: TypedResource, | |
getResource: (id: Int) -> T, | |
): StateFlow<T> { | |
if (typedResource in _stateFlowByResource) { | |
return getResourceStateFlow(typedResource) | |
} | |
val value = getResource(typedResource.id) | |
val stateFlow = MutableStateFlow(value) | |
addResourceStateFlow(typedResource, stateFlow) | |
return stateFlow | |
} | |
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") | |
private inline fun addResourceStateFlow(typedResource: TypedResource, stateFlow: MutableStateFlow<*>) { | |
_stateFlowByResource[typedResource] = stateFlow as MutableStateFlow<in Any?> | |
} | |
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") | |
private inline fun <T> getResourceStateFlow(typedResource: TypedResource): StateFlow<T> { | |
return _stateFlowByResource[typedResource] as StateFlow<T> | |
} | |
} | |
private sealed interface TypedResource { | |
val id: Int | |
class Integer(@IntegerRes override val id: Int) : TypedResource | |
class Boolean(@BoolRes override val id: Int) : TypedResource | |
class Color(@ColorRes override val id: Int) : TypedResource | |
class Dimension(@DimenRes override val id: Int) : TypedResource | |
class DimensionPixelOffset(@DimenRes override val id: Int) : TypedResource | |
class DimensionPixelSize(@DimenRes override val id: Int) : TypedResource | |
class Float(@DimenRes override val id: Int) : TypedResource | |
class String(@StringRes override val id: Int) : TypedResource | |
class QuantityString private constructor( | |
private val packedValue: Long | |
) : TypedResource { | |
@get:PluralsRes | |
override val id: Int get() = packedValue.toInt() | |
val quantity: Int get() = (packedValue shr 32).toInt() | |
constructor( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
) : this( | |
packedValue = id.toLong() shl 32 or quantity.toLong(), | |
) | |
} | |
class Text(@StringRes override val id: Int) : TypedResource | |
class QuantityText private constructor( | |
private val packedValue: Long | |
) : TypedResource { | |
@get:PluralsRes | |
override val id: Int get() = packedValue.toInt() | |
val quantity: Int get() = (packedValue shr 32).toInt() | |
constructor( | |
@PluralsRes | |
id: Int, | |
quantity: Int, | |
) : this( | |
packedValue = id.toLong() shl 32 or quantity.toLong(), | |
) | |
} | |
class IntArray(@ArrayRes override val id: Int) : TypedResource | |
class StringArray(@ArrayRes override val id: Int) : TypedResource | |
class TextArray(@ArrayRes override val id: Int) : TypedResource | |
class Drawable(@DrawableRes override val id: Int) : TypedResource | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment