school-it.pl // android + kotlin // rozdział 8 // lekcja 31
Intencja z danymi
W lekcji 30 drugi ekran zawsze pokazywał to samo. Zwykle jednak drugi ekran potrzebuje danych z pierwszego: imienia z formularza, numeru wybranego elementu, zaznaczonej opcji. Intencja ma na to „kieszeń” — extras. Pierwszy ekran wkłada do niej wartości pod nazwami (kluczami), drugi wyjmuje je po tych samych nazwach. Zbudujesz formularz zapisu na zawody i ekran potwierdzenia.
Czego się dziś nauczysz
- Włożysz do intencji napis, liczbę i wartość logiczną (
putExtra). - Odczytasz je na drugim ekranie odpowiednią metodą
get…Extra. - Obsłużysz brak danych: wartości domyślne i
null. - Zapiszesz klucze jako stałe, żeby uniknąć literówek.
- Zobaczysz, że po powrocie formularz na pierwszym ekranie nadal jest wypełniony.
Wkładanie danych: putExtra
Każda wartość wkładana do intencji ma klucz — napis, po którym drugi ekran ją odnajdzie. Przypomina to kopertę z podpisanymi przegródkami:
val intencja = Intent(this, PotwierdzenieActivity::class.java)
intencja.putExtra("imie", "Ola") // String
intencja.putExtra("wiek", 15) // Int
intencja.putExtra("zgoda", true) // Boolean
startActivity(intencja)
putExtra(„wiek”, 15)
putExtra(„zgoda”, true)
wiek → 15
zgoda → true
getIntExtra(„wiek”, 0)
getBooleanExtra(„zgoda”, false)
putExtra ma jedną nazwę dla wszystkich typów — Kotlin sam wybiera wersję po typie wartości. Dlatego ważne jest, czym jest wartość: 15 to liczba, a "15" to napis.
Wyjmowanie danych
Drugi ekran ma dostęp do intencji, która go otworzyła, przez właściwość intent. Do odczytu jest osobna metoda dla każdego typu:
val imie = intent.getStringExtra("imie") ?: "?" // String? → brak = null
val wiek = intent.getIntExtra("wiek", 0) // brak = 0 (wartość domyślna)
val zgoda = intent.getBooleanExtra("zgoda", false) // brak = false
| Metoda | Zwraca | Gdy klucza nie ma |
|---|---|---|
getStringExtra("klucz") | String? | null — dlatego ?: "?" albo ?: "". |
getIntExtra("klucz", 0) | Int | drugi argument — wartość domyślna. |
getDoubleExtra("klucz", 0.0) | Double | wartość domyślna. |
getBooleanExtra("klucz", false) | Boolean | wartość domyślna. |
hasExtra("klucz") | Boolean | Sprawdza, czy klucz w ogóle jest. |
Liczby i wartości logiczne nie mogą być „puste”, więc metoda musi wiedzieć, co oddać, gdy klucza brakuje — stąd drugi argument. Napis może być null, więc obywa się bez niego.
Klucze bez literówek
Klucz wpisujesz dwa razy: przy wysyłaniu i przy odbiorze. Jeśli raz napiszesz "imie", a raz "imię", kompilator nic nie zauważy, a drugi ekran dostanie null. Na krótkich przykładach wystarczy uważać. W większych aplikacjach klucze zapisuje się raz, jako stałe w klasie ekranu, który je odbiera:
class PotwierdzenieActivity : AppCompatActivity() {
companion object { // „wspólne dla klasy”, dostępne bez obiektu
const val IMIE = "imie"
const val WIEK = "wiek"
}
…
val imie = intent.getStringExtra(IMIE)
}
// w MainActivity:
intencja.putExtra(PotwierdzenieActivity.IMIE, "Ola")
Literówka w nazwie stałej (PotwierdzenieActivity.IMEI) to już błąd kompilacji — Android Studio podkreśli ją na czerwono. W przykładzie tej lekcji klucze są zwykłymi napisami, żeby kod był krótszy; w zadaniu 6 przepiszesz go na stałe.
Zapis na zawody
Formularz: imię, wiek, konkurencja (RadioGroup) i zgoda rodzica (CheckBox). Dalej sprawdza pola i otwiera ekran potwierdzenia, przekazując cztery wartości. Drugi ekran sam wylicza kategorię wiekową i ostrzega, gdy brakuje zgody. Popraw wraca do wypełnionego formularza, Wyślij pokazuje Toast i też wraca.
Przygotowanie projektu
- Nowy projekt Empty Views Activity, nazwa
Zawody, Kotlin, API 24. - Kreatorem dodaj aktywność
PotwierdzenieActivity(New → Activity → Empty Views Activity). - Podmień zawartość obu układów i obu klas.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="16dp"
android:fitsSystemWindows="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Zapis na zawody"
android:textSize="24sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etImie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Imię"
android:inputType="textPersonName|textCapWords" />
<EditText
android:id="@+id/etWiek"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Wiek"
android:inputType="number"
android:maxLength="2" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Konkurencja"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rgKonkurencja"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/rbBieg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="Bieg 100 m" />
<RadioButton
android:id="@+id/rbSkok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Skok w dal" />
<RadioButton
android:id="@+id/rbKula"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pchnięcie kulą" />
</RadioGroup>
<CheckBox
android:id="@+id/cbZgoda"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Mam zgodę rodzica" />
<Button
android:id="@+id/btnDalej"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Dalej" />
</LinearLayout>
package pl.schoolit.zawody
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
// ---------- kontrolki ----------
private lateinit var etImie: EditText
private lateinit var etWiek: EditText
private lateinit var rgKonkurencja: RadioGroup
private lateinit var cbZgoda: CheckBox
private lateinit var btnDalej: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initUI()
btnDalej.setOnClickListener { przejdzDalej() }
}
private fun initUI() {
etImie = findViewById(R.id.etImie)
etWiek = findViewById(R.id.etWiek)
rgKonkurencja = findViewById(R.id.rgKonkurencja)
cbZgoda = findViewById(R.id.cbZgoda)
btnDalej = findViewById(R.id.btnDalej)
}
// ---------- akcje ----------
private fun przejdzDalej() {
// 1. odczyt i sprawdzenie formularza (lekcja 06)
val imie = etImie.text.toString().trim()
val wiek = etWiek.text.toString().toIntOrNull()
if (imie.isEmpty()) {
etImie.error = "Podaj imię"
return
}
if (wiek == null || wiek !in 7..19) {
etWiek.error = "Wiek od 7 do 19 lat"
return
}
// napis zaznaczonego RadioButtona (lekcja 10)
val zaznaczony = findViewById<RadioButton>(rgKonkurencja.checkedRadioButtonId)
val konkurencja = zaznaczony.text.toString()
// 2. intencja z danymi
val intencja = Intent(this, PotwierdzenieActivity::class.java)
intencja.putExtra("imie", imie) // String
intencja.putExtra("wiek", wiek) // Int
intencja.putExtra("konkurencja", konkurencja) // String
intencja.putExtra("zgoda", cbZgoda.isChecked) // Boolean
startActivity(intencja)
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="16dp"
android:fitsSystemWindows="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Potwierdzenie"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvZawodnik"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvKonkurencja"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="18sp" />
<TextView
android:id="@+id/tvKategoria"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp" />
<TextView
android:id="@+id/tvZgoda"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<Button
android:id="@+id/btnPopraw"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="Popraw" />
<Button
android:id="@+id/btnWyslij"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Wyślij" />
</LinearLayout>
</LinearLayout>
package pl.schoolit.zawody
import android.graphics.Color
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class PotwierdzenieActivity : AppCompatActivity() {
// ---------- kontrolki ----------
private lateinit var tvZawodnik: TextView
private lateinit var tvKonkurencja: TextView
private lateinit var tvKategoria: TextView
private lateinit var tvZgoda: TextView
private lateinit var btnPopraw: Button
private lateinit var btnWyslij: Button
// ---------- dane z intencji ----------
private var imie = ""
private var wiek = 0
private var konkurencja = ""
private var zgoda = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_potwierdzenie)
initUI()
odczytajDane()
ustawZdarzenia()
pokazDane()
}
private fun initUI() {
tvZawodnik = findViewById(R.id.tvZawodnik)
tvKonkurencja = findViewById(R.id.tvKonkurencja)
tvKategoria = findViewById(R.id.tvKategoria)
tvZgoda = findViewById(R.id.tvZgoda)
btnPopraw = findViewById(R.id.btnPopraw)
btnWyslij = findViewById(R.id.btnWyslij)
}
// intent = intencja, która otworzyła TEN ekran
private fun odczytajDane() {
imie = intent.getStringExtra("imie") ?: "?"
wiek = intent.getIntExtra("wiek", 0)
konkurencja = intent.getStringExtra("konkurencja") ?: "?"
zgoda = intent.getBooleanExtra("zgoda", false)
}
private fun ustawZdarzenia() {
btnPopraw.setOnClickListener { finish() } // formularz czeka pod spodem
btnWyslij.setOnClickListener {
Toast.makeText(this, "Zgłoszenie wysłane: $imie", Toast.LENGTH_SHORT).show()
finish()
}
}
// ---------- obliczenia ----------
private fun kategoria(): String = when {
wiek <= 12 -> "młodzik"
wiek <= 15 -> "junior młodszy"
else -> "junior"
}
// ---------- dane → ekran ----------
private fun pokazDane() {
tvZawodnik.text = "$imie, $wiek lat"
tvKonkurencja.text = "Konkurencja: $konkurencja"
tvKategoria.text = "Kategoria: ${kategoria()}"
if (zgoda) {
tvZgoda.text = "✓ Zgoda rodzica dołączona"
tvZgoda.setTextColor(Color.parseColor("#2E7D32"))
} else {
tvZgoda.text = "✗ Brak zgody rodzica — zgłoszenie nie zostanie przyjęte"
tvZgoda.setTextColor(Color.parseColor("#C62828"))
}
btnWyslij.isEnabled = zgoda
}
}
Co dzieje się po kolei
przejdzDalej()najpierw sprawdza formularz. Przy błędzie pokazuje komunikat przy polu i kończy metodę przezreturn— intencja nie powstaje.- Napis wybranej konkurencji pochodzi z zaznaczonego
RadioButton. W grupie zawsze coś jest zaznaczone, bo pierwszy przycisk maandroid:checked="true". - Cztery
putExtraz różnymi typami: dwa napisy, liczba (wiekpo sprawdzeniu jest jużInt, nieInt?) iBoolean. - Drugi ekran przepisuje dane z
intentdo swoich pól wodczytajDane(). Potem korzystają z nichkategoria()ipokazDane(). - Popraw to zwykłe
finish(). Formularz nie był zamknięty, więc wszystkie pola są wypełnione tak, jak je zostawiłeś.
Sprawdź trzy rzeczy
- W
MainActivityzmień klucz naputExtra("Imie", imie)(wielka litera). Na potwierdzeniu pojawi się „?” — klucze rozróżniają wielkość liter. - Zamień
putExtra("wiek", wiek)naputExtra("wiek", wiek.toString()). Ekran pokaże „0 lat”: wysłano napis, agetIntExtraszuka liczby i oddaje wartość domyślną. - Wypełnij formularz, przejdź dalej i kliknij Popraw. Wszystkie pola i zaznaczenia zostały — nic nie trzeba wpisywać od nowa.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
intencja.putExtra("klucz", wartosc) | metoda | Wkłada wartość (String, Int, Double, Boolean…) pod kluczem. |
intent (w aktywności) | Intent | Intencja, która otworzyła ten ekran. |
intent.getStringExtra("klucz") | String? | Napis albo null. |
intent.getIntExtra("klucz", domyślna) | Int | Liczba albo wartość domyślna. |
intent.getBooleanExtra("klucz", domyślna) | Boolean | Wartość logiczna albo domyślna. |
intent.getDoubleExtra("klucz", domyślna) | Double | Liczba z przecinkiem albo domyślna. |
intent.hasExtra("klucz") | Boolean | Czy klucz został wysłany. |
companion object { const val K = "k" } | składnia | Stałe klasy — klucze bez literówek. |
x !in 7..19 | operator | „x nie należy do przedziału 7–19”. |
Zanim utkniesz
| Zapis / objaw | Problem |
|---|---|
drugi ekran pokazuje null, „?” albo 0 | Różne klucze przy wysyłaniu i odbiorze (literówka, wielkość liter). |
getIntExtra zwraca 0, choć wysłano wiek | Wysłano napis ("15" albo et.text.toString()). Zamień na liczbę przed putExtra. |
val imie: String = intent.getStringExtra("imie") → błąd kompilacji | Metoda zwraca String?. Dodaj ?: "". |
getIntExtra("wiek") → błąd kompilacji | Brak drugiego argumentu — wartości domyślnej. |
putExtra po startActivity | Intencja już wysłana — późniejsze dane nie dotrą. Najpierw wszystkie putExtra, na końcu startActivity. |
odczyt intent jako pole klasy: private val imie = intent.getStringExtra(…) | Awaria NullPointerException przy otwieraniu ekranu: pola tworzą się, zanim Android przypnie intencję do aktywności. Czytaj dane w onCreate (tu: odczytajDane()). |
putExtra("wiek", wiek), gdy wiek jest Int? → błąd kompilacji | Najpierw sprawdź null — po sprawdzeniu Kotlin traktuje zmienną jako Int. |
Zadania
Każde zadanie wykonaj w nowym projekcie Empty Views Activity; drugi ekran twórz kreatorem. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Pole „Imię” i przycisk Przywitaj. Drugi ekran wyświetla dużym napisem „Cześć, Ola!”. Gdy pole jest puste — „Cześć, nieznajomy!”.
pokaż wskazówkę
- Na drugim ekranie:
val imie = intent.getStringExtra("imie"), potemif (imie.isNullOrBlank()).
Przerób zadanie 2 z lekcji 30: zamiast trzech aktywności — jedna PrzedmiotActivity. Każdy przycisk wysyła nazwę przedmiotu, nauczyciela i numer sali (Int).
pokaż wskazówkę
- Napisz w
MainActivitymetodęotworz(nazwa: String, nauczyciel: String, sala: Int)i wywołuj ją z trzech przycisków.
Pierwszy ekran: waga (kg) i wzrost (cm). Drugi ekran dostaje dwie liczby Double, liczy BMI, pokazuje wynik z jednym miejscem po przecinku i opis (niedowaga / prawidłowa / nadwaga) w odpowiednim kolorze.
pokaż wskazówkę
getDoubleExtra("waga", 0.0); BMI = waga / (wzrost/100)²;"%.1f".format(bmi).
Lista 8 państw (ListView, lekcja 25). Kliknięcie otwiera ekran szczegółów, który dostaje numer klikniętego wiersza i sam odczytuje stolicę i liczbę ludności z tablic (tablice w obu aktywnościach albo w osobnym pliku).
pokaż wskazówkę
putExtra("nr", position); na drugim ekranieval nr = intent.getIntExtra("nr", -1); gdy-1—finish().
Wykorzystaj przykład z lekcji 09 (CheckBoxy z dodatkami). Zamów przenosi na ekran podsumowania: lista wybranych dodatków (jeden napis połączony przecinkami), liczba dodatków i cena.
pokaż wskazówkę
- Dodatki: zbierz w
mutableListOf<String>()i wyślijjoinToString(", ")jako jeden napis.
Przepisz przykład tak, by wszystkie klucze były stałymi w companion object klasy PotwierdzenieActivity. Dodaj czwartą konkurencję i pole „Klasa” (np. „2B”). Sprawdź, że celowa literówka w nazwie stałej daje błąd kompilacji.
pokaż wskazówkę
- W
MainActivity:intencja.putExtra(PotwierdzenieActivity.KLASA, klasa); w drugim ekranie wystarczy samoKLASA.
Co trzeba zapamiętać
- Wysyłanie:
intencja.putExtra("klucz", wartosc)— przedstartActivity. - Odbiór:
intent.get…Extra("klucz"); napis może byćnull, liczba iBooleanmają wartość domyślną. - Typ przy odbiorze musi być taki sam jak przy wysyłaniu.
- Klucze identyczne po obu stronach — najlepiej jako stałe.
- Pierwszy ekran czeka pod spodem z danymi —
finish()drugiego wraca do wypełnionego formularza.
Gdy wartości jest dużo, lepiej wysłać cały obiekt — o tym lekcja 32.
Dokumentacja: Intent.putExtra, Intent.getIntExtra.