Before you start
By the end you can
- hand a view a listener, as an anonymous class or a lambda;
- read and check typed text as it changes; answer with a Toast;
- delete a card with a long press; compare
android:onClickwith a listener.
- Java MainActivity.java
- Layout activity_main.xml
- Resources strings.xml
- Emulator
- Logcat
Behind, or a fresh project? Start from here about 4 min
The project: your own from Part 6, or a fresh one: Part 0, steps 0.3 and 0.4 (about 5 minutes).
Your package name, for the code of this page:
Without JavaScript, the code says com.yourname.notes: replace yourname by the name you chose.
Gradle: check these 3 lines. Add any that is missing, then click Sync Now. Keep your own version numbers.
Gradle Scripts › libs.versions.tomlAdd the marked lines[versions]agp = "9.5.0"junit = "4.13.2"junitVersion = "1.3.0"espressoCore = "3.7.0"appcompat = "1.8.0"material = "1.14.0"constraintlayout = "2.2.2"
[libraries]junit = { group = "junit", name = "junit", version.ref = "junit" }ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }material = { group = "com.google.android.material", name = "material", version.ref = "material" }constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]android-application = { id = "com.android.application", version.ref = "agp" }Gradle Scripts › build.gradle.kts (Module :app)Add the marked linesplugins { alias(libs.plugins.android.application)}
android { namespace = "com.yourname.notes" compileSdk { version = release(37) }
defaultConfig { applicationId = "com.yourname.notes" minSdk = 24 targetSdk = 37 versionCode = 1 versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
buildTypes { release { optimization { enable = true packageScope = setOf("androidx.**", "kotlin.**", "kotlinx.**") } } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 }}
dependencies { implementation(libs.appcompat) implementation(libs.material) implementation(libs.constraintlayout) testImplementation(libs.junit) androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.ext.junit)}
lab1-part7-res.zip zip · 8.7 KBresand manifest: download the pack and open it in Explorer. Drag itsappfolder onto your project folder (C:\Users\<you>\), then choose Replace the files in the destination. Not Extract All: it makes a folder inside and replaces nothing. Then, in res › values › themes, delete themes.xml (night) if it is still there.AndroidStudioProjects\ Notes Java: open MainActivity.java, select all, then paste this file. Missing? First right-click kotlin+java › com.yourname.notes, the one without
(test), then NewJava Class: typeMainActivity, then press Enter.kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole filepackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.LinearLayout;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount(); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}▶. You are ready when the phone shows three cards and the pill says 3.
Step 1 of 6
Hand Add a listener
Goal Make Add answer a tap: its listener writes “Add tapped” in Logcat.
- Java kotlin+java › com.yourname.notes › MainActivity.java
- Emulator
- Logcat package:mine tag:Lifecycle
-
A variable for the Add button.
- In MainActivity.java, click at the end of
updateCount();, the last line insideonCreate(). Press Enter twice. - Type
Button buttonAdd = findViewById(R.id.buttonAdd); Buttonis red? Click it, press Alt+Enter, then choose Import class.
- In MainActivity.java, click at the end of
-
Type Its listener: an anonymous class that logs “Add tapped”.
- Click at the end of line 43, then press Enter.
- Type
buttonAdd.setOnClickListener(newand a space: Android Studio adds the). A list is open? Press Esc. - Press Ctrl+Shift+Space: a list opens,
View.OnClickListener{...}first. Press Enter. - Android Studio writes lines 44 to 49, with
viewwhere the panel saysv: both work. InonClick(), typeLog.d(TAG, "Add tapped");
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.LinearLayout;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "Add tapped"); } }); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}new View.OnClickListener() { … }is an anonymous class: a class with no name, written where it is used. Coming from Swing? It is the anonymous class you passed toaddActionListener. -
Run the app, then tap Add twice.
- Click ▶ Run 'app', in the toolbar.
- In Logcat, click Clear Logcat: the trash icon at the top of its left column.
- In Running Devices, tap Add twice.
You should see10-31 18:55:04.426 4031 4031 D Lifecycle: Add tapped 10-31 18:55:05.458 4031 4031 D Lifecycle: Add tappedYour times and process numbers will differ.
-
Turn the anonymous class into a lambda.
- In MainActivity.java, click inside
View.OnClickListener, on line 44. - Press Alt+Enter: a list opens, Replace with lambda first.
MainActivity.java Editor · centreFull size
new View.OnClickListener(): the caret here, then Alt+Enter.- Replace with lambda.
- In MainActivity.java, click inside
-
Accept it: one line instead of six.
- Press Enter: lines 44 to 49 become one line, 44.
MainActivity.java Editor · centre
package com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.LinearLayout;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> Log.d(TAG, "Add tapped")); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}v (or view) is the tapped view, here Add.
Why A listener is an object Android calls when the event happens: here, a tap on Add. A lambda writes it in one line.
Done I see “Add tapped” at each tap, then a one-line listener.
Stuck? 2 symptoms · the file
- No “Add tapped” line when you tap Add The
setOnClickListenerline is missing, or the id is wrong. Compare with the file below. - Your lines show next to many others in Logcat The query lost its filter. Type
package:mine tag:Lifecyclein it, as in Part 5.
The files at the end of this step
package com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.LinearLayout;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> Log.d(TAG, "Add tapped")); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}Step 2 of 6
Check the title, then add the note
Goal Add a typed note as a card, or show the red line.
- Java MainActivity.java
- Resources res › values › strings.xml
- Emulator
-
Paste Four views: imports, variables and
findViewByIdlines.- Click Copy 1 line, right of After line 7.
- In MainActivity.java, click at the end of line 7, press Enter, then Ctrl+V.
- The same for After line 10, 29 and 49.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer()); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }} -
Type
addNoteFromComposer(): no title, the red line; a title, a Toast.- On line 56, select
Log.d(TAG, "Add tapped"), then typeaddNoteFromComposer()over it. - Click at the end of line 77, the
}that closesaddNote(). Press Enter twice. - Type lines 79 to 84, with Enter between them: Android Studio writes both
}. - Click after the
}of line 85, press Enter, then type line 86.note_addedstays red until action d.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer()); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}trim()drops the spaces at either end. A Toast floats, then fades. - On line 56, select
-
Paste The rest of
addNoteFromComposer().- Click Copy 5 lines.
- Click at the end of line 85, press Enter, then Ctrl+V.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer()); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }} -
Paste The string “Note added”, in strings.xml.
- Double-click res › values › strings.xml.
- Click Copy 1 line.
- Click at the end of line 13, press Enter, then Ctrl+V.
res › values › strings.xmlAdd the marked lines<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources> -
Run the app, then, for each row:
- Click ▶ Run 'app'.
- On the phone, click Title, then type the row’s Title.
- Click Write something…, then type the row’s Write something… cell.
- Tap Add.
-
Rows 1 to 3, in order.
Title Write something… You should see 1 (nothing) (nothing) red “Give your note a title first.”; still 3 cards 2 Groceries (nothing) Toast “Note added”; a peach card on top, no text line; pill 4; red line gone 3 Call Yasmine About the project, before Friday. a butter card on top, with its text; pill 5

