CPS251 Android Development by Scott Shaper

The NoteScreen View

The NoteScreen composable is the main user interface (UI) for our note-taking application. This is the screen that users will interact with to view, add, edit, and delete notes. It's built using Jetpack Compose, Android's modern toolkit for building native UI.

This screen is designed to be reactive, meaning it automatically updates when the underlying data changes. It uses a NoteViewModel to manage and observe the state of our notes, ensuring that the UI always displays the most up-to-date information.

Below is the full code for the NoteScreen composable, followed by a detailed explanation of each part.


// Opt-in to use experimental Material 3 API features, like the new Scaffold.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteScreen(viewModel: NoteViewModel) {
    // Collect the list of notes from the ViewModel as a State, so UI recomposes on changes.
    val notes by viewModel.notes.collectAsState()
    // State for the title input field.
    var title by remember { mutableStateOf("") }
    // State for the content input field.
    var content by remember { mutableStateOf("") }
    // State to hold the note currently being edited, if any.
    var editingNote by remember { mutableStateOf(null) }
    // State to control the visibility of the delete confirmation dialog.
    var showDeleteDialog by remember { mutableStateOf(null) }
    // Formatter for displaying dates in a consistent format.
    val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) }
    // State for managing SnackBar messages.
    val snackbarHostState = remember { SnackbarHostState() }
    // Coroutine scope for launching suspending functions, like showing snackbars.
    val scope = rememberCoroutineScope()

    // Scaffold provides a basic screen layout with a SnackBarHost.
    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) }
    ) { padding ->
        // Main column for arranging input fields, buttons, and the note list.
        Column(modifier = Modifier
            .padding(16.dp)
            .padding(top = 50.dp)) {
            // Title for the note input section.
            Text("Add a Note", style = MaterialTheme.typography.titleLarge)
            Spacer(modifier = Modifier.height(8.dp))
            // Outlined text field for note title input.
            OutlinedTextField(
                value = title,
                onValueChange = { title = it },
                label = { Text("Title") },
                modifier = Modifier.fillMaxWidth()
            )
            Spacer(modifier = Modifier.height(4.dp))
            // Outlined text field for note content input.
            OutlinedTextField(
                value = content,
                onValueChange = { content = it },
                label = { Text("Content") },
                modifier = Modifier.fillMaxWidth()
            )
            Spacer(modifier = Modifier.height(8.dp))
            // Row for action buttons (Add/Update Note, Cancel Edit).
            Row {
                // Button to add a new note or update an existing one.
                Button(onClick = {
                    if (title.isNotBlank() && content.isNotBlank()) {
                        if (editingNote == null) {
                            // If not editing, add a new note.
                            viewModel.addNote(title, content, dateFormat.format(Date()))
                            scope.launch { snackbarHostState.showSnackbar("Note added!") }
                        } else {
                            // If editing, delete the old note and add a new one with updated content.
                            viewModel.deleteNote(editingNote!!)
                            viewModel.addNote(title, content, dateFormat.format(Date()))
                            editingNote = null
                            scope.launch { snackbarHostState.showSnackbar("Note updated!") }
                        }
                        // Clear input fields after action.
                        title = ""
                        content = ""
                    }
                }) {
                    // Button text changes based on whether a note is being edited.
                    Text(if (editingNote == null) "Add Note" else "Update Note")
                }
                // Show "Cancel Edit" button only when a note is being edited.
                if (editingNote != null) {
                    Spacer(modifier = Modifier.width(8.dp))
                    OutlinedButton(onClick = {
                        // Clear editing state and input fields on cancel.
                        editingNote = null
                        title = ""
                        content = ""
                    }) {
                        Text("Cancel Edit")
                    }
                }
            }
            Spacer(modifier = Modifier.height(16.dp))
            // Title for the list of notes.
            Text("Your Notes", style = MaterialTheme.typography.titleMedium)
            Spacer(modifier = Modifier.height(8.dp))
            // LazyColumn to efficiently display a scrollable list of notes.
            LazyColumn(modifier = Modifier.fillMaxHeight()) {
                items(notes.size) { idx ->
                    val note = notes[idx]
                    // Surface for individual note display, with a slight elevation.
                    Surface(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(vertical = 4.dp),
                        tonalElevation = 2.dp
                    ) {
                        // Row to arrange note details and action buttons horizontally.
                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(8.dp),
                            horizontalArrangement = Arrangement.SpaceBetween
                        ) {
                            // Column for displaying note title, content, and date.
                            Column(modifier = Modifier.weight(1f)) {
                                Text(note.title, style = MaterialTheme.typography.titleSmall)
                                Text(note.content, style = MaterialTheme.typography.bodyMedium)
                                Text(note.date, style = MaterialTheme.typography.bodySmall)
                            }
                            // Column for Edit and Delete buttons.
                            Column {
                                // Button to initiate editing of a note.
                                Button(onClick = {
                                    editingNote = note
                                    title = note.title
                                    content = note.content
                                }) {
                                    Text("Edit")
                                }
                                Spacer(modifier = Modifier.height(4.dp))
                                // Button to show delete confirmation dialog.
                                Button(onClick = { showDeleteDialog = note }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) {
                                    Text("Delete")
                                }
                            }
                        }
                    }
                }
            }
        }
        // Delete confirmation AlertDialog.
        if (showDeleteDialog != null) {
            AlertDialog(
                onDismissRequest = { showDeleteDialog = null },
                title = { Text("Delete Note") },
                text = { Text("Are you sure you want to delete this note?") },
                confirmButton = {
                    TextButton(onClick = {
                        // Delete the note, show snackbar, and reset states.
                        viewModel.deleteNote(showDeleteDialog!!)
                        scope.launch { snackbarHostState.showSnackbar("Note deleted!") }
                        showDeleteDialog = null
                        // If the deleted note was being edited, clear editing state.
                        if (editingNote == showDeleteDialog) {
                            editingNote = null
                            title = ""
                            content = ""
                        }
                    }) {
                        Text("Delete")
                    }
                },
                dismissButton = {
                    TextButton(onClick = { showDeleteDialog = null }) {
                        Text("Cancel")
                    }
                }
            )
        }
    }
} 

