Before you start
By the end you can
- pass a String and an int to a second activity;
- come back with Back or
finish(); say what survives a rotation; - share a note with an implicit Intent; say who picks the app.
- Android Studio Project
- Layout Split
- Java
- Manifest
- Emulator
- Logcat
Behind, or a fresh project? Start from here about 4 min
The project: your own from Part 7, or a fresh one: Part 0, steps 0.3 and 0.4 (about 5 minutes).
Your package name, for the code of this page:
Without JavaScript, the code says com.yourname.notes: replace yourname by the name you chose.
Gradle: check these 3 lines. Add any that is missing, then click Sync Now. Keep your own version numbers.
Gradle Scripts › libs.versions.tomlAdd the marked lines[versions]agp = "9.5.0"junit = "4.13.2"junitVersion = "1.3.0"espressoCore = "3.7.0"appcompat = "1.8.0"material = "1.14.0"constraintlayout = "2.2.2"
[libraries]junit = { group = "junit", name = "junit", version.ref = "junit" }ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }material = { group = "com.google.android.material", name = "material", version.ref = "material" }constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]android-application = { id = "com.android.application", version.ref = "agp" }Gradle Scripts › build.gradle.kts (Module :app)Add the marked linesplugins { alias(libs.plugins.android.application)}
android { namespace = "com.yourname.notes" compileSdk { version = release(37) }
defaultConfig { applicationId = "com.yourname.notes" minSdk = 24 targetSdk = 37 versionCode = 1 versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
buildTypes { release { optimization { enable = true packageScope = setOf("androidx.**", "kotlin.**", "kotlinx.**") } } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 }}
dependencies { implementation(libs.appcompat) implementation(libs.material) implementation(libs.constraintlayout) testImplementation(libs.junit) androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.ext.junit)}Restarting this part? First delete
NoteActivity.javaandres/layout/activity_note.xml. If Android Studio shows Conflicts Detected, click Refactor Anyway.
lab1-part8-res.zip zip · 8.7 KBresand manifest: download the pack and open it in Explorer. Drag itsappfolder onto your project folder (C:\Users\<you>\), then choose Replace the files in the destination. Not Extract All: it makes a folder inside and replaces nothing. Then, in res › values › themes, delete themes.xml (night) if it is still there.AndroidStudioProjects\ Notes Java: open MainActivity.java, select all, then paste this file. Missing? First right-click kotlin+java › com.yourname.notes, the one without
(test), then NewJava Class: typeMainActivity, then press Enter.kotlin+java › com.yourname.notes › MainActivity.javaReplace the whole filepackage com.yourname.notes;
import android.os.Bundle;import android.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"); }}▶. You are ready when the phone shows three cards, and holding one deletes it.
Step 1 of 6
Create the note page, in three pieces
Goal Give the note page its layout, class and manifest line.
- Resources res › values › strings.xml
- Layout res › layout › activity_note.xml · Split
- Java kotlin+java › com.yourname.notes › NoteActivity.java
- Manifest manifests › AndroidManifest.xml
-
Paste The page’s 3 texts, in strings.xml.
- In Project, open res › values › strings.xml.
- In the panel below, click Copy 3 lines.
- Click at the end of line 22, then press Enter.
- Press Ctrl+V: lines 23 to 25 appear.
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> <string name="note_kicker">NOTE</string> <string name="back_to_notes">Back to notes</string> <string name="share">Share</string></resources> -
Paste The layout
activity_note.xml, a new file (step 2.1 made the first one).- In Project, right-click res › layout.
- Choose NewLayout Resource File.
- A New Resource File window opens: in File name, type
activity_note, then click OK. - It opens in Design: click Code, at the top right of the editor.
- Click Copy the file, on the panel below.
- Click in the code, press Ctrl+A, then Ctrl+V.
res › layout › activity_note.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=".NoteActivity">
<LinearLayout android:id="@+id/noteSheet" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="16dp" android:background="@drawable/bg_note_butter" android:orientation="vertical" android:padding="24dp" app:layout_constraintBottom_toTopOf="@id/buttonShare" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:letterSpacing="0.1" android:text="@string/note_kicker" android:textColor="@color/muted" android:textSize="12sp" android:textStyle="bold" />
<TextView android:id="@+id/textTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:fontFamily="sans-serif-black" android:textColor="@color/ink" android:textSize="24sp" tools:text="Ideas for the app" />
<TextView android:id="@+id/textBody" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:lineSpacingMultiplier="1.25" android:textColor="@color/ink" android:textSize="18sp" tools:text="Pastel cards, a Share button, maybe a photo one day." /> </LinearLayout>
<Button android:id="@+id/buttonShare" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:text="@string/share" app:cornerRadius="12dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" />
<Button android:id="@+id/buttonBack" style="@style/Widget.Material3.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:text="@string/back_to_notes" app:cornerRadius="12dp" app:layout_constraintBaseline_toBaselineOf="@id/buttonShare" app:layout_constraintEnd_toStartOf="@id/buttonShare" />
</androidx.constraintlayout.widget.ConstraintLayout>NoteActivityreads 4 of the marked ids now;buttonSharefrom step 8.5.tools:textfills the preview only. -
Paste The class
NoteActivity, which receives the note.- In Project, right-click the plain kotlin+java › com.yourname.notes, without
(test)or(androidTest). - Choose NewJava Class.
- A New Java Class popup opens: type
NoteActivity, then press Enter. - The class opens: click Copy the file, on the panel below.
- Click in the class, press Ctrl+A, then Ctrl+V.
kotlin+java › com.yourname.notes › NoteActivity.javaReplace the whole filepackage com.yourname.notes;
import android.os.Bundle;import android.util.Log;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class NoteActivity extends AppCompatActivity {
public static final String EXTRA_TITLE = "com.yourname.notes.EXTRA_TITLE"; public static final String EXTRA_BODY = "com.yourname.notes.EXTRA_BODY"; public static final String EXTRA_BACKGROUND = "com.yourname.notes.EXTRA_BACKGROUND"; private static final String TAG = "Lifecycle";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_note); Log.d(TAG, "Note onCreate");
String title = getIntent().getStringExtra(EXTRA_TITLE); String body = getIntent().getStringExtra(EXTRA_BODY); int background = getIntent().getIntExtra(EXTRA_BACKGROUND, R.drawable.bg_note_butter);
TextView textTitle = findViewById(R.id.textTitle); TextView textBody = findViewById(R.id.textBody); textTitle.setText(title); textBody.setText(body); findViewById(R.id.noteSheet).setBackgroundResource(background);
findViewById(R.id.buttonBack).setOnClickListener(v -> finish()); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Note onDestroy"); }}An Intent, the message that opens a screen, carries values under keys: its extras. This class reads 3, which step 8.2 sends. Back to notes calls
finish(): the page closes. - In Project, right-click the plain kotlin+java › com.yourname.notes, without
-
Paste The manifest line that declares the page.
- In Project, open manifests › AndroidManifest.xml.
- In the panel below, click Copy 3 lines.
- Click at the end of line 21,
</activity>, then press Enter. - Press Ctrl+V: lines 22 to 24 appear.
manifests › AndroidManifest.xmlAdd the marked lines<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Notes"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".NoteActivity" android:exported="false" /> </application>
</manifest>exported="false": only your own app may open this screen. -
The note page’s preview.
- Click the activity_note.xml tab.
- Click Split, at the top right: the preview shows.
activity_note.xml · preview Layout editor · centreFull size
Done I see the preview, and no red code.
Stuck? 3 symptoms · the files
@string/note_kickeris red, and the preview shows an error The 3 strings are missing. Do action a again.- In the manifest,
.NoteActivitystays redNoteActivity.javais not in the plain package. Drag it onto the plain com.yourname.notes. - No preview beside the code Click Split, at the top right of the editor.
The four panels above hold the files at the end of this step: click Whole file, then Copy the file.
Step 2 of 6
Open the note page with an Intent
Goal Make a tap on a card open its note page.
- Java kotlin+java › com.yourname.notes › MainActivity.java
- Emulator
-
Type A tap on a card calls
openNote().- Open kotlin+java › com.yourname.notes › MainActivity.java.
- In
addNote(), click in the empty line abovecard.setOnLongClickListener, then press Enter. - Completion lists open as you type: keep typing.
- Type the marked line.
openNotestays red until action b writes it.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.content.Intent;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.setOnClickListener(v -> openNote(title, body, background)); 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(""); }
private void openNote(String title, String body, int background) { Intent intent = new Intent(this, NoteActivity.class); intent.putExtra(NoteActivity.EXTRA_TITLE, title); intent.putExtra(NoteActivity.EXTRA_BODY, body); intent.putExtra(NoteActivity.EXTRA_BACKGROUND, background); startActivity(intent); }
@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"); }}Each card’s lambda keeps that card’s own
title,bodyandbackground. -
Type The method
openNote(), which sends the note in an Intent.- Click right after the
}that endsonClearClicked(), at the end of its line. - Press Enter twice: the caret is now where
private void openNote(…begins. - Completion lists open as you type: keep typing.
- Type from
private void openNote(…down tostartActivity(intent);. The method’s closing}arrives by itself: do not type it. - If
Intentis red, click it, then press Alt+Enter. - If a list opens, choose Import class: the import arrives on line 3.
kotlin+java › com.yourname.notes › MainActivity.javaAdd the marked linespackage com.yourname.notes;
import android.content.Intent;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.setOnClickListener(v -> openNote(title, body, background)); 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(""); }
private void openNote(String title, String body, int background) { Intent intent = new Intent(this, NoteActivity.class); intent.putExtra(NoteActivity.EXTRA_TITLE, title); intent.putExtra(NoteActivity.EXTRA_BODY, body); intent.putExtra(NoteActivity.EXTRA_BACKGROUND, background); startActivity(intent); }
@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"); }}Whole file shows line 3 in grey: Android Studio writes it.
- Click right after the
-
The note page, on the phone.
- Click ▶ Run 'app' (↻ Rerun if the app is running).
- On the phone, tap Ideas for the app.

