Mehdi Tmimi Mobile

Mobile Development Lab 1 Part 7 of 8

The screen answers: events

Tap Add: nothing happens. A button acts only when you hand it a listener.

The notes screen at the end of Part 6: three pastel cards, Exam, Ideas and Saturday, and the pill says 3.
Where you start

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:onClick with 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
  1. The project: your own from Part 6, or a fresh one: Part 0, steps 0.3 and 0.4 (about 5 minutes).

  2. Your package name, for the code of this page:

    Without JavaScript, the code says com.yourname.notes: replace yourname by the name you chose.

  3. 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 lines
    plugins {    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)}
  4. res and manifest: download the pack and open it in Explorer. Drag its app folder onto your project folder (C:\Users\<you>\AndroidStudioProjects\Notes), 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.

    lab1-part7-res.zip zip · 8.7 KB
  5. 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: type MainActivity, then press Enter.

    kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
    package 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"); }}
  6. ▶. You are ready when the phone shows three cards and the pill says 3.

Step 1 of 6

Hand Add a listener

7 min

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
  1. A variable for the Add button.

    1. In MainActivity.java, click at the end of updateCount();, the last line inside onCreate(). Press Enter twice.
    2. Type Button buttonAdd = findViewById(R.id.buttonAdd);
    3. Button is red? Click it, press Alt+Enter, then choose Import class.
  2. Type Its listener: an anonymous class that logs “Add tapped”.

    1. Click at the end of line 43, then press Enter.
    2. Type buttonAdd.setOnClickListener(new and a space: Android Studio adds the ). A list is open? Press Esc.
    3. Press Ctrl+Shift+Space: a list opens, View.OnClickListener{...} first. Press Enter.
    4. Android Studio writes lines 44 to 49, with view where the panel says v: both work. In onClick(), type Log.d(TAG, "Add tapped");
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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(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 to addActionListener.

  3. Run the app, then tap Add twice.

    1. Click ▶ Run 'app', in the toolbar.
    2. In Logcat, click Clear Logcat: the trash icon at the top of its left column.
    3. 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 tapped

    Your times and process numbers will differ.

  4. Turn the anonymous class into a lambda.

    1. In MainActivity.java, click inside View.OnClickListener, on line 44.
    2. Press Alt+Enter: a list opens, Replace with lambda first.
    MainActivity.java Editor · centreFull size
    The code of the anonymous class on buttonAdd, line 44: the Alt+Enter popup offers Replace with lambda.
    1. new View.OnClickListener(): the caret here, then Alt+Enter.
    2. Replace with lambda.
  5. Accept it: one line instead of six.

    1. Press Enter: lines 44 to 49 become one line, 44.
    MainActivity.java Editor · centre
kotlin+java › com.yourname.notes › MainActivity.javaRead only
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 setOnClickListener line 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:Lifecycle in it, as in Part 5.

The files at the end of this step

kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
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

12 min

Goal Add a typed note as a card, or show the red line.

  • Java MainActivity.java
  • Resources res › values › strings.xml
  • Emulator
  1. Paste Four views: imports, variables and findViewById lines.

    1. Click Copy 1 line, right of After line 7.
    2. In MainActivity.java, click at the end of line 7, press Enter, then Ctrl+V.
    3. The same for After line 10, 29 and 49.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}
  2. Type addNoteFromComposer(): no title, the red line; a title, a Toast.

    1. On line 56, select Log.d(TAG, "Add tapped"), then type addNoteFromComposer() over it.
    2. Click at the end of line 77, the } that closes addNote(). Press Enter twice.
    3. Type lines 79 to 84, with Enter between them: Android Studio writes both }.
    4. Click after the } of line 85, press Enter, then type line 86. note_added stays red until action d.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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; } 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.

  3. Paste The rest of addNoteFromComposer().

    1. Click Copy 5 lines.
    2. Click at the end of line 85, press Enter, then Ctrl+V.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}
  4. Paste The string “Note added”, in strings.xml.

    1. Double-click res › values › strings.xml.
    2. Click Copy 1 line.
    3. 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>
  5. Run the app, then, for each row:

    1. Click ▶ Run 'app'.
    2. On the phone, click Title, then type the row’s Title.
    3. Click Write something…, then type the row’s Write something… cell.
    4. Tap Add.
  6. Rows 1 to 3, in order.

    TitleWrite something…You should see
    1(nothing)(nothing)red “Give your note a title first.”; still 3 cards
    2Groceries(nothing)Toast “Note added”; a peach card on top, no text line; pill 4; red line gone
    3Call YasmineAbout the project, before Friday.a butter card on top, with its text; pill 5
Row 2: the Toast “Note added” with the app’s icon, over the open keyboard; the peach card Groceries on top, and the pill says 4.

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

Row 1 at real size: Title empty, and the red line “Give your note a title first.” under the two fields.
Row 1 at real size

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 with addNoteFromComposer().
  • 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

kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
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"); }}
res › values › strings.xmlReplace the whole file
<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

9 min