You should see Row 1: red line. Row 2: Toast, peach card.

Done I see the 3 rows behave as the table says.
Stuck? 2 symptoms · the files
- Add adds nothing, and Logcat still shows “Add tapped” The lambda still calls
Log.d. Replace it withaddNoteFromComposer(). - The card of row 2 is not peach Do rows 1 to 3 in order, right after ▶: each new card takes the next colour.
The files at the end of this step
package com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer()); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources>Step 3 of 6
Follow the typing with a TextWatcher
Goal Make the counter follow your typing, red from 120 characters.
- Resources res › values › strings.xml
- Java MainActivity.java
- Emulator
-
Paste The string
counter, in strings.xml.- Click Copy 1 line, in the panel.
- In strings.xml, click at the end of line 8, press Enter, then Ctrl+V.
res › values › strings.xmlAdd the marked lines<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="counter">%1$d / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources>%1$d: the first number given togetString(). -
Paste Two imports.
- Click Copy 2 lines, in the panel.
- In MainActivity.java, click at the end of line 3, press Enter, then Ctrl+V.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }} -
Paste
formWatcher, aTextWatcher.- Click Copy 15 lines, in the panel.
- Click at the end of line 36, press Enter, then Ctrl+V.
updateForm()stays red until action e.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}A
TextWatcherhas 3 methods: no lambda. Implement methods would write them otherwise: paste this block. -
Paste Hand it to both fields.
- Click Copy 3 lines, in the panel.
- Click at the end of line 73, in
onCreate(), press Enter, then Ctrl+V.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}set…Listenerreplaces a listener,add…Listeneradds one: oneTextWatcherserves both fields. -
Type
updateForm(): the counter’s text and colour.- Click at the end of line 112, the
}that closesaddNoteFromComposer(). Press Enter twice. - Type lines 114 to 120, with Enter between them: both
}arrive by themselves.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}setTextColor()needs a colour, not its id:getColor()gives it. - Click at the end of line 112, the
-
Row 4’s sentence, on the phone.
- Click ▶ Run 'app'.
- Triple-click row 4’s sentence below, then press Ctrl+C.
- On the phone, click Write something…, then press Ctrl+V: the counter turns red.
- Press Backspace 6 times:
119 / 140, no longer red.
Row 4 Title Write something… You should see 4 (nothing) Buy milk, eggs and bread. Call the bank before noon. Print the lab report, then revise the lifecycle and the intents tonight. the counter 125 / 140, in red
You should see The counter moves at each key, red from 120.

