TD7 — NotesPad v2: RecyclerView & Navigation
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
AdapterandViewHolder - 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:
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 Class → NoteAdapter.
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
EditNoteActivitywith 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 |