LazyColumn does not show me the list of items - android-jetpack-compose

I'm just learning how to use LayzyColumn. The LazyColumn does not show me the list of items. When I click the button, the items that are loaded in the list are not displayed.
Here my code:
#Composable
fun MainContent() {
val list = remember { mutableListOf<String>() }
LazyColumn(
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
itemsIndexed(list) { index, item ->
Card(
backgroundColor = Color(0xFFF7C2D4),
elevation = 4.dp
) {
Row(
Modifier
.fillMaxWidth()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = item.takeLast(5), Modifier.weight(1F))
}
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
Alignment.BottomEnd
) {
FloatingActionButton(onClick = { list.add(UUID.randomUUID().toString()) }) {
Icon(imageVector = Icons.Default.Add, contentDescription = "")
}
}
}

Compose cannot track changes of plain kotlin types. You need to use Compose mutable states.
In this case mutableStateListOf should be used instead of mutableListOf.
I suggest you start with this youtube video which explains the basic principles of why do you need to use state in Compose. You can continue deepening your knowledge with state in Compose documentation.

Related

How to add and delete items in a lazyVerticalGrid Column in jetpack compose

I'm new to jetpack compose and I am just looking for a way to add and delete items from a lazyVerticalGrid using jetpack compose from a pop-up menu. Nothing complicated, just simple code to make me easily understand what I have to do
This is pretty easy as other LazyLists. You need to have a list that you can trigger recomposition after delete, add or update and use unique keys to not recompose your entire list and limit recomposition to a range of items.
val list = remember { mutableStateListOf<Snack>() }
You can remove or add inside you menu by changing this list
Adding, removing items or replacing any items with new one, to update you need to pass a new instance of object, will trigger recomposition
#Composable
private fun GridExample() {
val scrollState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val list = remember { mutableStateListOf<Snack>() }
Column {
LazyVerticalGrid(
contentPadding = PaddingValues(12.dp),
modifier = Modifier
.weight(1f)
.background(backgroundColor),
columns = GridCells.Fixed(3),
content = {
items(items = list,
key = { snack: Snack ->
snack.name
}) { snack: Snack ->
GridSnackCard(snack = snack)
}
}
)
Row(
modifier = Modifier.padding( 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
modifier = Modifier.weight(1f),
onClick = {
if (list.size < snacks.size) {
list.add(snacks[list.size])
}
},
shape = RoundedCornerShape(8.dp)
) {
Text(text = "Add")
}
Spacer(modifier = Modifier.width(10.dp))
Button(
modifier = Modifier.weight(1f),
onClick = {
if (list.size > 0) {
list.removeLast()
}
},
shape = RoundedCornerShape(8.dp)
) {
Text(text = "Remove")
}
}
}
}

compose can not test Dialog

I have dialog in compose:
#Composable
fun testgtt() {
val saveDialogState = remember { mutableStateOf(false) }
Button(onClick = { saveDialogState.value = true }, modifier = Modifier.testTag(PLACE_TAG)) {
Text(text = "helllow")
}
Dialog(onDismissRequest = { saveDialogState.value = false }) {
Text(text = "helllow",modifier = Modifier.testTag(BUTTON_TAG))
}
}
and want to test it:
#Test
fun das(){
composeTestRule.setContent {
TerTheme {
testgtt()
}
}
composeTestRule.onRoot(useUnmergedTree = true).printToLog("currentLabelExists")
composeTestRule.onNodeWithTag(PLACE_TAG).performClick()
composeTestRule.onNodeWithTag(BUTTON_TAG).assertIsDisplayed()
}
but I get this error:
java.lang.AssertionError: Failed: assertExists.
Reason: Expected exactly '1' node but found '2' nodes that satisfy: (isRoot)
Nodes found:
1) Node #1 at (l=0.0, t=0.0, r=206.0, b=126.0)px
Has 1 child
2) Node #78 at (l=0.0, t=0.0, r=116.0, b=49.0)px
Has 1 child
Inspite of the fact that I see the Dialog itself.
The reason for this error is the line: composeTestRule.onRoot(useUnmergedTree = true).printToLog("currentLabelExists")
onRoot expects a single node, but i suspect both the containing view and the dialog each return their own root (Speculation)
A possible workaround is to instead print both root trees using something like
composeTestRule.onAllNodes(isRoot()).printToLog("currentLabelExists")
use navigation component:
#Composable
fun de(){
val navController = rememberNavController()
Scaffold { innerPadding ->
NavHost(navController, "home", Modifier.padding(innerPadding)) {
composable("home") {
// This content fills the area provided to the NavHost
val saveDialogState = remember { mutableStateOf(true) }
Button(onClick = {
navController.navigate("detail_dialog")
}, modifier = Modifier.testTag(PLACE_TAG)) {
Text(text = "helllow")
}
}
dialog("detail_dialog") {
// This content will be automatically added to a Dialog() composable
// and appear above the HomeScreen or other composable destinations
Dialog(onDismissRequest = {navController.navigate("home")}) {
Card(
shape = RoundedCornerShape(10.dp),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
// .padding(horizontal = 16.dp)
.padding(vertical = 8.dp),
elevation = 8.dp
){
Text(text = "helllow", modifier = Modifier.testTag(BUTTON_TAG))
}
}
}
}
}
}
As #DanielO said you can use the isRoot() selector, see below. That however prints out the same message as before.
A possible workaround is to instead print both root trees using something like
composeTestRule.onAllNodes(isRoot()).printToLog("currentLabelExists")
You have to distinctivly select which root you are looking for. By using the selectors:
.get( index )
.onFirst()
.onLast()
When added it should look like this:
composeTestRule.onAllNodes(isRoot()).get(1).printToLog("T:")
composeTestRule.onAllNodes(isRoot()).onFirst().printToLog("T:")
composeTestRule.onAllNodes(isRoot()).onLast().printToLog("T:")

