Android Compose How to Fix "ComposableModifierFactory" and "UnnecessaryComposedModifier" Lint Warnings? - android

How can I create reusable modifiers without android compose lint rules throwing a fit?
I don't want to have to copy/paste the same modifiers for every screen within my app, I would rather just create an extension function I can call like this,
Box(modifier = Modifier.defaultFillScreen())
But that extension function, shown below, keeps throwing lint errors.
#Composable
fun Modifier.defaultFillScreen() = this.then(Modifier
.fillMaxWidth()
.navigationBarsWithImePadding()
.verticalScroll(rememberScrollState())
.padding(dimensionResource(id = R.dimen.standard_padding)))
Gives me the following lint error:
ComposableModifierFactory: Modifier factory functions should not be
marked as #Composable, and should use composed instead
When I make that change I then get a new lint error:
fun Modifier.defaultFillScreen() = composed { this.then(Modifier
.fillMaxWidth()
.navigationBarsWithImePadding()
.verticalScroll(rememberScrollState())
.padding(dimensionResource(id = R.dimen.standard_padding))) }
UnnecessaryComposedModifier: Unnecessary use of Modifier.composed
How can I create a reusable modifier without compose complaining about it? Writing the same 5 lines of modifier code for every screen is not an acceptable answer.
dependencies:
'androidx.activity:activity-compose:1.3.1',
'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07',
'androidx.compose.material:material:1.0.5',
'androidx.navigation:navigation-compose:2.4.0-alpha06',
'androidx.compose.ui:ui:1.0.5',
'androidx.compose.ui:ui-tooling:1.0.5'
Android studio:
Android Studio Arctic Fox | 2020.3.1 Build
#AI-203.7717.56.2031.7583922, built on July 26, 2021

I don't see the same warning with compose 1.2.0-alpha and Android Studio Bubblebee, it used to appear when i use Modifier.composed without state.
Purpose of Modifier.composed is having stateful modifiers which you use with remember, LaunchedEffect. When you don't have a state associated with your Modifier you should you Modifier.then instead
fun Modifier.composedBackground(width: Dp, height: Dp, index: Int) = composed(
// pass inspector information for debug
inspectorInfo = debugInspectorInfo {
// name should match the name of the modifier
name = "myModifier"
// add name and value of each argument
properties["width"] = width
properties["height"] = height
properties["index"] = index
},
// pass your modifier implementation that resolved per modified element
factory = {
val density = LocalDensity.current
val color: Color = remember(index) {
Color(
red = Random.nextInt(256),
green = Random.nextInt(256),
blue = Random.nextInt(256),
alpha = 255
)
}
// 🔥 Without remember this color is created every time item using this modifier composed
// val color: Color = Color(
// red = Random.nextInt(256),
// green = Random.nextInt(256),
// blue = Random.nextInt(256),
// alpha = 255
// )
// add your modifier implementation here
Modifier.drawBehind {
val widthInPx = with(density) { width.toPx() }
val heightInPx = with(density) { height.toPx() }
drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
}
}
)
This is just a sample composed example. If you change from remember you will see that at each recomposition random color will change.
And without composed it will give error #Composable invocations can only happen from the context of a #Composable function if you use remember like the snippet below.
fun Modifier.nonComposedBackground(width: Dp, height: Dp, index: Int) = this.then(
// add your modifier implementation here
Modifier.drawBehind {
val color: Color = remember(index) {
Color(
red = Random.nextInt(256),
green = Random.nextInt(256),
blue = Random.nextInt(256),
alpha = 255
)
}
val widthInPx = width.toPx()
val heightInPx = height.toPx()
drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
}
)

Related

compose modifiy icon set part of the color