You should see The note page, mint like its card, its text whole.
Done I see the note page in its card’s colour.
Why new Intent(this, NoteActivity.class) names the screen to open: an explicit Intent. Its keys are NoteActivity’s constants, whose values start with your package: Android’s rule for your own extras.
Stuck? 4 symptoms · the file
local variables referenced from a lambda expression must be final or effectively finalYou changedtitleorbodyinsideaddNote(). Leave them as they arrive.- The app closes when you tap a card The manifest lacks
NoteActivity’s lines. Do step 8.1, action d, again. Intentstays red Its import is missing. ClickIntent, then press Alt+Enter.error: cannot find symbol·symbol: class NoteActivityNoteActivity.javais not in the plain package. Drag it onto the plain com.yourname.notes.
The files at the end of this step
package com.yourname.notes;
import android.content.Intent;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.setOnClickListener(v -> openNote(title, body, background)); 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(""); }
private void openNote(String title, String body, int background) { Intent intent = new Intent(this, NoteActivity.class); intent.putExtra(NoteActivity.EXTRA_TITLE, title); intent.putExtra(NoteActivity.EXTRA_BODY, body); intent.putExtra(NoteActivity.EXTRA_BACKGROUND, background); startActivity(intent); }
@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 3 of 6
Come back, and read Logcat
Goal Come back; read the round trip in Logcat.
- Emulator
- Logcat package:mine tag:Lifecycle
Predict first
Open a note, then come back: what happens to the notes screen?
See the answer
It stops (Main onStop) and waits under the note page: the back stack. finish() destroys the note page (Note onDestroy), and the notes screen comes back as you left it.
-
The list as you leave it: one more note, one title typed but not added.
- On the phone, tap Back to notes: the list of notes comes back.
- Type
Groceriesin Title, then tap Add. - Type
Call Yasminein Title, without tapping Add. - Close the keyboard with its down arrow, at the bottom left of the phone.
-
One clean round trip, in Logcat.
- In Logcat, at the bottom of the window, the query must read
package:mine tag:Lifecycle: type it if not. - Click the trash icon, Clear Logcat, at the top of Logcat’s left column.
- On the phone, tap the Groceries card: its note page opens.
- Tap Back to notes.
- In Logcat, at the bottom of the window, the query must read
You should see10-31 19:00:23.570 7176 7176 D Lifecycle: Main onPause
10-31 19:00:23.595 7176 7176 D Lifecycle: Note onCreate
10-31 19:00:24.338 7176 7176 D Lifecycle: Main onStop
10-31 19:00:30.111 7176 7176 D Lifecycle: Main onRestart
10-31 19:00:30.112 7176 7176 D Lifecycle: Main onStart
10-31 19:00:30.112 7176 7176 D Lifecycle: Main onResume
10-31 19:00:30.698 7176 7176 D Lifecycle: Note onDestroy
Your times and process numbers will differ.