LazyColumn list not showing in jetpack compose

I m new To Jetpack Compose , i'm trying to create a Proxy list app , used lazy column to list proxies but its now showing .. app runs successfully but result not getting , Give Me a Solution . Thx
Lib : https://github.com/gumify/hiper
#Preview
#Composable
fun App(){
val hiper = Hiper.getInstance().async() // for asynchronous requests
val Proxies = remember {mutableStateListOf<String>() }
Surface(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()) {
Column(modifier = Modifier
.background(Color.Blue)
.height(20.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Button (
modifier = Modifier.padding(all = Dp(10F)),
onClick = {
hiper.get("http://spys.me/proxy.txt") { response ->
if (response.isSuccessful) {
val lines = response.text.toString().split("\n")
for (line in lines) {
Proxies.add(line)
}
}
}
}) { Text(text = "Get Proxies") }
LazyColumn( modifier = Modifier.background( color = Color.Gray)){
items(Proxies){item ->
Text(text = item)
}
}
}
}
}
}

Strange behaviour with horizontalArrangement using JetPack Compose

I don't understand why my switches are not aligned, why it is expanding outside the screenview ? of course I didn't succeed so far to find a proper solution to align my switches with jetpack compose.
the kotlin code :
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
class ToolsActivityK2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
ToolContent_()
}
}
}
}
#Preview
#Composable
fun Preview_ToolContent_() {
ToolContent_()
}
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
subtitle,
Modifier.padding(end = 16.dp)
)
Switch(checked = false, onCheckedChange = null)
}
}
#Composable
fun ToolContent_() {
Column {
SettingsSwitch_("launch something 1")
SettingsSwitch_("launch launch launch launch again and again and again something 2")
SettingsSwitch_("launch something 3")
}
}
if I replace the switches by images, it's worse the image for line 2 does not show on screen ! I for sure miss something but I cannot figure what
Configuration infos:
Android Studio Bumblebee | 2021.1.1
classpath 'com.android.tools.build:gradle:7.1.0'
ext.kotlin_version = '1.6.10'
kotlinCompilerExtensionVersion '1.1.0-rc02'
androidx.compose.ui:ui:1.0.5
Use weight to achieve something like this,
Complete code
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
subtitle,
Modifier.padding(end = 16.dp).weight(1f)
)
Switch(checked = false, onCheckedChange = null)
}
}
Reason
I have applied background color to all the components in your code to show how they are placed.
#Composable
fun SettingsSwitch_(
subtitle: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Red)
.padding(start = 16.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
subtitle,
Modifier.background(Cyan).padding(end = 16.dp)// .weight(1f)
)
Switch(checked = false, onCheckedChange = null, modifier = Modifier.background(Green))
}
}
And this code gives the following layout.
So, you can see that the Text has occupied the full space available in the Row. Hence the Switch appears in the wrong position.
More info
If we look into the docs for Arrangement.SpaceBetween we can see,
Place children such that they are spaced evenly across the main axis,
without free space before the first child or after the last child.
Visually: 1##2##3 for LTR or 3##2##1 for RTL.
All the Arrangement options only affect the extra free space between the children. Since the is no extra free space in the second Row the Arrangement does not have any effect there.