I have an icon to indicate sequence.
My requirement is that when the order is reversed, the lower half of the icon turns blue; when the order is turned, the upper half of the icon turns blue.
I found a related question, but it conflicts with my needs in two points. First, I don't know how to write such code in compose. Second, I prefer to use code to control the color transformation.
Using BlendModes you can manipulate any pixel using another shape, png file, drawing or Path. You can refer these answers for more details
Jetpack Compose Applying PorterDuffMode to Image
How to clip or cut a Composable?
Result
Implementation
#Preview
#Composable
private fun Test() {
Image(
modifier = Modifier
.size(100.dp)
.drawWithContent {
val height = size.height
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
// Destination
drawContent()
// Source
drawRect(
Color.Red,
topLeft = Offset(0f, height / 2),
size = Size(size.width, size.height / 2),
blendMode = BlendMode.SrcIn
)
}
},
painter = painterResource(id = R.drawable.arrows),
contentDescription = null
)
}

How to remove Surface padding in jetpack compose

In LazyColumn when we use LazyListScope.items with Surface. Inside multiple items there is extra padding on TOP and BOTTOM. I want to remove this padding. I am using Surface component of Material 3. BOM version is compose_bom = "2022.11.00".
Please don't suggest any alpha or beta version fix. If Material 3 stable api don't have solution, then please suggest normal Surface Material.
PreviewCreateListView
#Preview(showBackground = true)
#Composable
fun PreviewCreateListView() {
CreateListView()
}
CreateListView
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun CreateListView() {
val itemList = listOf(1, 2, 3)
LazyColumn(
contentPadding = PaddingValues(16.dp),
) {
items(itemList) { item ->
Surface(
onClick = { },
color = Color.Blue
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "$item",
)
}
}
}
}
Output
The M3 Surface with the onClick parameter has a minimum touch target size (48.dp) for accessibility. It will include extra space outside the component to ensure that they are accessible.
You can override this behaviour applying false to the LocalMinimumInteractiveComponentEnforcement. If it is set to false there will be no extra space.
Something like:
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false) {
Surface(
onClick = { },
color = Color.Blue
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "$item",
)
}
}
Note: LocalMinimumInteractiveComponentEnforcement requires at least
M2 1.4.0-alpha04 and M3 1.1.0-alpha04. Before you can use LocalMinimumTouchTargetEnforcement in the same way.
The Surface variant that you use, with a onClick parameter, enforces a minimum height for accessibility purposes, see this at line 221
If you want to remove the space, use the variant without the onClick argument and use a Modifier.clickable instead
#Composable
fun CreateListView() {
val itemList = listOf(1, 2, 3)
LazyColumn(
contentPadding = PaddingValues(16.dp),
) {
items(itemList) { item ->
Surface(
modifier = Modifier.clickable { },
color = Color.Blue
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "$item",
)
}
}
}
}

How to get correct systemBars height using compose

I know this seems trivial but I've looked up every doc, maybe I'm missing something.
I am trying to get this
across different device screen size (focusing on compact types). I successfully got screen height using LocalConfiguration.current.screenHeightDp
Now I'm implementing edge-to-edge display.
I followed this doc, but i get this
looking at this there is a space between them (blue), because of the feature to draw behind systembars. then i tried to get the systemBars height(top & bottom) to add to the boxes resp to compensate for the extra space.
I used inset.getInsets(WindowInsetsCompat.Type.systemBars()) which returns the heights of the systembars but after that the boxes overlap(yellow)
I logged the values I'm getting from the insets, it was quite more than the values of LocalConfiguration.current.screenHeightDP
How do i compensate for the space or get the correct height for the systemBars?
Here is my code :
#Composable
fun MyBox() {
val view = LocalView.current
val inset = ViewCompat.getRootWindowInsets(view)!!
val insetSystemBar = inset.getInsets(WindowInsetsCompat.Type.systemBars()) //Insets{left=0, top=136, right=0, bottom=132}
val insetStatusBar = insetSystemBar.top // 136
val insetNavBar = insetSystemBar.bottom // 132
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp //This returns 753
val testHeightRatio = 0.5
val TopBoxHeight = (((screenHeight * testHeightRatio).roundToInt())).dp // 377
val BottomBoxHeight = (((screenHeight * testHeightRatio).roundToInt())).dp // 377
val TopBoxAndStatusBarHeight = TopBoxHeight + insetStatusBar.dp // 513
val BottomBoxAndNavBar = BottomBoxHeight + insetNavBar.dp // 509
MyBoxTheme {
Surface(Modifier.fillMaxSize()) {
Box(
Modifier
.fillMaxSize()
.background(Color(0xFF00B8D4))
) {
Box(
modifier = Modifier
.height(BottomBoxAndNavBar)
.fillMaxWidth()
.align(Alignment.BottomCenter)
.background(Color(0x86FF0303)),
)
Box(
modifier = Modifier
.height(TopBoxAndStatusBarHeight)
.fillMaxWidth()
.background(Color(0x8FFFDD03))
.align(Alignment.TopCenter)
)
}
}
}
}