Goal Make the counter follow your typing, red from 120 characters.

  • Resources res › values › strings.xml
  • Java MainActivity.java
  • Emulator
  1. Paste The string counter, in strings.xml.

    1. Click Copy 1 line, in the panel.
    2. 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 to getString().

  2. Paste Two imports.

    1. Click Copy 2 lines, in the panel.
    2. In MainActivity.java, click at the end of line 3, press Enter, then Ctrl+V.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}
  3. Paste formWatcher, a TextWatcher.

    1. Click Copy 15 lines, in the panel.
    2. 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 lines
    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"); }}

    A TextWatcher has 3 methods: no lambda. Implement methods would write them otherwise: paste this block.

  4. Paste Hand it to both fields.

    1. Click Copy 3 lines, in the panel.
    2. Click at the end of line 73, in onCreate(), press Enter, then Ctrl+V.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}

    set…Listener replaces a listener, add…Listener adds one: one TextWatcher serves both fields.

  5. Type updateForm(): the counter’s text and colour.

    1. Click at the end of line 112, the } that closes addNoteFromComposer(). Press Enter twice.
    2. Type lines 114 to 120, with Enter between them: both } arrive by themselves.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}

    setTextColor() needs a colour, not its id: getColor() gives it.

  6. Row 4’s sentence, on the phone.

    1. Click ▶ Run 'app'.
    2. Triple-click row 4’s sentence below, then press Ctrl+C.
    3. On the phone, click Write something…, then press Ctrl+V: the counter turns red.
    4. Press Backspace 6 times: 119 / 140, no longer red.
    Row 4
    TitleWrite 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.

Row 4 at real size: the 125-character sentence in Write something, and the counter 125 / 140 in red beside Clear and Add.
Row 4 at real size

Done I see the counter move, red from 120.

Stuck? 2 symptoms · the files
  • The counter does not move The TextWatcher is added to one field only. Add it to both, at the end of onCreate().
  • 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. Write getColor(colour).

The files at the end of this step

kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
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"); }}
res › values › strings.xmlReplace the whole file
<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

7 min

Goal Hold a card (mouse button down) to delete it, and update the pill.

  • Resources res › values › strings.xml
  • Java MainActivity.java
  • Emulator
  1. Paste The string note_deleted, in strings.xml.

    1. Click Copy 1 line, in the panel.
    2. 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>
  2. Type A long-press listener on each card, in addNote().

    1. In MainActivity.java, click at the end of line 94, notesAdded++;. Press Enter twice.
    2. Type lines 96 to 100, with Enter between them.
    3. Android Studio writes line 101 as }): click at its end, then type ;.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}

    A lambda of several lines takes braces. return true means “I handled the press”. Each card gets its own listener, so removeView(card) removes the card you hold.

  3. Add Groceries again, then hold its card.

    1. Click ▶ Run 'app'.
    2. On the phone, click Title, type Groceries, then tap Add.
    3. Press the mouse button on the Groceries card and keep it down: the card goes.
    The long press at real size: the Toast “Note deleted” with the app’s icon, on the paper below the three starting cards.
    The long press: the Toast at real size
  4. Delete every card.

    1. Hold each card the same way, until none is left.
Every card deleted: the pill says 0, and the list shows “No notes yet. Write your first one above.”

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.showContextMenuForChild … The lambda returns false. Return true.
  • The card goes, but the pill does not change The listener does not call updateCount(). Add it after removeView(card).

The files at the end of this step

kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
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"); }}
res › values › strings.xmlReplace the whole file
<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

6 min

Goal Wire Clear through android:onClick, then break it.

  • Layout res › layout › activity_main.xml · Code
  • Java MainActivity.java
  • Emulator
  • Logcat package:mine
  1. Type android:onClick on Clear, in the layout.

    1. In Project, double-click res › layout › activity_main.xml.
    2. Click Code, at the top right of the editor.
    3. Click at the end of line 129, android:layout_height in buttonClear, then press Enter.
    4. 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>
  2. Type The method the XML names, in MainActivity.java.

    1. In MainActivity.java, click at the end of line 129, the } that closes updateForm(). Press Enter twice.
    2. Type lines 131 to 133, with Enter between them: the } arrives by itself.
    kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked lines
    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"); }}

    The same name, public, and one View parameter: Android looks for exactly this.

  3. Rename the method in Java only.

    Break it

    1. On line 131, click just before the ( of onClearClicked(.
    2. Press Backspace twice: onClearClick. Not with Refactor › Rename: it renames the XML value too.
    3. In Logcat’s query, delete tag:Lifecycle: keep only package:mine.
    4. 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
    The tooltip on line 130 of the layout: Corresponding method handler public void onClearClicked(android.view.View) not found, with Suppress: Add tools:ignore="OnClick" attribute and More actions.
    1. Corresponding method handler 'public void onClearClicked(android.view.View)' not found: no Java method has this name. Do not click Suppress.

    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.

  4. Put the name back.

    1. On line 131, click just before the (, then type ed: onClearClicked again.
    2. In Logcat’s query, type a space, then tag:Lifecycle, after package:mine.
    3. Click ▶ Run 'app'.
    4. On the phone, click Title, type a word, then tap Clear: the field empties.

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 only package:mine.

The files at the end of this step

kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole file
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"); }}
res › layout › activity_main.xmlReplace the whole file
<?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 codeyour line, visible in JavaAndroid, 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 namea compile errorit 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)yesimpossible: XML has no attribute for a long press, and a view made with new has no XML
Two ways to link a tap to your code. The rest of the lab uses listeners.

Step 6 of 6

See why a double tap needs more

2 min

Goal Explain in one sentence why Notes has no double tap.

  1. Your finger taps the same view twice, quickly
  2. OnClickListener runs at the first tap, then again: it cannot tell one tap from a double tap
  3. GestureDetector watches the touches and calls onDoubleTap()
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.setVisibility(View.GONE);, then ▶ again.

If it doesn’t work

  • error: cannot find symbol · symbol: class Button (or EditText, 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, this is the listener. Write MainActivity.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:onClick with a listener.
Your app at the end of Part 7, after Run 'app': the three seed cards again, and now Add, holding a card and typing all answer.
Your app now
← Lab 1, all parts