You should see The list as you left it.
Done I see Note onDestroy in Logcat.
Stuck? 2 symptoms
- Logcat shows other apps’ lines, or none of yours Type
package:mine tag:Lifecyclein Logcat’s query field, as in Part 5. - No
Note onCreateline Logcat was cleared after you tapped the card. Clear it, then tap the card again.
No file changes in this step: steps 8.1 and 8.2 hold the files.
Step 4 of 6
Break it: hide the note page from the manifest
Goal See Android refuse an undeclared activity.
- Manifest manifests › AndroidManifest.xml
- Emulator
- Logcat package:mine
Break it
Can Android open an activity the manifest forgot?
-
The page’s 3 lines in the manifest, made a comment.
- Open manifests › AndroidManifest.xml.
- Click anywhere in line 22,
<activity. - Press Ctrl+/ three times, once a line: lines 22 to 24 become comments,
<!--…-->.
manifests › AndroidManifest.xmlRead only<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Notes"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".NoteActivity" android:exported="false" /> </application>
</manifest> -
The app asks for the forgotten page.
- In Logcat’s query field, delete
tag:Lifecycle: keep onlypackage:mine. - Click ▶ Run 'app' (↻ Rerun if the app is running).
- On the phone, tap a card.
- In Logcat’s query field, delete
-
Put them back.
- In the manifest, click in line 22 again.
- Press Ctrl+/ three times: the comments go.
- Click ▶ Run 'app' (↻ Rerun if the app is running).
- On the phone, tap a card: its page opens again.
What happened
The app closes. In Logcat, in red:
You should see10-31 18:59:41.415 6726 6726 E AndroidRuntime: FATAL EXCEPTION: main
10-31 18:59:41.415 6726 6726 E AndroidRuntime: Process: com.yourname.notes, PID: 6726
10-31 18:59:41.415 6726 6726 E AndroidRuntime: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.yourname.notes/com.yourname.notes.NoteActivity}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?
Your times and process numbers will differ.
startActivity() asks Android for NoteActivity, and Android opens only the activities a manifest declares.
Done I see ActivityNotFoundException, then a card opens again.
Stuck? 2 symptoms · the file
- The app closes, but Logcat shows no red line The query still says
tag:Lifecycle, which hides Android’s lines. Keeppackage:mineonly. - A card still closes the app after action c The lines are still a comment (
<!--). Paste the manifest’s lines of step 8.1 again.
The manifest at the end of this step is the one of step 8.1, action d.
Step 5 of 6
Share the note with an implicit Intent
Goal Make Share hand the note to an app Android finds.
- Java kotlin+java › com.yourname.notes › NoteActivity.java
- Emulator
Explicit · step 8.2: you name the screen
- MainActivity builds
new Intent(this, NoteActivity.class) - 3 extras the title, the text, the background
- Android opens
NoteActivityon top of the list; Back orfinish()removes it
Implicit · step 8.5: you name the action, Android chooses
- NoteActivity builds
new Intent(Intent.ACTION_SEND) - 1 extra the note’s text, of type
text/plain - Android lists the apps that accept it; the user picks one
-
Type The Share button calls
share().- Open kotlin+java › com.yourname.notes › NoteActivity.java.
- In
onCreate(), click at the end of thebuttonBackline, then press Enter. - Completion lists open as you type: keep typing.
- Type the marked line.
sharestays red until action b writes it.
kotlin+java › com.yourname.notes › NoteActivity.javaAdd the marked linespackage com.yourname.notes;
import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class NoteActivity extends AppCompatActivity {
public static final String EXTRA_TITLE = "com.yourname.notes.EXTRA_TITLE"; public static final String EXTRA_BODY = "com.yourname.notes.EXTRA_BODY"; public static final String EXTRA_BACKGROUND = "com.yourname.notes.EXTRA_BACKGROUND"; private static final String TAG = "Lifecycle";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_note); Log.d(TAG, "Note onCreate");
String title = getIntent().getStringExtra(EXTRA_TITLE); String body = getIntent().getStringExtra(EXTRA_BODY); int background = getIntent().getIntExtra(EXTRA_BACKGROUND, R.drawable.bg_note_butter);
TextView textTitle = findViewById(R.id.textTitle); TextView textBody = findViewById(R.id.textBody); textTitle.setText(title); textBody.setText(body); findViewById(R.id.noteSheet).setBackgroundResource(background);
findViewById(R.id.buttonBack).setOnClickListener(v -> finish()); findViewById(R.id.buttonShare).setOnClickListener(v -> share(title, body)); }
private void share(String title, String body) { Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); send.putExtra(Intent.EXTRA_TEXT, title + "\n" + body); startActivity(Intent.createChooser(send, null)); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Note onDestroy"); }} -
Type The method
share(), which hands the note to Android.- Click right after the
}that endsonCreate(), at the end of its line. - Press Enter twice: the caret is now where
private void share(…begins. - Completion lists open as you type: keep typing.
- Type from
private void share(…down tostartActivity(Intent.createChooser(send, null));. The method’s closing}arrives by itself: do not type it. - If
Intentis red, click it, then press Alt+Enter. - If a list opens, choose Import class: the import arrives on line 3.
kotlin+java › com.yourname.notes › NoteActivity.javaAdd the marked linespackage com.yourname.notes;
import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class NoteActivity extends AppCompatActivity {
public static final String EXTRA_TITLE = "com.yourname.notes.EXTRA_TITLE"; public static final String EXTRA_BODY = "com.yourname.notes.EXTRA_BODY"; public static final String EXTRA_BACKGROUND = "com.yourname.notes.EXTRA_BACKGROUND"; private static final String TAG = "Lifecycle";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_note); Log.d(TAG, "Note onCreate");
String title = getIntent().getStringExtra(EXTRA_TITLE); String body = getIntent().getStringExtra(EXTRA_BODY); int background = getIntent().getIntExtra(EXTRA_BACKGROUND, R.drawable.bg_note_butter);
TextView textTitle = findViewById(R.id.textTitle); TextView textBody = findViewById(R.id.textBody); textTitle.setText(title); textBody.setText(body); findViewById(R.id.noteSheet).setBackgroundResource(background);
findViewById(R.id.buttonBack).setOnClickListener(v -> finish()); findViewById(R.id.buttonShare).setOnClickListener(v -> share(title, body)); }
private void share(String title, String body) { Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); send.putExtra(Intent.EXTRA_TEXT, title + "\n" + body); startActivity(Intent.createChooser(send, null)); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Note onDestroy"); }}Whole file shows line 3 in grey: Android Studio writes it.
createChooser()asks Android for the share sheet. - Click right after the
-
The share sheet, on the phone.
- Click ▶ Run 'app' (↻ Rerun if the app is running).
- On the phone, tap a card: its note page opens.
- Tap Share.

