I am trying to restric the app from affected fro system font scaling. I had gone through many solutions but none helped. Most of them tell use dp instead of sp for text sizes but in compose we can use only sp if i am right as it expects a Text Unit.
Is there any right way to restrict font scaling in our app done with jetpack compose ? Please help .
(Solutions refered) : https://l.workplace.com/l.php?u=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F21546805%2Fhow-to-prevent-system-font-size-changing-effects-to-android-application&h=AT0zIuBPbUONm0T6q8PtqbxCdX6P_ywlp-yFGrqPMqZt7H3wsWYltKO5XwbW3i0lenrxxLi3nn_kMO4aPtFUfig2iG0BcRZpd0wTuZ1_XFpdsjDM6E7RPyZ-G_c2dlmuzGqsSEHYbqBJun0hLLZgOpRUszKbe9-1xQ
You can have an extension for Int or Float like this
#Composable
fun Int.scaledSp(): TextUnit {
val value: Int = this
return with(LocalDensity.current) {
val fontScale = this.fontScale
val textSize = value / fontScale
textSize.sp
}
You can add an extension parameter of Int
val Int.scaledSp:TextUnit
#Composable get() = scaledSp()
Text(text = "Hello World", fontSize = 20.scaledSp)
override fun attachBaseContext(newBase: Context?) {
val newOverride = Configuration(newBase?.resources?.configuration)
if (newOverride.fontScale >= 1.1f)
newOverride.fontScale = 1.1f
applyOverrideConfiguration(newOverride)
super.attachBaseContext(newBase)
}
You can use something like this in your main activity.
Till there is no solution on jetpack compose for Text(), you can use AndroidView:
#Composable
fun CustomText(
// attributes you need to set
){
AndroidView(factory = { context ->
AppCompatTextView(context).apply {
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 25)
setText("")
// other attributes you want to set or other features which is not available in jetpack compose now.
}
},)
}
Related
I'm trying to dynamically swap a text inside a LottieAnimation in jetpack compose.
The lottie file is exported without glyphs
It's working when using the old android view inside a
AndroidView(factory = { context ->
val view = LottieAnimationView(context).apply {
setAnimation(R.raw.testing_no_glyphs)
playAnimation()
repeatCount = LottieConstants.IterateForever
}
val textDel = object : TextDelegate(view) {
override fun getText(layerName: String?, input: String?): String {
return when (layerName) {
"Test234" -> "OtherLettersHere"
else -> super.getText(layerName, input)
}
}
}
val fontDel = object : FontAssetDelegate() {
override fun getFontPath(fontFamily: String?, fontStyle: String?, fontName: String?): String {
return "fonts/[MyFontInside /assets].ttf"
}
}
view.setTextDelegate(textDel)
view.setFontAssetDelegate(fontDel)
return#AndroidView view
})
But I can't find the correct handles in the JetpackCompose version of Lottie to get the same result.
If we export the lottie with glyphs, it's works for the letters in the chars array inside the lottie json. But we want to be able to replace with any letters/symbols, so this isn't a viable solution.
I've noticed in the 5.3.0-SNAPSHOT that a fontMap parameter has been added, but I can't figure out which key to hit with it.
Here is my code:
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234"))
)
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.testing)
)
val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
LottieAnimation(
composition,
{ progress },
dynamicProperties = dynamicProperties,
fontMap = mapOf("fName" to Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf"))
)
It just shows a blank for all the texts inside the Lottie Animation - so this is kinda where i'm stuck.
After some trial an error I found a way to add the typeface for a specific layer:
val typeface = Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf")
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234")),
--> rememberLottieDynamicProperty(LottieProperty.TYPEFACE, value = typeface, keyPath = arrayOf("Test234")),
)
Hence there is no need for the fontMap in my case
Like below are two functions
#Composable
private fun WaterCounter(modifier: Modifier = Modifier) {
val count = 0
Text(
text = "You've had $count glasses of water",
modifier = modifier.padding(all = 16.dp)
)
}
#Preview(showBackground = true)
#Composable
private fun PreviewWaterCounter() {
WaterCounter()
}
So, wouldn't it be better if we add #Preview annotation to the WaterCounter, which will save some lines of code and will work both as a preview and a widget?
For simple situations like your posted code, having a separate composable preview seems a bit too much, but consider this scenario with 2 composables with non-default parameters,
#Composable
fun PersonBiography(
details: Data,
otherParameters : Any?
) {
Box(
modifier = Modifier.background(Color.Red)
) {
Text(details.dataValue)
}
}
#Composable
fun AccountDetails(
details: Data
) {
Box(
modifier = Modifier.background(Color.Green)
) {
Text(details.dataValue)
}
}
both of them requires same data class , the first one has an additional parameter. If I have to preview them I have to break their signature, assigning default values to them just for the sake of the preview.
#Preview
#Composable
fun PersonBiography(
details: Data = Data(dataValue = ""),
otherParameters : Any? = null
) { … }
#Preview
#Composable
fun AccountDetails(
details: Data = Data(dataValue = "")
) { … }
A good workaround on this is having 2 separate preview composables and taking advantage of PreviewParameterProvider to have a re-usable utility that can provide instances of the parameters I needed.
class DetailsPreviewProvider : PreviewParameterProvider<Data> {
override val values = listOf(Data(dataValue = "Some Data")).asSequence()
}
#Preview
#Composable
fun PersonBiographyPreview(#PreviewParameter(DetailsPreviewProvider::class) details: Data) {
PersonBiography(
details = details,
// you may also consider creating a separate provider for this one if needed
null
)
}
#Preview
#Composable
fun AccountDetailsPreview(#PreviewParameter(DetailsPreviewProvider::class) details: Data) {
AccountDetails(details)
}
Or if PreviewParameterProvider is a bit too much, you can simply create a preview composable where you can create and supply the mock data.
#Preview
#Composable
fun AccountDetailsPreview() {
val data = Data("Some Account Information")
AccountDetails(data)
}
With any of these approaches, you don't need to break your actual composable's structure just to have a glimpse of what it would look like.
I want to calculate height of Box in a function and I want compose to remember its result and not call that function again. That function has some composable content and therefore it is a #Composable function.
The problem is that, Compose won't let me call this function from remember block.
This is the code that I have:
val coverImageHeightInDp = remember {
viewModel.calculateTopCoverHeightInDp()
}
Box(modifier = Modifier
.then(modifier)
.fillMaxWidth()
.height(coverImageHeightInDp)
)
And the function in viewModel:
#Composable
fun calculateTopCoverHeightInDp(): Dp {
val originalImageDimens = Size(1440f, 828f)
val imageRatio = originalImageDimens.width / originalImageDimens.height
val configuration = LocalConfiguration.current
val screenWidthInDp = configuration.screenWidthDp.dp
return screenWidthInDp / imageRatio
}
How can I code it in a way that the result of this function is remembered and this function is not called again, until screen rotation?
Thanks.
When your computation functions requires some variable from #Composable scope you can just pass the variable you get in composition such as density or configuration
fun calculateTopCoverHeightInDp(configuration: Configuration): Dp {
val originalImageDimens = Size(1440f, 828f)
val imageRatio = originalImageDimens.width / originalImageDimens.height
val screenWidthInDp = configuration.screenWidthDp.dp
return screenWidthInDp / imageRatio
}
And use it as
val configuration: Configuration = LocalConfiguration.current
val calculatedHeight = remember(configuration){
calculateTopCoverHeightInDp(configuration)
}
Also you can check out this answer for difference between Composable and non-Composable functions.
What are differents between Composable function and normal function in Android?
I have a project with several flavors. Each of these flavors has its own configuration which is available as a json file in the assets folder in the respective project structure.
In the theme definition I read the JSON using Gson and cast it into a corresponding model.
My problem is now the following:
At runtime of the app this all works wonderfully but in the composable preview in Android Studio it unfortunately only works for a single flavor. As soon as I switch to another flavor in the build variant, the json-asset of the old variant continues to load. Since the configuration also contains assets that are only available in the respective flavors, this leads to a crash of the preview.
I debugged the preview handling by throwing some exceptions during the casting and it seems, like if there'S something cached and not reset after build-variant change. A restart of Android Studio didn't also help so I don't quite know what to do about it.
Has anyone noticed a similar behavior and/or found a solution for it?
Here is some code to explain::
My theme definition:
object AppTheme {
val colors: AppColors
#Composable
#ReadOnlyComposable
get() = LocalAppColors.current
val typography: AppTypography
#Composable
#ReadOnlyComposable
get() = LocalAppTypography.current
val configuration: ConfigurationC
#Composable
#ReadOnlyComposable
get() = LocalAppConfiguration.current
}
private val LocalAppColors = staticCompositionLocalOf {
lightAppColors
}
private val LocalAppTypography = staticCompositionLocalOf {
appTypography
}
private val LocalAppConfiguration = staticCompositionLocalOf {
ConfigurationC()
}
#Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit,
) {
val colors = if (darkTheme) darkAppColors else lightAppColors
CompositionLocalProvider(
LocalAppConfiguration provides ConfigurationC.init(LocalContext.current),
LocalAppColors provides colors,
LocalAppTypography provides typography,
) {
MaterialTheme(
colors = colors.materialColors,
typography = typography.materialTypography,
content = content,
)
}
}
A simple Preview:
#Composable
#Preview(name = "light", showBackground = true)
#Preview(name = "dark", showBackground = true, uiMode = UI_MODE_NIGHT_YES)
fun EnabledPreview() {
AppTheme {
Button.MyCustomButton(
modifier = Modifier,
title = "Custom Button",
font = AppTheme.configuration.font.h1
color = AppTheme.configuration.colors.text1
enabled = enabled,
onClick = {}
)
}
}
I want to support devices like phone, tablets, foldables and tv. Will the following code work for that purpose ?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSize = rememberWindowSizeClass()
when (windowSize.widthWindowSizeClass) {
is WindowSizeClass.WindowType.COMPACT -> {
CompactActivityUi()
/*TODO(reason = "Ui for COMPACT window")*/
}
is WindowSizeClass.WindowType.MEDIUM -> {
/*TODO(reason = "Ui for Medium window")*/
}
else -> {
/*TODO(reason = "Ui for EXPANDED window")*/
}
}
}
}
}
#Composable
fun CompactActivityUi() {
AppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
}
}
}
data class WindowSizeClass(
val widthWindowSizeClass: WindowType,
val heightWindowSizeClass: WindowType,
val widthWindowDpSize: Dp,
val heightWindowDpSize: Dp
) {
sealed class WindowType {
object COMPACT : WindowType()
object MEDIUM : WindowType()
object EXPANDED : WindowType()
}
}
#Composable
fun Activity.rememberWindowSizeClass(): WindowSizeClass {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity = this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
return WindowSizeClass(
widthWindowSizeClass = when {
windowDpSize.width < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
windowDpSize.width < 600.dp -> WindowSizeClass.WindowType.COMPACT
windowDpSize.width < 840.dp -> WindowSizeClass.WindowType.MEDIUM
else -> WindowSizeClass.WindowType.EXPANDED
},
heightWindowSizeClass = when {
windowDpSize.height < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
windowDpSize.height < 480.dp -> WindowSizeClass.WindowType.COMPACT
windowDpSize.height < 900.dp -> WindowSizeClass.WindowType.MEDIUM
else -> WindowSizeClass.WindowType.EXPANDED
},
widthWindowDpSize = windowDpSize.width,
heightWindowDpSize = windowDpSize.height
)
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
CompactActivityUi()
}
Official documentation
Official Sample
Official video
Will this code work for foldables, specially foldables in tabletop posture?
Is it recommended to have BottomNavigation on phone, Nav Rail on tablet and Navigation Drawer or T.V.
The code you included will help you support devices of different sizes, but it won't detect foldable postures. To do that, you need to use the Jetpack Window Manager library to access FoldingFeature information (API reference).
The Jetcaster official Compose sample has an example of how to detect tabletop/book mode (MainActivity, WindowInfoUtil), but it's also important to note that some foldable devices have completely separate screens (even when flat). To support all kinds of foldables, I would recommend checking the FoldingFeature.isSeparating property in addition to FoldingFeature.state.
For more information about foldable support, you can check out these docs:
Make your app fold aware
Jetpack Window Manager for Compose
WindowState library
For navigation, I'm not familiar with the guidance for TV, but for other devices I think the recommendations depend on the current window size class. BottomNavigation is best for COMPACT width (most phones), while NavigationRail is better for MEDIUM and EXPANDED widths (tablets and larger foldables).