Done I see the counter move, red from 120.
Stuck? 2 symptoms · the files
- The counter does not move The
TextWatcheris added to one field only. Add it to both, at the end ofonCreate(). - The counter turns an odd colour; lint says
Should pass resolved color instead of resource id here: getResources().getColor(colour)setTextColor(R.color.…)passes an id. WritegetColor(colour).
The files at the end of this step
package com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="counter">%1$d / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources>Step 4 of 6
Delete a card with a long press
Goal Hold a card (mouse button down) to delete it, and update the pill.
- Resources res › values › strings.xml
- Java MainActivity.java
- Emulator
-
Paste The string
note_deleted, in strings.xml.- Click Copy 1 line, in the panel.
- In strings.xml, click at the end of line 15, press Enter, then Ctrl+V.
res › values › strings.xmlAdd the marked lines<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="counter">%1$d / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="note_deleted">Note deleted</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources> -
Type A long-press listener on each card, in
addNote().- In MainActivity.java, click at the end of line 94,
notesAdded++;. Press Enter twice. - Type lines 96 to 100, with Enter between them.
- Android Studio writes line 101 as
}): click at its end, then type;.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
card.setOnLongClickListener(v -> { notesContainer.removeView(card); updateCount(); Toast.makeText(this, R.string.note_deleted, Toast.LENGTH_SHORT).show(); return true; });
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}A lambda of several lines takes braces.
return truemeans “I handled the press”. Each card gets its own listener, soremoveView(card)removes the card you hold. - In MainActivity.java, click at the end of line 94,
-
Add Groceries again, then hold its card.
- Click ▶ Run 'app'.
- On the phone, click Title, type
Groceries, then tap Add. - Press the mouse button on the Groceries card and keep it down: the card goes.

The long press: the Toast at real size -
Delete every card.
- Hold each card the same way, until none is left.

