I am practicing Jetpack compose, by making a simple Application with multiple destinations (screen), I happen to be referencing an Android Developer codelab, for a destination.
But the final looks is very different from the codelab's, most noticably the fonts are way bigger on my code than it is in the codelab even though I'm using the same font style, h3, and I've tried as much as possible to be using the same or a more recent dependecency than was used in the codelab.
Here's what I mean.
The Android Developer look;
And my code looks like;
Here's the Android Developer compose code;
#Composable
fun FavoriteCollectionCard(
#StringRes text: Int,
#DrawableRes drawable: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.small,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.width(192.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(56.dp)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
Here's the Android Developer compose code;
#Composable
fun DashBoardCard(
#StringRes text: Int,
#DrawableRes drawable: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.small,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.width(192.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(56.dp)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
As I said before I tried to make the dependencies the same or more recent, I don't want this question to be anymore bucky, but I'm just going to add a few dependency, please whatever you need I'm happy to provide.
Android Developer Codelab dependency;
ext {
compose_version = '1.2.0-alpha05'
}
plugins {
id 'com.android.application' version '7.2.0' apply false
id 'com.android.library' version '7.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'com.diffplug.spotless' version '6.3.0'
}
My dependency;
ext {
compose_version = '1.2.0-alpha05'
nav_version = "2.5.1"
}
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
EDIT;
I will greatly appreciate any type of help, pointers, corrections whatever help you can offer, I will be much greatful.
Thanks a lot for your help, in advance.
Are you using same implementation of MaterialTheme as they are and are you wrapping your screen in it? Their implementation is here and h3 is defined here like this:
h3 = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
letterSpacing = 0.sp
)
Your image definitely doesn't look like 14sp, it also looks like different color, so I guess you are missing the theme completely.
Related
I'm developing an app for a very limited hardware and decided to use jetpack compose.
The problem arises when I need to display a list of cards and the lazyrow used for it gets extremely laggy. For comparison, I picked up a sample project with a recyclerView and used it to display roughly the same list of cards and the scrolling is as smooth as can be. Is jetpack compose inherently slower than xml view or am I doing something wrong?
Compose code (I can't exactly share my code, but the card composable is just a card with some images, icons and text):
#Composable
fun mainComposable(){
...
cardList = remember{ arrayListOf(...) }
lazyList(cardList)
...
}
#Composable
fun lazyList(
cardList: List<CardContent>,
){
LazyRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 32.dp)
) {
items(
items = cardList,
key = { it.id }) { item ->
CardComposable(
content = item
)
}
}
}
I've already spent some time searching so I found a lot of optimizations, like running in release mode, setting minifyEnabled and shrinkResources to true on build.gradle and android.enableR8.fullMode to true in gradle.properties, using keys on the LazyRow, etc. They helped, but the scrolling is still fundamentally slower than an equivalent xml view app with recyclerView.
Edit: Added CardComposable code
#Composable
fun CardComposable(
content: Content,
) {
Card(
shape = RoundedCornerShape(8.dp),
elevation = 1.dp,
modifier = Modifier
.width(216.dp)
.height(308.dp)
) {
Column {
Box(contentAlignment = Alignment.TopEnd) {
Image(
painter = painterResource(id = content.image),
modifier = Modifier
.width(216.dp)
.height(164.dp)
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(8.dp)
) {
Image(
painter = painterResource(id = R.drawable.button_background),
modifier = Modifier
.width(40.dp)
.height(40.dp)
)
Icon(
painter = painterResource(id = R.drawable.button),
modifier = Modifier
.width(16.dp)
.height(16.dp),
tint = GenericRedColor
)
}
}
Column(
modifier = Modifier
.padding(start = 16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = content.Name,
maxLines = 2,
color = GenericBlackColor,
fontSize = 16.sp,
fontWeight = FontWeight(500),
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.width(134.dp)
.padding(top = 16.dp)
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(start = 16.dp, top = 8.dp)
) {
Icon(
painter = painterResource(id = R.drawable.square_button),
modifier = Modifier
.width(32.dp)
.height(32.dp),
tint = GenericRedColor
)
Icon(
painter = painterResource(id = R.drawable.button_icon),
modifier = Modifier
.width(16.dp)
.height(16.dp),
tint = GenericWhiteColor
)
}
}
Text(
text = content.contentType,
color = GenericLightGrayColor2,
fontSize = 14.sp,
modifier = Modifier.padding(top = 8.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(26.dp),
modifier = Modifier.padding(top = 24.dp)
) {
val iconModifier = Modifier
.padding(end = 4.dp)
.width(12.dp)
.height(12.dp)
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(R.drawable.icon_1),
modifier = iconModifier,
tint = GenericLightGrayColor2
)
Text(
text = content.text_1,
fontSize = 14.sp,
color = GenericLightGrayColor2
)
Icon(
painter = painterResource(R.drawable.icon_2),
modifier = iconModifier,
tint = GenericLightGrayColor2
)
Text(
text = content.text2,
fontSize = 14.sp,
color = GenericLightGrayColor2
)
Icon(
painter = painterResource(R.drawable.icon_3),
modifier = iconModifier,
tint = GenericLightGrayColor2
)
Text(
text = content.text3,
fontSize = 14.sp,
color = GenericLightGrayColor2
)
}
}
}
}
}
}
Jetpack Compose is a separate library and is not included in the Android Operating System. Hence the code from the library should be Just In Time (JIT) compiled on the first run. This makes it inherently slower than Android View based code which is Ahead Of Time compiled(AOT) and binaries are stored inside the OS on the device.
This design decision of making Jetpack Compose as a standalone library has its advantages too. It makes it easier to update and use different versions of the library irrespective of the Android OS version, and enables backwards compatibility between compose and android versions.
In iOS, Swift takes the other approach and the Swift binaries are Ahead Of Time compiled and included in the OS. This is one of the main reasons other than Apple's laziness that prevents backwards compatibility in iOS.
Regarding the performance differences between RecyclerView and LazyLists, LazyLists are considerably less performant than RecyclerView. This has multiple reasons. I think it's mainly because Compose is a newer library and is constantly improving. The performance of earlier versions of LazyLists were considerably worse. Performance would be further improved in the upcoming compose versions.
For the time being, Since Jetpack Compose has interoperability with Android View based code, you can use RecyclerView in Compose with minimal performance overhead. Using AndroidView() function in Jetpack Compose.
#Composable
fun MyView(data: State<List<Item>>) {
//This function enables Compose to interop with View based code.
AndroidView(
factory = { context ->
RecyclerView(context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
layoutManager = LinearLayoutManager(context)
adapter = ItemListAdapter().also { it.submitList().value }
}
},
update = { recyclerView ->
//Callback that runs on each recomposition.
}
)
}
I use Android Studio Bumblebee 2021.1.1 Patch 3 built on March 16, 2022
androidx.compose.ui:ui-tooling, androidx.compose.ui:ui-tooling-preview, androidx.compose.ui:ui
are in version 1.2.0-alpha07
I want to create preview of my composable but I can't create any one. Every time I see error:
"The project needs to be compiled for the preview to be displayed"
I rebuilt, synced project and restarted Android Studio but it doesn't help.
What can be wrong?
My composable for example:
#Composable
fun DefaultProfileAvatarBox(
modifier: Modifier = Modifier
.size(60.dp),
firstLetter: String
) {
val finalModifier = modifier
.clip(CircleShape)
.background(blueAvatarBackground)
Box(
modifier = finalModifier
) {
Text(
text = firstLetter,
modifier = Modifier
.padding(bottom = 2.dp)
.align(Alignment.Center),
fontSize = 32.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White,
textAlign = TextAlign.Center
)
}
}
#Preview
#Composable
fun DefaultProfileAvatarBoxPreview() {
DefaultProfileAvatarBox(firstLetter = "K")
}
I had the same problem
But when I used the stable version of activity-compose (1.4.0) !
The problem was solved.
dependencies {
// ...
implementation 'androidx.activity:activity-compose:1.4.0'
}
Use version 1.1.1 to avoid this.
I hope it helps you.
If we have a compose component which gets two or more modifiers, how should we handle it ?
I mean the naming of modifiers while lint complains changing the name of modifier parameter
Sample code to figure out easily :
#Composable
private fun CompletionSection(iconModifier: Modifier, textModifier: Modifier, isActivated: Boolean, newText: String?) {
if (isActivated) {
Icon(
painter = painterResource(R.drawable.ds_ic_check_circle),
modifier = iconModifier
.wrapContentSize()
.padding(top = 18.dp),
tint = MaterialTheme.colors.positive,
contentDescription = null
)
} else if (!newText.isNullOrBlank()) {
Surface(
modifier = textModifier.padding(top = 18.dp),
shape = RoundedCornerShape(32.dp),
border = BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary.copy(alpha = 0.6f)),
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontSize = 11.sp,
color = MaterialTheme.colors.primary.copy(alpha = 0.6f),
text = newText,
modifier = Modifier
.defaultMinSize(minHeight = 20.dp)
.wrapContentSize()
.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.android.caption2
)
}
}
}
Here, where the function is used →
ConstraintLayout(
modifier = Modifier.fillMaxSize(),
constraintSet = decoupledConstraints(
marginSpacing02 = marginSpacing02,
marginSpacing01 = marginSpacing01,
entity = entity
)
) {
CompletionSection(
iconModifier = Modifier.layoutId("completedIcon"),
textModifier = Modifier.layoutId("newTextField"),
isActivated = isActivated,
newText = newText
)
}
I assume the reason for this kind of warning is because you usually have one modifier that has to be applied to the whole view. Having an other modifier in arguments is kind of OK, but, for example if you need to apply Modifier.align, you had to duplicate it.
In your case, when you look from where you're using this function, it's hard to tell which modifier will be applied and which is not - it depends on other parameters and you have to know the logic.
I think at least it could have one generic modifier named modifier, which would apply for both views, and two named ones - in my opinion this would make the API a bit more predictable. You can chain modifiers like this: modifier.then(iconModifier).yourModifier()
Anyway, you can suppress it:
#SuppressLint("ModifierParameter")
#Composable
// ...
https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier
Composables that accept modifiers to be applied to a specific subcomponent foo should name the parameter fooModifier and follow the same guidelines above for default values and behavior. Subcomponent modifiers should be grouped together and follow the parent composable's modifier. For example:
#Composable
fun ButtonBar(
onOk: () -> Unit,
onCancel: () -> Unit,
modifier: Modifier = Modifier,
buttonModifier: Modifier = Modifier
) {
Row(modifier) {
Button(onCancel, buttonModifier) {
Text("Cancel")
}
Button(onOk, buttonModifier) {
Text("Ok")
}
}
}
Composables are designed to accept only one Modifier, so the lint won't be satifsfied no matter how you rename them.
The Composable is something like a unit of interface, and it having multiple modifiers is leaking its inner workings to the outer composables that use it.
How to set elevation for material3 card? I am using new material3 card and getting error
This material API is experimental and is likely to change or to be removed in the future.
Here is code ->
#ExperimentalMaterial3Api
#Composable
fun ProfileCard(
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(all = 16.dp),
shape = RoundedCornerShape(size = 16.dp),
containerColor = MaterialTheme.colorScheme.surface,
border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.inverseOnSurface),
elevation = CardDefaults.outlinedCardElevation()
) {
...
}
}
I am not able to run app due to error caused by elevation how to set elevation?
Edit : Solved the issue by adding
elevation = CardDefaults.outlinedCardElevation(defaultElevation = 1.dp)
Why do we have to add border for CardDefaults.outlinedCardElevation why does't it show by default?
If you find the same message in the future for another reason, one option could be to add in your build.gradle file, the following:
kotlinOptions {
allWarningsAsErrors = false
freeCompilerArgs += [
'-opt-in=androidx.compose.material3.ExperimentalMaterial3Api'
]
}
In this way, after Sync Project with Gradle Files you will not see the warnings in your compose code.
i am new to jetpack compose and also have some experience with flutter so when move to compose its a little bit similar to flutter declarative UI style but i don't quite understand
like in flutter when i want some widget to expanded and want it to take full available space we can use Exapanded widget and if i use this in Column its automatically add flex of 1 or weight in terms of jetpack compose so i want to do same thing in jetpack compose but its seems like jetpack column not automatically and weight to its children and also i cant add
Modifier.weight(1f) in Card of NetworkCardComposable so i have to pass Modifier from as Parameter and in when using this Compoasable in columns this Modifier.weight(1f) have to set through parameter which is odd i think.
#Preview(showBackground = true, widthDp = 500, heightDp = 200)
#Composable
fun NetworkCardComposable(modifier: Modifier = Modifier) {
Card(
modifier = modifier
.fillMaxSize(1f)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
backgroundColor = colorResource(id = R.color.jazz_color_primary)
) {
Row {
Card(
modifier = Modifier
.weight(2f)
.fillMaxSize()
.padding(8.dp),
shape = RoundedCornerShape(16.dp),
) {
Image(
modifier = Modifier
.padding(8.dp),
painter = painterResource(R.drawable.jazz_logo),
contentDescription = null,
)
}
Row(
modifier = Modifier
.weight(2f)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
text = "Bundles & Offers",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.h5.fontSize,
)
Icon(
modifier = Modifier.size(56.dp),
imageVector = Icons.Default.ChevronRight,
tint = Color.White,
contentDescription = null,
)
}
}
}
}
#Preview(showBackground = true, showSystemUi = true)
#Composable
fun networkCard() {
Column(Modifier.fillMaxSize()) {
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
}
}
all thought i have acheive my desired layout but if there is any better approach to this please answer
Jetpack Compose aims at providing the maximum control over the UI. Hence, by default, the elements of a container like column default to wrap content. If you want them to occupy Max available space of the container, the proper way to do it is using Modifier.fillMaxSize()
As far as weights are concerned, if you do not specify then explicitly while adding more than one elements inside a container, the weights are calculated accordingly, based on the minimum required dimensions, (i.e., again, wrapContentSize ())