How to convert a color resource to a Color object in Jetpack Compose?

I have a Text and I want to draw a circle that should have a color that exists inside my resource file.
Text(
modifier = Modifier.align(Alignment.TopEnd).drawBehind {
drawCircle(
color = colorResource(R.color.primary),
radius = 96.00f
)
},
text = "X"
)
But I get:
#Composable invocations can only happen from the context of a #Composable function
If I use color = Color.Red, it works fine. So I don't want to use any of those colors, I want to use mine. So I thought to make a conversion, but I cannot find any solution. How can I convert that?
You need to call Composable functions from a scope that has #Composable annotation.
Box() {
val color = colorResource(R.color.black)
Text(
modifier = Modifier.align(Alignment.TopEnd).drawBehind {
drawCircle(
color = color,
radius = 96.00f
)
},
text = "X"
)
}
colorResource is a #Composable function
#Composable
#ReadOnlyComposable
fun colorResource(#ColorRes id: Int): Color {
val context = LocalContext.current
return if (Build.VERSION.SDK_INT >= 23) {
ColorResourceHelper.getColor(context, id)
} else {
#Suppress("DEPRECATION")
Color(context.resources.getColor(id))
}
}
but lambda of Modifier.drawBehind isn't
fun Modifier.drawBehind(
onDraw: DrawScope.() -> Unit
) = this.then(
DrawBackgroundModifier(
onDraw = onDraw,
inspectorInfo = debugInspectorInfo {
name = "drawBehind"
properties["onDraw"] = onDraw
}
)
)
You can check this answer for difference between function, lambdas or params with #Composable annotation or the ones without

How to draw parallel horizontal lines in a compose TextField?

I am trying to build a note taking app using Jetpack Compose. I want a custom TextField which contains parallel horizontal lines just like a real notepad.
How can I achieve this?
(I know basics of compose Canvas but an not able to figure out how to begin)
This is what I am trying to make:
Ok since your question explicitly states that you wish to know the method to help you draw parallel horizontal lines, this implementation might be helpful:-
#Preview
#Composable
fun LeafPad() {
val textSize = 25.sp
Box(
Modifier
.fillMaxSize()
.background(Color(0xFFFEFCB5))) {
Canvas(modifier = Modifier.fillMaxSize()) {
var yCord = 0f
repeat(40) {
drawLine(
Color(0xFFB2B461),
Offset(0f, yCord),
Offset(size.width, yCord),
strokeWidth = 2f
)
yCord += 1.8f * textSize.toPx()
}
}
var value by remember { mutableStateOf("") }
TextField(
modifier = Modifier.fillMaxSize(),
value = value,
onValueChange = { value = it },
textStyle = TextStyle(fontSize = textSize, color = Color.Black, lineHeight = (1.8f * textSize.value).sp)
)
}
}
Honestly, looking at your use case, you might want to consider using Pagination instead which features stuff like infinite scrolling, because I assume that for a Text Editor, you would want to scroll down when the user reaches the end of the page, but of course it solely depends on your design and implementation.

Categories

Resources