You should see A sheet, “Sharing text”, showing your note.
Done I see my note’s text in the sheet.
Stuck? 3 symptoms · the file
- Share does nothing The
buttonShareline is missing: line 34. Intentstays red Its import is missing. ClickIntent, then press Alt+Enter.- The sheet looks different from the picture Each phone lists its own apps. What matters is your note’s text at the top.
The files at the end of this step
package com.yourname.notes;
import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class NoteActivity extends AppCompatActivity {
public static final String EXTRA_TITLE = "com.yourname.notes.EXTRA_TITLE"; public static final String EXTRA_BODY = "com.yourname.notes.EXTRA_BODY"; public static final String EXTRA_BACKGROUND = "com.yourname.notes.EXTRA_BACKGROUND"; private static final String TAG = "Lifecycle";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_note); Log.d(TAG, "Note onCreate");
String title = getIntent().getStringExtra(EXTRA_TITLE); String body = getIntent().getStringExtra(EXTRA_BODY); int background = getIntent().getIntExtra(EXTRA_BACKGROUND, R.drawable.bg_note_butter);
TextView textTitle = findViewById(R.id.textTitle); TextView textBody = findViewById(R.id.textBody); textTitle.setText(title); textBody.setText(body); findViewById(R.id.noteSheet).setBackgroundResource(background);
findViewById(R.id.buttonBack).setOnClickListener(v -> finish()); findViewById(R.id.buttonShare).setOnClickListener(v -> share(title, body)); }
private void share(String title, String body) { Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); send.putExtra(Intent.EXTRA_TEXT, title + "\n" + body); startActivity(Intent.createChooser(send, null)); }
@Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "Note onDestroy"); }}Step 6 of 6
Turn the phone: what survives?
Goal Turn the phone, and see what survives.
- Emulator Running Devices
-
The list with five notes.
- In Running Devices, click Back until the list of notes shows.
- On the phone, type
Groceriesin Title, then tap Add. - Type
Call Yasminein Title. - Type
About the project, before Friday.in Write something…, then tap Add.
-
A title half typed.
- Type
Halfin Title, without tapping Add. - Close the keyboard with its down arrow, at the bottom left of the phone.
- Type
-
The phone turned, then upright again.
- In Running Devices, click Rotate Left: the phone turns on its side.
- Click Rotate Right: the phone stands upright again.