You should see On the long press: the Toast; pill 4 → 3. Then pill 0, the empty message.
Done I see pill 0 and the message “No notes yet.”
Stuck? 2 symptoms · the files
- The app closes when you hold a card:
NullPointerException … ViewParent.The lambda returnsshowContextMenuForChild … false. Returntrue. - The card goes, but the pill does not change The listener does not call
updateCount(). Add it afterremoveView(card).
The files at the end of this step
package com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
card.setOnLongClickListener(v -> { notesContainer.removeView(card); updateCount(); Toast.makeText(this, R.string.note_deleted, Toast.LENGTH_SHORT).show(); return true; });
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}<resources> <string name="app_name">Notes</string> <string name="logo_description">Notes logo</string> <string name="count_start">0</string> <string name="title_hint">Title</string> <string name="body_hint">Write something…</string> <string name="error_no_title">Give your note a title first.</string> <string name="counter_start">0 / 140</string> <string name="counter">%1$d / 140</string> <string name="clear">Clear</string> <string name="add">Add</string> <string name="your_notes">YOUR NOTES</string> <string name="list_hint">Tap to open · hold to delete</string> <string name="empty">No notes yet.\nWrite your first one above.</string> <string name="note_added">Note added</string> <string name="note_deleted">Note deleted</string> <string name="seed_title_1">Saturday</string> <string name="seed_body_1">Football at 10, then revise ConstraintLayout.</string> <string name="seed_title_2">Ideas for the app</string> <string name="seed_body_2">Pastel cards, a Share button, maybe a photo one day. Then save the notes, so that turning the phone loses nothing.</string> <string name="seed_title_3">Exam: the lifecycle</string> <string name="seed_body_3">onCreate, onStart, onResume… then onPause, onStop, onDestroy.</string></resources>Step 5 of 6
Wire Clear from the XML, then break it
Goal Wire Clear through android:onClick, then break it.
- Layout res › layout › activity_main.xml · Code
- Java MainActivity.java
- Emulator
- Logcat package:mine
-
Type
android:onClickon Clear, in the layout.- In Project, double-click res › layout › activity_main.xml.
- Click Code, at the top right of the editor.
- Click at the end of line 129,
android:layout_heightinbuttonClear, then press Enter. - Type
android:onClick="onClearClicked".
res › layout › activity_main.xmlAdd the marked lines<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity">
<View android:id="@+id/header" android:layout_width="0dp" android:layout_height="168dp" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:background="@drawable/bg_header" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<ImageView android:id="@+id/imageLogo" android:layout_width="36dp" android:layout_height="36dp" android:layout_marginStart="16dp" android:layout_marginTop="20dp" android:contentDescription="@string/logo_description" app:layout_constraintStart_toStartOf="@id/header" app:layout_constraintTop_toTopOf="@id/header" app:srcCompat="@drawable/ic_notes" />
<TextView android:id="@+id/textAppTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:fontFamily="sans-serif-black" android:text="@string/app_name" android:textColor="@color/on_brand" android:textSize="30sp" app:layout_constraintStart_toStartOf="@id/imageLogo" app:layout_constraintTop_toBottomOf="@id/imageLogo" />
<TextView android:id="@+id/textCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:background="@drawable/bg_pill" android:paddingStart="10dp" android:paddingTop="2dp" android:paddingEnd="10dp" android:paddingBottom="2dp" android:text="@string/count_start" android:textColor="@color/brand" android:textSize="14sp" android:textStyle="bold" app:layout_constraintBaseline_toBaselineOf="@id/textAppTitle" app:layout_constraintStart_toEndOf="@id/textAppTitle" />
<LinearLayout android:id="@+id/composer" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="32dp" android:layout_marginTop="120dp" android:layout_marginEnd="32dp" android:background="@drawable/bg_card" android:elevation="6dp" android:orientation="vertical" android:padding="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/header">
<EditText android:id="@+id/editTitle" style="@style/NotesField" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/title_hint" android:importantForAutofill="no" android:inputType="textCapSentences" />
<EditText android:id="@+id/editBody" style="@style/NotesField" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="top" android:hint="@string/body_hint" android:importantForAutofill="no" android:inputType="textMultiLine|textCapSentences" android:maxLength="140" android:minLines="3" />
<TextView android:id="@+id/textError" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/error_no_title" android:textColor="@color/error" android:textSize="14sp" android:visibility="gone" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="center_vertical" android:orientation="horizontal">
<TextView android:id="@+id/textCounter" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/counter_start" android:textColor="@color/muted" android:textSize="12sp" />
<Button android:id="@+id/buttonClear" style="@style/Widget.Material3.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClearClicked" android:text="@string/clear" />
<Button android:id="@+id/buttonAdd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:text="@string/add" app:cornerRadius="12dp" /> </LinearLayout> </LinearLayout>
<TextView android:id="@+id/textListTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="32dp" android:layout_marginTop="24dp" android:letterSpacing="0.1" android:text="@string/your_notes" android:textColor="@color/muted" android:textSize="12sp" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/composer" />
<TextView android:id="@+id/textListHint" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="32dp" android:gravity="end" android:text="@string/list_hint" android:textColor="@color/muted" android:textSize="12sp" app:layout_constraintBaseline_toBaselineOf="@id/textListTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/textListTitle" />
<ScrollView android:id="@+id/scrollNotes" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textListHint">
<LinearLayout android:id="@+id/notesContainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="16dp" android:paddingEnd="16dp" android:paddingBottom="16dp" /> </ScrollView>
<TextView android:id="@+id/textEmpty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="48dp" android:gravity="center" android:text="@string/empty" android:textColor="@color/muted" android:textSize="16sp" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/scrollNotes" />
</androidx.constraintlayout.widget.ConstraintLayout> -
Type The method the XML names, in
MainActivity.java.- In MainActivity.java, click at the end of line 129, the
}that closesupdateForm(). Press Enter twice. - Type lines 131 to 133, with Enter between them: the
}arrives by itself.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
card.setOnLongClickListener(v -> { notesContainer.removeView(card); updateCount(); Toast.makeText(this, R.string.note_deleted, Toast.LENGTH_SHORT).show(); return true; });
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
public void onClearClicked(View view) { editTitle.setText(""); editBody.setText(""); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}The same name,
public, and oneViewparameter: Android looks for exactly this. - In MainActivity.java, click at the end of line 129, the
-
Rename the method in Java only.
Break it
- On line 131, click just before the
(ofonClearClicked(. - Press Backspace twice:
onClearClick. Not with Refactor › Rename: it renames the XML value too. - In Logcat’s query, delete
tag:Lifecycle: keep onlypackage:mine. - Click ▶ Run 'app', then tap Clear on the phone.
What happened
Click the activity_main.xml tab: line 130 now has a red mark. Hover over it for its tooltip:
activity_main.xml, line 130 Editor · centreFull size
- Corresponding method handler 'public void onClearClicked(
android. : no Java method has this name. Do not click Suppress.view. View)' not found
The app still builds. At the tap on Clear, it closes, and Logcat shows:
You should see10-31 18:59:18.030 6512 6512 E AndroidRuntime: FATAL EXCEPTION: main 10-31 18:59:18.030 6512 6512 E AndroidRuntime: Process: com.yourname.notes, PID: 6512 10-31 18:59:18.030 6512 6512 E AndroidRuntime: java.lang.IllegalStateException: Could not find method onClearClicked(View) in a parent or ancestor Context for android:onClick attribute defined on view class com.google.android.material.button.MaterialButton with id 'buttonClear'Your times and process numbers will differ.
Android looks for the method by its name only at the tap.
- On line 131, click just before the
-
Put the name back.
- On line 131, click just before the
(, then typeed:onClearClickedagain. - In Logcat’s query, type a space, then
tag:Lifecycle, afterpackage:mine. - Click ▶ Run 'app'.
- On the phone, click Title, type a word, then tap Clear: the field empties.
- On line 131, click just before the
Done I see the crash; with the name back, Clear works.
Stuck? 3 symptoms · the files
- Clear closes the app before the Break it:
IllegalStateException: Could not find method onClearClicked(View) …The two names differ. Give the Java method the XML’s name, exactly. - Nothing breaks after the rename Refactor › Rename renamed the XML value too: undo (Ctrl+Z), then type over the name.
- The app closes, but Logcat shows no crash The query still holds
tag:Lifecycle. Keep onlypackage:mine.
The files at the end of this step
package com.yourname.notes;
import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Lifecycle"; private static final int[] NOTE_BACKGROUNDS = { R.drawable.bg_note_butter, R.drawable.bg_note_mint, R.drawable.bg_note_lavender, R.drawable.bg_note_peach };
private TextView textCount; private TextView textEmpty; private LinearLayout notesContainer; private int notesAdded = 0;
private EditText editTitle; private EditText editBody; private TextView textError; private TextView textCounter;
private final TextWatcher formWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void afterTextChanged(Editable s) { updateForm(); } };
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Main onCreate");
textCount = findViewById(R.id.textCount); textEmpty = findViewById(R.id.textEmpty); notesContainer = findViewById(R.id.notesContainer);
addNote(getString(R.string.seed_title_1), getString(R.string.seed_body_1)); addNote(getString(R.string.seed_title_2), getString(R.string.seed_body_2)); addNote(getString(R.string.seed_title_3), getString(R.string.seed_body_3)); updateCount();
editTitle = findViewById(R.id.editTitle); editBody = findViewById(R.id.editBody); textError = findViewById(R.id.textError); textCounter = findViewById(R.id.textCounter);
Button buttonAdd = findViewById(R.id.buttonAdd); buttonAdd.setOnClickListener(v -> addNoteFromComposer());
editTitle.addTextChangedListener(formWatcher); editBody.addTextChangedListener(formWatcher); }
private void updateCount() { int count = notesContainer.getChildCount(); textCount.setText(String.valueOf(count)); textEmpty.setVisibility(count == 0 ? View.VISIBLE : View.GONE); }
private void addNote(String title, String body) { View card = LayoutInflater.from(this).inflate(R.layout.item_note, notesContainer, false); TextView cardTitle = card.findViewById(R.id.textNoteTitle); TextView cardBody = card.findViewById(R.id.textNoteBody); cardTitle.setText(title); cardBody.setText(body); cardBody.setVisibility(body.isEmpty() ? View.GONE : View.VISIBLE); int background = NOTE_BACKGROUNDS[notesAdded % NOTE_BACKGROUNDS.length]; card.setBackgroundResource(background); notesAdded++;
card.setOnLongClickListener(v -> { notesContainer.removeView(card); updateCount(); Toast.makeText(this, R.string.note_deleted, Toast.LENGTH_SHORT).show(); return true; });
notesContainer.addView(card, 0); }
private void addNoteFromComposer() { String title = editTitle.getText().toString().trim(); String body = editBody.getText().toString().trim(); if (title.isEmpty()) { textError.setVisibility(View.VISIBLE); return; } textError.setVisibility(View.GONE); addNote(title, body); updateCount(); editTitle.setText(""); editBody.setText(""); Toast.makeText(this, R.string.note_added, Toast.LENGTH_SHORT).show(); }
private void updateForm() { int length = editBody.length(); textCounter.setText(getString(R.string.counter, length)); int colour = length >= 120 ? R.color.error : R.color.muted; textCounter.setTextColor(getColor(colour)); if (editTitle.length() > 0) { textError.setVisibility(View.GONE); } }
public void onClearClicked(View view) { editTitle.setText(""); editBody.setText(""); }
@Override protected void onStart() { super.onStart(); Log.d(TAG, "Main onStart"); }
@Override protected void onResume() { super.onResume(); Log.d(TAG, "Main onResume"); }
@Override protected void onPause() { super.onPause(); Log.d(TAG, "Main onPause"); }
@Override protected void onStop() { super.onStop(); Log.d(TAG, "Main onStop"); }
@Override protected void onRestart() { super.onRestart(); Log.d(TAG, "Main onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Main onDestroy"); }}<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity">
<View android:id="@+id/header" android:layout_width="0dp" android:layout_height="168dp" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:background="@drawable/bg_header" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<ImageView android:id="@+id/imageLogo" android:layout_width="36dp" android:layout_height="36dp" android:layout_marginStart="16dp" android:layout_marginTop="20dp" android:contentDescription="@string/logo_description" app:layout_constraintStart_toStartOf="@id/header" app:layout_constraintTop_toTopOf="@id/header" app:srcCompat="@drawable/ic_notes" />
<TextView android:id="@+id/textAppTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:fontFamily="sans-serif-black" android:text="@string/app_name" android:textColor="@color/on_brand" android:textSize="30sp" app:layout_constraintStart_toStartOf="@id/imageLogo" app:layout_constraintTop_toBottomOf="@id/imageLogo" />
<TextView android:id="@+id/textCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:background="@drawable/bg_pill" android:paddingStart="10dp" android:paddingTop="2dp" android:paddingEnd="10dp" android:paddingBottom="2dp" android:text="@string/count_start" android:textColor="@color/brand" android:textSize="14sp" android:textStyle="bold" app:layout_constraintBaseline_toBaselineOf="@id/textAppTitle" app:layout_constraintStart_toEndOf="@id/textAppTitle" />
<LinearLayout android:id="@+id/composer" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="32dp" android:layout_marginTop="120dp" android:layout_marginEnd="32dp" android:background="@drawable/bg_card" android:elevation="6dp" android:orientation="vertical" android:padding="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/header">
<EditText android:id="@+id/editTitle" style="@style/NotesField" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/title_hint" android:importantForAutofill="no" android:inputType="textCapSentences" />
<EditText android:id="@+id/editBody" style="@style/NotesField" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="top" android:hint="@string/body_hint" android:importantForAutofill="no" android:inputType="textMultiLine|textCapSentences" android:maxLength="140" android:minLines="3" />
<TextView android:id="@+id/textError" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/error_no_title" android:textColor="@color/error" android:textSize="14sp" android:visibility="gone" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="center_vertical" android:orientation="horizontal">
<TextView android:id="@+id/textCounter" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/counter_start" android:textColor="@color/muted" android:textSize="12sp" />
<Button android:id="@+id/buttonClear" style="@style/Widget.Material3.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClearClicked" android:text="@string/clear" />
<Button android:id="@+id/buttonAdd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:text="@string/add" app:cornerRadius="12dp" /> </LinearLayout> </LinearLayout>
<TextView android:id="@+id/textListTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="32dp" android:layout_marginTop="24dp" android:letterSpacing="0.1" android:text="@string/your_notes" android:textColor="@color/muted" android:textSize="12sp" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/composer" />
<TextView android:id="@+id/textListHint" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="32dp" android:gravity="end" android:text="@string/list_hint" android:textColor="@color/muted" android:textSize="12sp" app:layout_constraintBaseline_toBaselineOf="@id/textListTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/textListTitle" />
<ScrollView android:id="@+id/scrollNotes" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textListHint">
<LinearLayout android:id="@+id/notesContainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="16dp" android:paddingEnd="16dp" android:paddingBottom="16dp" /> </ScrollView>
<TextView android:id="@+id/textEmpty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="48dp" android:gravity="center" android:text="@string/empty" android:textColor="@color/muted" android:textSize="16sp" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/scrollNotes" />
</androidx.constraintlayout.widget.ConstraintLayout>Listener or android:onClick?
| Listener in Java (the course’s way) | android:onClick in XML | |
|---|---|---|
| Who links the view to the code | your line, visible in Java | Android, at run time, by the method’s name; deprecated since API 31 (it still works, but Android no longer recommends it) |
| A typo in the name | a compile error | it builds, then the app closes on the tap. Lint marks the XML line as an Error, but ▶ does not run lint |
A long press (your cards), or a view made with new (Way 1) | yes | impossible: XML has no attribute for a long press, and a view made with new has no XML |
Step 6 of 6
See why a double tap needs more
Goal Explain in one sentence why Notes has no double tap.
- Your finger taps the same view twice, quickly
OnClickListenerruns at the first tap, then again: it cannot tell one tap from a double tapGestureDetectorwatches the touches and callsonDoubleTap()
View has no double-tap listener: a double tap takes a GestureDetector. Its code is extension E5, on the lab’s page.Now you
Clear the red line too
After Add with an empty title, Clear leaves the red line. Hide it too: one line.
See the answer
In onClearClicked(), add textError., then ▶ again.
If it doesn’t work
error: cannot find symbol·symbol: class Button(orEditText,Toast,TextWatcher) The import is missing. Click the red name, then press Alt+Enter.error: no suitable method found for makeText(<anonymous OnClickListener>,int,int)Inside an anonymous class,thisis the listener. WriteMainActivity.this.- Old lines mix with the new ones in Logcat Click Clear Logcat (the trash icon) before each action.
End of the part
Recap
Add acts now: onCreate() hands it a listener; cards and fields have theirs. An android:onClick typo shows only at the tap.
- hand a view a listener, as an anonymous class or a lambda;
- read and check typed text as it changes; answer with a Toast;
- delete a card with a long press; compare
android:onClickwith a listener.