Understanding the `NoteScreen` Composable

The NoteScreen is a sophisticated UI component built with Jetpack Compose. It handles user input for creating and editing notes, displays a list of existing notes, and manages interactions like deleting notes and showing feedback. Let's break down its key parts.

1. Screen Setup and ViewModel Integration


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteScreen(viewModel: NoteViewModel) {
    val notes by viewModel.notes.collectAsState()
    var title by remember { mutableStateOf("") }
    var content by remember { mutableStateOf("") }
    var editingNote by remember { mutableStateOf<Note?>(null) }
    var showDeleteDialog by remember { mutableStateOf<Note?>(null) }
    val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) }
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) }
    ) { padding ->
        // ... UI content ...
    }
}

2. Note Input and Action Buttons


    Column(modifier = Modifier
        .padding(16.dp)
        .padding(top = 50.dp)) {
        Text("Add a Note", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(8.dp))
        OutlinedTextField(
            value = title,
            onValueChange = { title = it },
            label = { Text("Title") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(4.dp))
        OutlinedTextField(
            value = content,
            onValueChange = { content = it },
            label = { Text("Content") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(8.dp))
        Row {
            Button(onClick = { // ... add/update logic ... }) {
                Text(if (editingNote == null) "Add Note" else "Update Note")
            }
            if (editingNote != null) {
                Spacer(modifier = Modifier.width(8.dp))
                OutlinedButton(onClick = { // ... cancel logic ... }) {
                    Text("Cancel Edit")
                }
            }
        }
    }

3. Displaying the List of Notes


    Text("Your Notes", style = MaterialTheme.typography.titleMedium)
    Spacer(modifier = Modifier.height(8.dp))
    LazyColumn(modifier = Modifier.fillMaxHeight()) {
        items(notes.size) { idx ->
            val note = notes[idx]
            Surface(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 4.dp),
                tonalElevation = 2.dp
            ) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(8.dp),
                    horizontalArrangement = Arrangement.SpaceBetween
                ) {
                    Column(modifier = Modifier.weight(1f)) {
                        Text(note.title, style = MaterialTheme.typography.titleSmall)
                        Text(note.content, style = MaterialTheme.typography.bodyMedium)
                        Text(note.date, style = MaterialTheme.typography.bodySmall)
                    }
                    Column {
                        Button(onClick = { // ... edit logic ... }) {
                            Text("Edit")
                        }
                        Spacer(modifier = Modifier.height(4.dp))
                        Button(onClick = { // ... delete dialog logic ... }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) {
                            Text("Delete")
                        }
                    }
                }
            }
        }
    }

4. Delete Confirmation Dialog


    if (showDeleteDialog != null) {
        AlertDialog(
            onDismissRequest = { showDeleteDialog = null },
            title = { Text("Delete Note") },
            text = { Text("Are you sure you want to delete this note?") },
            confirmButton = {
                TextButton(onClick = { // ... delete logic ... }) {
                    Text("Delete")
                }
            },
            dismissButton = {
                TextButton(onClick = { showDeleteDialog = null }) {
                    Text("Cancel")
                }
            }
        )
    }

Tips for Success

Common Mistakes to Avoid

Best Practices