Skip to content

TD7 — NotesPad v2: RecyclerView & Navigation

← Part 3 Overview

S7 Inf A3 — Java for Graphical and Mobile Programming
Stéphane Derrode — Centrale Lyon
Part 3 — Android
Duration: 2h · Continue the NotesPad project from TD6


Objectives

  • Replace the status TextView with a scrollable RecyclerView
  • Write a custom Adapter and ViewHolder
  • Create a second Activity (EditNoteActivity) for note editing
  • Navigate between Activities with an explicit Intent
  • Pass data with putExtra / getStringExtra
  • Return a result with ActivityResultLauncher

Part 1 — Add RecyclerView to the Layout (20 min)

1.1 Add the dependency

Open app/build.gradle and add inside the dependencies block:

implementation 'androidx.recyclerview:recyclerview:1.3.1'

Click Sync Now when prompted.

1.2 Update activity_main.xml

Replace the file content:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <EditText
        android:id="@+id/noteInput"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/hint_note"
        android:inputType="text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/addButton"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginEnd="8dp"/>

    <Button
        android:id="@+id/addButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btn_add"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBaseline_toBaselineOf="@id/noteInput"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/notesList"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/noteInput"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="12dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

1.3 Create item_note.xml

Right-click res/layout/New → Layout Resource File → name it item_note.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp"
    android:background="?attr/selectableItemBackground">

    <TextView
        android:id="@+id/noteText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="16sp"/>

    <TextView
        android:id="@+id/noteIndex"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:textColor="#BDBDBD"
        android:layout_marginStart="8dp"/>

</LinearLayout>

Part 2 — Write the Adapter (30 min)

Create a new Java class: right-click your package → New → Java ClassNoteAdapter.

package com.s7infa3.notespad;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class NoteAdapter extends
        RecyclerView.Adapter<NoteAdapter.NoteHolder> {

    private List<String> notes;
    private OnNoteClick  listener;

    // Click callback interface
    public interface OnNoteClick {
        void onClick(int position);
    }

    public NoteAdapter(List<String> notes, OnNoteClick listener) {
        this.notes    = notes;
        this.listener = listener;
    }

    // --- ViewHolder: holds the views for one row ---
    static class NoteHolder extends RecyclerView.ViewHolder {
        TextView noteText, noteIndex;

        NoteHolder(View v) {
            super(v);
            noteText  = v.findViewById(R.id.noteText);
            noteIndex = v.findViewById(R.id.noteIndex);
        }
    }

    // --- Called to create a new row view ---
    @NonNull
    @Override
    public NoteHolder onCreateViewHolder(
            @NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_note, parent, false);
        return new NoteHolder(v);
    }

    // --- Called to fill a row with data ---
    @Override
    public void onBindViewHolder(
            @NonNull NoteHolder holder, int position) {
        holder.noteText.setText(notes.get(position));
        holder.noteIndex.setText("#" + (position + 1));

        // Open edit screen on click
        holder.itemView.setOnClickListener(
            v -> listener.onClick(position));
    }

    @Override
    public int getItemCount() {
        return notes.size();
    }
}

Part 3 — Connect the RecyclerView (15 min)

Update MainActivity.java — replace onCreate() and the addNote() method:

private NoteAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    noteInput = findViewById(R.id.noteInput);
    Button addButton = findViewById(R.id.addButton);

    // Set up RecyclerView
    RecyclerView rv = findViewById(R.id.notesList);
    rv.setLayoutManager(new LinearLayoutManager(this));
    adapter = new NoteAdapter(notes, pos -> editNote(pos));
    rv.setAdapter(adapter);

    addButton.setOnClickListener(v -> addNote());
}

private void addNote() {
    String text = noteInput.getText().toString().trim();
    if (text.isEmpty()) {
        Toast.makeText(this, R.string.error_empty,
            Toast.LENGTH_SHORT).show();
        return;
    }
    notes.add(text);
    noteInput.setText("");
    adapter.notifyItemInserted(notes.size() - 1);
    Log.d(TAG, "Note added: " + text);
}

private void editNote(int position) {
    // TODO in Part 4 — launch EditNoteActivity
    Toast.makeText(this, "Edit: " + notes.get(position),
        Toast.LENGTH_SHORT).show();
}

Add the missing imports (Android Studio will suggest them with Alt+Enter).

Test: run the app, add several notes — they should appear in a scrollable list. Tapping shows a Toast with the note text.


Part 4 — Create EditNoteActivity (30 min)

4.1 Create the Activity

File → New → Activity → Empty Views Activity - Activity Name: EditNoteActivity - Layout Name: activity_edit_note - Leave other options as default → Finish

Android Studio creates EditNoteActivity.java, activity_edit_note.xml, and registers it in AndroidManifest.xml.

