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)
  • Navigate to it with an explicit Intent
  • Pass data with putExtra / getStringExtra
  • Return a result with ActivityResultLauncher (setResult / finish)

The ActivityResultLauncher pattern is covered in the CM7 lecture; here you wire it end-to-end, and you reuse it in the project (BE2).


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 — A Second Activity & One-Way Navigation (30 min)

Tapping a note will open a second screen showing that note. Here we focus on the navigation itself: launching an Activity and passing data to it. (Sending the edited text back to the list comes in Part 5.)

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

A full-height multi-line editor:

<?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="match_parent"
        android:inputType="textMultiLine"
        android:gravity="top"
        android:hint="@string/hint_note"
        android:textSize="18sp"/>

</LinearLayout>

Add to strings.xml:

<string name="edit_note_title">Edit Note</string>

4.3 Implement EditNoteActivity.java

Read the note text sent by MainActivity and show it in the editor:

package com.s7infa3.notespad;

import android.content.Intent;
import android.os.Bundle;
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";

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

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

        EditText editor = findViewById(R.id.editor);
        if (text != null) editor.setText(text);
    }
}

4.4 Launch it from MainActivity

Replace the editNote() placeholder:

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);
    startActivity(intent);
}

Add the missing imports (Alt+Enter).

Test: tap a note → EditNoteActivity opens, its title bar shows "Edit Note", and the editor displays the note text. Press Back to return to the list.


Part 5 — Return the edited note to the list (30 min)

Now make the editor send its result back: Save updates the note in the list and Delete removes it. This is the ActivityResultLauncher pattern from the CM7 lecture — you reuse it in the project (BE2).

To make Save and Delete update the list, the second Activity must return a result to MainActivity.

5.1 Add the buttons to activity_edit_note.xml

Give the editor height=0dp + weight=1 and add a button row below it:

    <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>

Add to strings.xml:

<string name="btn_save">Save</string>
<string name="btn_delete">Delete</string>

5.2 Return a result from EditNoteActivity

Promote noteIndex to an instance field, then wire the two buttons:

private int noteIndex;   // field — listeners need it

// inside onCreate(), after editor.setText(...):
noteIndex = getIntent().getIntExtra(KEY_INDEX, -1);

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

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);    // MUST be before finish()
    finish();
});

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

5.3 Receive the result in MainActivity

// field
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);
            }
        });

Finally, in editNote() launch with editLauncher.launch(intent) instead of startActivity(intent).


Part 6 — 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 wherever the list changes).

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 the noteIndex TextView.


Deliverable

No formal submission. Verify before leaving:

  • [ ] RecyclerView shows all notes
  • [ ] Tapping a note opens EditNoteActivity with the correct text
  • [ ] The title bar shows "Edit Note"
  • [ ] Save returns the edited text to the list; Delete removes the note
  • [ ] Back returns to the list

Keep this project — you will add persistence (CM8) in the final project or in autonomy.


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)
Editor is empty on open Extra key mismatch The putExtra key must equal the getStringExtra key (KEY_TEXT)
Data not returned Forgot setResult() before finish() setResult() MUST be called before finish()