I 'm trying to go lazycolumn last item because I have a lazycolumn and empty list. The application I made is a chatbot application, there are bot messages and user messages. When the bot gives a message, it is added to the list
, at the same time, when the user enters a reply, it is added to the list
and as it is added to this list
, the empty list starts to fill up and lazycolumn is also displayed, but it is a separate thing that should be in a correspondence application. As messages are added, that is, when the end of the screen is reached, the screen needs to slide down. I used scrollToEnd
or something similar to do this, but my project does not see scrollToEnd
and gives the following error Unresolved reference: scrollToEnd
or for example isScrolledToEnd()
When I use this, I wonder if there is a separate item that I should add. Is there a dependency or are they obsolete? can you help me solve this issue
var index = 1
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun FirstScreen() {
val list = remember { mutableStateListOf<Message>() }
val UserList = remember { mutableStateListOf<Message>() }
val botList = listOf("Peter", "Francesca", "Luigi", "Igor")
val random = (0..3).random()
val botName: String = botList[random]
val hashMap: HashMap<String, String> = HashMap<String, String>()
val coroutineScope = rememberCoroutineScope()
customBotMessage(message = Message1, list)
Column(modifier = Modifier.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top) {
Image(
painter = painterResource(id = R.drawable.diyetkolik_logo),
contentDescription = "logo",
modifier = Modifier.padding(10.dp)
)
Divider()
val listState = rememberLazyListState()
LazyColumn(modifier = Modifier.fillMaxSize(),
state = listState) {
println("size:"+list.size)
items(list.size) { i ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement =
if (list[i].id == RECEIVE_ID)
Arrangement.Start
else
Arrangement.End
) {
if (list[i].id == RECEIVE_ID) {
Item(
message = list[i],
botName,
botcolor,
list,
simpleTextFlag = true,
hashMap,
modifier = Modifier
.padding(
start = 32.dp,
end = 4.dp,
top = 4.dp
),
)
} else {
Item(
message = list[i],
"user",
usercolor,
UserList,
simpleTextFlag = false,
hashMap,
Modifier.padding(
start = 4.dp,
end = 32.dp,
top = 4.dp
)
)
}
}
}
}
val endOfListReached = remember {
derivedStateOf {
listState.isAtBottom() // error occurs @Composable invocations can only happen from the context of a @Composable function
}
}
// act when end of list reached
LaunchedEffect(endOfListReached) {
// do your stuff
}
}
}
private fun customBotMessage(message: String, list: SnapshotStateList<Message>) {
GlobalScope.launch {
delay(1000)
withContext(Dispatchers.Main) {
list.add(
Message(
message,
RECEIVE_ID,
Timestamp(System.currentTimeMillis()).toString()
)
)
}
}
}
@Composable
fun Item(
message: Message,
person: String,
color: Color,
list: SnapshotStateList<Message>,
simpleTextFlag: Boolean,
hashMap: HashMap<String, String>,
modifier: Modifier
) {
Column(verticalArrangement = Arrangement.Top) {
Card(
modifier = Modifier
.padding(10.dp),
backgroundColor = color,
elevation = 10.dp
) {
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier.padding(3.dp)
) {
Text(
buildAnnotatedString {
withStyle(
style = SpanStyle(
fontWeight = FontWeight.Medium,
color = Color.Black
)
) {
append("$person: ")
}
},
modifier = Modifier
.padding(4.dp)
)
Text(
buildAnnotatedString {
withStyle(
style = SpanStyle(
fontWeight = FontWeight.W900,
color = Color.White//Color(/*0xFF4552B8*/)
)
)
{
append(message.message)
}
}
)
}
}
if (simpleTextFlag)
SimpleText(list = list, hashMap)
}
}
@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable
fun SimpleText(list: SnapshotStateList<Message>, hashMap: HashMap<String, String>) {
val coroutineScope = rememberCoroutineScope()
val keyboardController = LocalSoftwareKeyboardController.current
val bringIntoViewRequester = remember { BringIntoViewRequester() }
var visible by remember { mutableStateOf(true) }
var text by remember { mutableStateOf("") }
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInHorizontally(),
exit = fadeOut() + slideOutHorizontally()
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.End,
modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester)
) {
OutlinedTextField(
modifier = Modifier
.padding(2.dp)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
bringIntoViewRequester.bringIntoView()
}
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
value = text,
onValueChange = { text = it },
shape = RoundedCornerShape(12.dp),
label = { Text("send message") })
IconButton(onClick = {
if (text.isNotEmpty()) {
//Adds it to our local list
list.add(
Message(
text,
Constants.SEND_ID,
Timestamp(System.currentTimeMillis()).toString()
)
)
hashMap.put(Messages.listOfMessageKeys[index - 1], text)
customBotMessage(listOfMessages[index], list)
index += 1
visible = false
}
text = ""
}) {
Icon(
modifier = Modifier.padding(2.dp),
painter = painterResource(id = R.drawable.ic_baseline_send_24),
contentDescription = "send message img"
)
}
}
}
}
private fun customBotMessage(message: String, list: SnapshotStateList<Message>) {
GlobalScope.launch {
delay(1000)
withContext(Dispatchers.Main) {
list.add(
Message(
message,
Constants.RECEIVE_ID,
Timestamp(System.currentTimeMillis()).toString()
)
)
}
}
}
I know it's badly written code and you might have trouble understanding it, so I'll explain how the code works for you. Briefly, the first screen that opens is FirstScreen and there is an empty list that I defined here, and by empty list I mean val list = remember { mutableStateListOf<Message>() }
in FirstScreen. First, I call the customBotMessage
function to interact with the user, and it adds the first message in the message list I prepared. When added to the list
, it enters the lazycolumn and is displayed on the screen as an Item
.
When going to Item
, there is a message box in the Item
and a textfield just below the message box where the user can enter an input, if the message incoming to Item
is a user message, show this input textfield. I checked this with the id
's in the if
states in the lazycolumn in FirstScreen, you can understand if you look at the FirstScreen. And when the user enters a value in that input textfield, it is added to the same list
and displayed on the screen, but as messages are added to the list
, I want the screen to scroll down, as I said above, how can I do this in my code ?
EDIT
@GabrieleMariotti
Thanks for your answer, but I guess I didn't explain exactly what I wanted or I didn't understand, sorry. I have a list and messages are added to that list in order, and as they are added, they appear on the screen, so I don't have 100 messages in the list at once. As in a correspondence application, the messages slide down the screen and when it reaches the bottom of the screen, the recently added messages are not visible. What I want is that the user can see the last added message, that is, the screen scrolls to the bottom so that the user can see the last added message. I did the answer you gave but it gave the following error
@Composable invocations can only happen from the context of a @Composable function
and I couldn't figure out exactly how to use it in my question. If your answer is the last index, it returns true, otherwise it returns false. If this is exactly the answer to my question, I couldn't figure out how and where to use it in my code.Could it be a problem with lazycolumn because lazycolumn is not supposed to do this task automatically? thanks for your answer