4.2 Edit activity_edit_note.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/editor"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:inputType="textMultiLine"
        android:gravity="top"
        android:hint="@string/hint_note"
        android:textSize="18sp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="12dp">

        <Button
            android:id="@+id/saveButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/btn_save"
            android:layout_marginEnd="8dp"/>

        <Button
            android:id="@+id/deleteButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/btn_delete"
            android:backgroundTint="#E53935"/>

    </LinearLayout>

</LinearLayout>

Add to strings.xml:

<string name="btn_save">Save</string>
<string name="btn_delete">Delete</string>
<string name="edit_note_title">Edit Note</string>

4.3 Implement EditNoteActivity.java

package com.s7infa3.notespad;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;

public class EditNoteActivity extends AppCompatActivity {

    public static final String KEY_TEXT  = "NOTE_TEXT";
    public static final String KEY_INDEX = "NOTE_INDEX";

    private int noteIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit_note);
        setTitle(R.string.edit_note_title);

        // Retrieve data sent by MainActivity
        Intent intent = getIntent();
        noteIndex     = intent.getIntExtra(KEY_INDEX, -1);
        String text   = intent.getStringExtra(KEY_TEXT);

        EditText editor    = findViewById(R.id.editor);
        Button saveButton  = findViewById(R.id.saveButton);
        Button deleteButton= findViewById(R.id.deleteButton);

        if (text != null) editor.setText(text);

        saveButton.setOnClickListener(v -> {
            String updated = editor.getText()
                                   .toString().trim();
            Intent result = new Intent();
            result.putExtra(KEY_TEXT, updated);
            result.putExtra(KEY_INDEX, noteIndex);
            result.putExtra("ACTION", "save");
            setResult(RESULT_OK, result);
            finish();
        });

        deleteButton.setOnClickListener(v -> {
            Intent result = new Intent();
            result.putExtra(KEY_INDEX, noteIndex);
            result.putExtra("ACTION", "delete");
            setResult(RESULT_OK, result);
            finish();
        });
    }
}

4.4 Connect from MainActivity

Replace the editNote() method and add the launcher in MainActivity.java:

// Add this field at the top of MainActivity
private ActivityResultLauncher<Intent> editLauncher =
    registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> {
            if (result.getResultCode() != RESULT_OK
                    || result.getData() == null) return;

            Intent data  = result.getData();
            int index    = data.getIntExtra(
                EditNoteActivity.KEY_INDEX, -1);
            String action = data.getStringExtra("ACTION");

            if (index < 0 || action == null) return;

            if ("save".equals(action)) {
                String updated = data.getStringExtra(
                    EditNoteActivity.KEY_TEXT);
                if (updated != null && !updated.isEmpty()) {
                    notes.set(index, updated);
                    adapter.notifyItemChanged(index);
                }
            } else if ("delete".equals(action)) {
                notes.remove(index);
                adapter.notifyItemRemoved(index);
            }
        });

private void editNote(int position) {
    Intent intent = new Intent(this, EditNoteActivity.class);
    intent.putExtra(EditNoteActivity.KEY_TEXT,
        notes.get(position));
    intent.putExtra(EditNoteActivity.KEY_INDEX, position);
    editLauncher.launch(intent);
}

Test: tap a note → EditNoteActivity opens with the note text. Edit and save → list updates. Delete → note removed from list.


Part 5 — Exploration (remaining time)

5.1 Add a back arrow to EditNoteActivity: in AndroidManifest.xml, add android:parentActivityName=".MainActivity" to the EditNoteActivity entry.

5.2 Add an empty state: when notes is empty, show a TextView saying "No notes yet — add one above!" and hide it when there is at least one note. Update it in addNote() and in the result handler.

5.3 Add note creation date: instead of List<String>, use a List<String[]> where [0] is the text and [1] is the date (new SimpleDateFormat("dd/MM HH:mm").format(new Date())). Display the date in noteIndex TextView.


Deliverable

No formal submission. Verify before leaving:

  • [ ] RecyclerView shows all notes
  • [ ] Tapping a note opens EditNoteActivity with the correct text
  • [ ] Saving returns the updated text to the list
  • [ ] Deleting removes the note from the list
  • [ ] App title shows correctly in EditNoteActivity

Keep this project — you will add persistence in the final project or in autonomy using the patterns from CM8.


Common Errors

Error Cause Fix
List shows but items are blank noteText ID doesn't match item_note.xml Check R.id.noteText matches android:id in XML
Tap → nothing happens Listener not set in onBindViewHolder Check holder.itemView.setOnClickListener(...)
EditNoteActivity not found Not declared in Manifest Use File → New → Activity (auto-registers)
Data not returned Forgot setResult() before finish() setResult() MUST be called before finish()
notifyItemChanged wrong index Position changed after deletion Use adapter.notifyDataSetChanged() as a fallback