Nine parts, one app
Each part has one goal, one visible result, and a catch-up at its top. Behind? Open the next part and start from there.
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.
Where you work
- Android Studio You write the XML of
res/and the Java ofkotlin+java. - Gradle Builds the app.
- The emulator Runs your app on a virtual phone. There, your XML becomes objects (Part 5).
- Logcat Shows what
Log.d()writes.
For the teacher The two sessions 1 min
- Session 1, 121 min of steps (151 if 3.5 is built by hand). Open with the finished app, live, and this route (5 min). Parts 1 and 2, then sync point A (“Hello, Notes!” with no bar); Part 3, sync point B (the skeleton), then a break; Part 4, sync point C (the styled screen).
- Session 2, 143 min of steps. Part 5, sync point D (Logcat after a rotation); Part 6, sync point E (three cards); Part 7, then a break; Part 8, then the quick check.
- At a sync point, project the part’s “Your app now”. A student who is behind starts the next part from its catch-up.
- Running late? Session 1: skip 4.6, then the “Now you”. Session 2: skip the “Now you”, then 8.3’s Predict; 8.6 becomes your 2-minute demo. Never skip 7.6, 8.5 or the cards of Part 6.
- Time left after 8.6? E5, the double tap (below), is an 8-minute class activity.
Before the first session
Part 0 · at home · 1–2 h
Install and prepare
Everything that downloads is done at home, the evening before. In class you only write code.
Your entry ticket
One screenshot
Android Studio with your Notes project, BUILD SUCCESSFUL, and the phone running beside it (Part 0, step 0.5). The project lives in C:\Users\you\.
Without JavaScript, the code says com.yourname.notes: replace yourname by the name you chose.
Three rules, for every part
- Every part starts with a catch-up. Behind? Start from the top of the next part.
- Type means you type it, helped only by Android Studio’s completion (Ctrl+Space). No AI assistant here: you type by hand once what an AI will write for you later, so you can check it.
- Paste means the Copy button of the code panel.
Extensions, for fast students and for home
E1 · about 10 min
The app speaks French
One file, res/values-fr/strings.xml, and every text of the app follows the phone’s language.
How
Right-click res, then NewAndroid Resource File. File name strings, Resource type Values. Pick Locale, click >>, then pick fr: Directory name now says values-fr. Click OK. Select all in the new file, then paste this file. Then ▶, and in the phone’s settings, add French and move it to the top.
<resources> <string name="app_name">Notes</string> <string name="logo_description">Logo de Notes</string> <string name="count_start">0</string> <string name="title_hint">Titre</string> <string name="body_hint">Écrivez quelque chose…</string> <string name="error_no_title">Donnez d’abord un titre à votre note.</string> <string name="counter_start">0 / 140</string> <string name="counter">%1$d / 140</string> <string name="clear">Effacer</string> <string name="add">Ajouter</string> <string name="your_notes">VOS NOTES</string> <string name="list_hint">Touchez pour ouvrir · maintenez pour supprimer</string> <string name="empty">Pas encore de note.\nÉcrivez la première ci-dessus.</string> <string name="note_added">Note ajoutée</string> <string name="note_deleted">Note supprimée</string> <string name="seed_title_1">Samedi</string> <string name="seed_body_1">Football à 10 h, puis réviser ConstraintLayout.</string> <string name="seed_title_2">Idées pour l’application</string> <string name="seed_body_2">Des cartes pastel, un bouton Partager, un jour peut-être une photo. Puis enregistrer les notes, pour qu’une rotation n’efface plus rien.</string> <string name="seed_title_3">Examen : le cycle de vie</string> <string name="seed_body_3">onCreate, onStart, onResume… puis onPause, onStop, onDestroy.</string> <string name="note_kicker">NOTE</string> <string name="back_to_notes">Retour aux notes</string> <string name="share">Partager</string></resources>Check the list: the longer French hint wraps onto a second line beside « VOS NOTES », never over it, and the list starts below it.
E5 · after Part 8
Double tap to enlarge
On the note page, a double tap switches the text between 18 and 24 sp. View has no double-tap listener: it takes a GestureDetector.
How
In NoteActivity.java, add the marked lines: two imports and an instance variable.
package com.yourname.notes;
import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.GestureDetector;import android.view.MotionEvent;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";
private boolean bigText = false;
@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);
GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { return true; }
@Override public boolean onDoubleTap(MotionEvent e) { bigText = !bigText; textBody.setTextSize(bigText ? 24 : 18); return true; } }); textBody.setOnTouchListener((v, event) -> detector.onTouchEvent(event));
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"); }}Then, in onCreate(), below the setBackgroundResource line and its empty line, the detector and its listener:
package com.yourname.notes;
import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.GestureDetector;import android.view.MotionEvent;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";
private boolean bigText = false;
@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);
GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { return true; }
@Override public boolean onDoubleTap(MotionEvent e) { bigText = !bigText; textBody.setTextSize(bigText ? 24 : 18); return true; } }); textBody.setOnTouchListener((v, event) -> detector.onTouchEvent(event));
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"); }}Lint then warns ClickableViewAccessibility on the setOnTouchListener line: with TalkBack, a double tap does not reach this listener. Acceptable here: the double tap is only an extra.
E6 · a challenge
Lock your notes
A sign-in screen in front, in Notes’ colours. It reuses bg_field and the theme, checks only the form (3+ characters, then 6+), and opens MainActivity. A real app asks its server: chapter 13.
- An older Android: the optional API 28 phone of Part 0.
- Your own phone: turn on USB debugging, as in the Try it of chapter 02.
The finished project, to compare with yours, not to work in. If you run it, a second “Notes” appears on the phone: uninstall it afterwards.
Go deeper Older Android Studio? 1 min
- “Phone and Tablet” is now Phone and large screens.
- Old tutorials start from Empty Views Activity, which writes the activity for you. In Rabbit 2 its code calls
WindowCompat.enableEdgeToEdgeand an insets listener: this lab needs neither. - Old projects use Groovy
build.gradleandcompileSdk 34. Rabbit 2 writes.ktsandcompileSdk { version = release(37) }. - Device Manager replaces the old AVD Manager. The emulator runs inside Android Studio, in Running Devices (SettingsToolsEmulator to change that).
- Before 2022, the Logcat window used drop-down menus; now it is a query field (
package:mine). - Old tutorials show the code folder as
javain the Android display; Rabbit 2 labels itkotlin+java, Java included. On disk it is stillapp/src/main/java. - Old tutorials name the device wizard’s pages “Select Hardware” and “System Image”; Rabbit 2 has Add Device, then Configure virtual device.
android:onClickis in old tutorials everywhere; it has been deprecated since API 31.- The lab’s projects use AGP 9.5.0, which needs Rabbit 2 or newer: update Android Studio. A newer one may write higher version numbers; keep yours.
Quick check · after Part 8
In the layout, a view has no vertical constraint. Where is it drawn on the phone?
See the answer
Nothing holds it vertically, so it goes to the top, in the editor too, with a red mark in the Component Tree. It still builds and runs: ▶ does not run lint (Part 3).
Which line turns activity_main.xml into objects?
See the answer
setContentView() inflates the layout: one object per tag. findViewById() only finds an object that already exists (Part 5).
You press Home. Which methods of your activity run?
See the answer
Home stops the activity; it does not destroy it. Leaving is not closing (Part 5).
findViewById() returns null. What is the most likely cause?
See the answer
It searches the objects of the current layout. Before setContentView() there are none; a hidden view is still there; a missing import does not compile (Part 6).
Why does the course prefer setOnClickListener() to android:onClick?
See the answer
android:onClick still works, but it is deprecated: a typo in its name builds, then crashes at the tap; and it cannot handle a long press, nor reach a view made with new (Part 7).
After a rotation, the notes you added are gone, but your half-typed title is still there. Why?
See the answer
Rotation destroys the activity and creates a new one, in the same process: onCreate() adds only the seeds. A text field with an id comes back through saved state. Keeping the notes is the next milestone (chapters 03 and 09).