Updating LazyColumn In Card Button Click

I am developing a Question/Answer Quiz App in JetPack Compose. I have the Card as below
Card(modifier = Modifier
.wrapContentHeight(Alignment.CenterVertically)
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(10.dp)
.width(300.dp)
.height(600.dp)
.clip(RoundedCornerShape(8.dp)),
elevation = 10.dp,
backgroundColor = Color.White
)
{
Column(
modifier = Modifier
.wrapContentHeight(Alignment.CenterVertically)
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(8.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
){
Text(
text = "Question : " + query.question_id,
style = Typography.h1
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = query.question,
style = Typography.subtitle1
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "Options",
style = Typography.h1
)
Spacer(modifier = Modifier.height(2.dp))
OptionsDetailsList(lstOptions = lstOptions)
Spacer(modifier = Modifier.height(10.dp))
Button(onClick = {
}
) {
Text(text = "View Answer")
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
The Answer List is a LazyColumn as below
#Composable
fun OptionsDetailsList(lstOptions: List<CertAnswers>){
Log.d("ListOptions Count" , lstOptions.size.toString())
LazyColumn(){
item {
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
items(lstOptions){
item ->
Text(text = item.answer,
modifier=Modifier.padding(3.dp),
style = Typography.subtitle1,
)
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
}
}
My Answers DataModel is as Below
data class CertAnswers(
#PrimaryKey (autoGenerate = true)
val id : Int,
var question_id : Int,
val ans_title: String,
val answer : String,
var isSolution: Boolean
)
With isSolution I can get whether an option is right answer.
But I want to show only on the button click and update LazyColumn to show the relevant option in different color.
I tried to call the OptionsDetailsList method again on button click but its not working.
How can i update the LazyColumn on Button Click within CardView.
You need to store a state value in your CardView indicating wether the button was tapped. rememberSaveable will make sure it's saved during recompositions and scrolling. I pass query.question_id as a key: in case it'll change most probably the value should be reinitialized. Check out more about state in compose in documentation
var answerRevealed by rememberSaveable(query.question_id) { mutableStateOf(false) }
You can change the background of the lazy column elements depending on this state. p.s. I suggest you use Modifier as the last argument, so you don't need a comma at the end and you can add/remove/reorder modifiers easily:
#Composable
fun OptionsDetailsList(answerRevealed: Boolean, lstOptions: List<CertAnswers>) {
Log.d("ListOptions Count", lstOptions.size.toString())
LazyColumn() {
item {
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
items(lstOptions) { item ->
Text(
text = item.answer,
style = Typography().subtitle1,
modifier = Modifier
.padding(3.dp)
.background(
if (answerRevealed) {
if (item.isSolution) {
Color.Green
} else {
Color.Red
}
} else {
Color.Transparent
}
)
)
Spacer(modifier = Modifier.requiredHeight(1.dp))
}
}
}
In your button just set this state to true. You can also hide the button once it's tapped, or change the text.
// hide after onClick
if (!answerRevealed) {
Button(onClick = {
answerRevealed = true
}
) {
Text(text = "View Answer")
}
}
// or change text
Button(onClick = {
answerRevealed = true
}
) {
Text(text = if (answerRevealed) "Hide Answer" else "View Answer")
}

Resources