You should see After: the 3 seeds, pill 3, Half still in Title.

Why Turning recreated the screen: onCreate() added the seeds again. A text field with an id keeps its text (chapter 03).
Stuck? 1 symptom
- The screen does not turn Auto-rotate is off. Drag down twice from the top of the phone’s screen, then tap the rotation tile (two arrows around a phone) so it turns blue.
No file changes in this step: steps 8.1, 8.2 and 8.5 hold the files.
Now you
Turn the note page
Open a note and turn the phone. Does the note stay? Why?
See the answer
Yes. Android recreates the note page, and the new NoteActivity receives the same Intent: onCreate() reads the same extras again.
If it doesn’t work
- The title is empty on the note page A key differs between the two classes:
getStringExtra()then returnsnull. Use the constants, never a typed string. error: class NoteActivity is public, should be declared in a file named NoteActivity.javaThe class name or its file name differs. Both must beNoteActivity.- Two “Notes” apps on the phone You ran
lab1-final.zip, which has its own package name. Uninstall that one.
End of the part
Recap
An Intent names NoteActivity and carries the note; finish() brings the list back. Your notes live only in views: saving them comes next.
- pass a String and an int to a second activity;
- come back with Back or
finish(); say what survives a rotation; - share a note with an implicit Intent; say who picks the app.

End of Lab 1
Nine parts, one app: what you can do now
Part 01–2 h, homeBefore class: install and prepare
I can install Android Studio, start a virtual phone and create my project.
- GradleminSdk 24targetSdk 37+ librarySync NowPart 118 min
I can read the five Gradle values and add a library.
Part 226 minYour first screen, piece by piece
I can write the three pieces of a screen and run it.
Part 342–72 minPlace the views with ConstraintLayout
I can place views that keep their shape on any phone.
Part 435 minI can define texts, colours, shapes and the theme once, in
res/.- LogcatD onCreateD onStartD onResumeD onPausePart 528 min
From XML to Java: the activity and its life
I can follow my activity’s life in Logcat.
Part 637 minI can change views from Java and add note cards.
Part 743 minI can react to taps, long presses and typing.
Part 835 minI can open a second screen with an Intent, and share a note.
Homework: one “Now you”, and one extension.
The finished project, to compare with yours.