'Why is LazyColumn not recomposing?

Link to the video of the problem: Video Link`

Sometimes many notes delete at once and the add functionality is not working. When I swipe the Lazy Column does not recompose and put everything in order again.

This is my code:

NoteButton:

@Composable
fun NoteButton(
    modifier: Modifier = Modifier,
    text: String,
    enabled: Boolean = true,
    shape: Shape = CircleShape,
    onClick: () -> Unit
) {
    Button(
        modifier = modifier,
        enabled = enabled,
        shape = shape,
        onClick = onClick
    ) {
        Text(text)
    }
}

NoteInputText:

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun NoteInputText(
    modifier: Modifier = Modifier,
    text: String,
    label: String,
    maxLines: Int = 1,
    onTextChange: (String) -> Unit,
    onImeAction: () -> Unit = {},
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(
        imeAction = if (maxLines == 1) ImeAction.Done else ImeAction.None
    )
) {

    val keyboardController = LocalSoftwareKeyboardController.current

    TextField(
        modifier = modifier,
        value = text,
        onValueChange = onTextChange,
        maxLines = maxLines,
        label = { Text(text = label) },
        keyboardOptions = keyboardOptions,
        keyboardActions = KeyboardActions(
            onDone = {
                onImeAction()
                keyboardController?.hide()
            }
        )
    )
}

NoteItem:

private const val TAG = "NoteItem"

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NoteItem(modifier: Modifier = Modifier, note: Note, onSwipe: (Note) -> Unit) {

    val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
        if (it == DismissValue.DismissedToStart) {
            Log.d(TAG, "NoteItem: ${note.id}")
            onSwipe(note)
        }
        true
    }

    SwipeToDismiss(
        state = dismissState,
        directions = setOf(DismissDirection.EndToStart),
        background = {}
    ) {
        Card(
            modifier = modifier
                .padding(5.dp)
                .fillMaxWidth(),
            shape = RoundedCornerShape(topEnd = 20.dp, bottomEnd = 20.dp),
            elevation = 5.dp
        ) {
            Column(modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp)) {
                Text(text = note.title, style = MaterialTheme.typography.h5)
                Text(text = note.description, style = MaterialTheme.typography.body1)
            }
        }
    }
}

NoteList:

private const val TAG = "NoteList"

@Composable
fun NoteList(modifier: Modifier = Modifier, onSwipe: (Note) -> Unit) {

    val noteViewModel: NoteViewModel = viewModel()
    val notes = noteViewModel.getAllNotes()

    if(notes.isNotEmpty()) {
        LazyColumn(modifier = modifier.padding(5.dp)) {
                items(notes) { note ->
                    NoteItem(note = note, onSwipe = { onSwipe(note) })
                }
        }
    }
}

Note:

data class Note(
    val id: Long = assignIdForNote(),
    val title: String,
    val description: String,
    val entryDate: LocalDateTime = LocalDateTime.now()
)

fun getNoteData() = mutableListOf(
    Note(title = "A good day", description = "We went on a vacation by the lake"),
    Note(title = "Android Compose", description = "Working on Android Compose course today"),
    Note(title = "Keep at it...", description = "Sometimes things just happen"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family today"),
    Note(title = "A movie day", description = "Watching a movie with family")
)

NoteViewModel:

class NoteViewModel: ViewModel() {

    private var notesList = mutableStateListOf<Note>()

    init {
        notesList.addAll(getNoteData())
    }

    fun addNote(note: Note) {
        notesList.add(note)
    }
    fun deleteNote(note: Note) {
        notesList.removeIf { it.id == note.id }
    }
    fun getAllNotes() = notesList
}

Navigation:

@Composable
fun Navigation() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = Screen.NotesScreen.name) {
        composable(Screen.NotesScreen.name) {
            NotesScreen(navController)
        }
        composable(Screen.AddScreen.name) {
            AddScreen(navController)
        }
    }
}

AddScreen:

private const val TAG = "AddScreen"

@Composable
fun AddScreen(navController: NavController) {

    val noteViewModel: NoteViewModel = viewModel()

    var title by remember {
        mutableStateOf("")
    }

    var description by remember {
        mutableStateOf("")
    }

    Log.d(TAG, "AddScreen: Title: $title")
    Log.d(TAG, "AddScreen: Description: $description")

    Column {

        TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) },
            navigationIcon = {
                IconButton(onClick = { navController.popBackStack() }) {
                    Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Go back")
                }
            })

        Column(
            modifier = Modifier
                .padding(10.dp)
                .fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            NoteInputText(
                modifier = Modifier.background(Color.White),
                text = title,
                label = "Title",
                onTextChange = {
                    title = it
                }
            )
            NoteInputText(
                modifier = Modifier.background(Color.White),
                text = description,
                label = "Description",
                onTextChange = {
                    description = it
                }
            )
            NoteButton(text = "Add") {
                if(title.isNotBlank() && description.isNotBlank()) {
                    Log.d(TAG, "AddScreen: Cool, $title, $description")
                    noteViewModel.addNote(Note(title = title, description = description))
                    navController.popBackStack()
                }
            }
        }
    }
}

NotesScreen:

private const val TAG = "NotesScreen"

@Composable
fun NotesScreen(navController: NavController) {

val noteViewModel: NoteViewModel = viewModel()
val notes = noteViewModel.getAllNotes()

Column {
    TopAppBar(
        title = { Text(text = stringResource(id = R.string.app_name)) },
        actions = {
            IconButton(onClick = { navController.navigate(Screen.AddScreen.name) }) {
                Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
            }
        }
    )
    if (notes.isNotEmpty()) {
        NoteList(onSwipe = {
            noteViewModel.deleteNote(it)
        })
    } else {
        Text(
            modifier = Modifier.padding(10.dp),
            text = "Click on the + icon to add a note",
            style = MaterialTheme.typography.h5
        )
    }
}

}

Screen:

enum class Screen {
    NotesScreen, AddScreen
}

Util:

private var previousId = 0L

fun assignIdForNote() = previousId++

MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NoteAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Navigation()
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    NoteAppTheme {
        Navigation()
    }
}


Solution 1:[1]

The reason your add functionality is not working is you were operating on a independent NoteViewModel on AddScreen, which is different with NotesScreen's NoteViewModel. You can check both noteViewModel's hashCode to verify.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 聂超群