Przejdź do treści
School IT / Przestrzeń do nauki

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.

Kotlin XML putExtra getStringExtra getIntExtra getBooleanExtra 2 × 45 min
CEL LEKCJI

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.
TEORIA

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:

pierwszy ekran — wysyłanie
val intencja = Intent(this, PotwierdzenieActivity::class.java)
intencja.putExtra("imie", "Ola")            // String
intencja.putExtra("wiek", 15)               // Int
intencja.putExtra("zgoda", true)            // Boolean
startActivity(intencja)
MainActivity
putExtra(„imie”, „Ola”)
putExtra(„wiek”, 15)
putExtra(„zgoda”, true)
→
Intent
imie → „Ola”
wiek → 15
zgoda → true
→
PotwierdzenieActivity
getStringExtra(„imie”)
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.

TEORIA

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:

drugi ekran — odbiór (w onCreate)
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
MetodaZwracaGdy klucza nie ma
getStringExtra("klucz")String?null — dlatego ?: "?" albo ?: "".
getIntExtra("klucz", 0)Intdrugi argument — wartość domyślna.
getDoubleExtra("klucz", 0.0)Doublewartość domyślna.
getBooleanExtra("klucz", false)Booleanwartość domyślna.
hasExtra("klucz")BooleanSprawdza, 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.

TEORIA

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:

stałe kluczy — dobra praktyka
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.

PRZYKŁAD

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.

12:10
Zapis na zawody
Ola
15
Konkurencja
Bieg 100 m
Skok w dal
Pchnięcie kulą
Mam zgodę rodzica
Dalej
MainActivity — formularz
→4 × putExtra
12:10
Potwierdzenie
Ola, 15 lat
Konkurencja: Skok w dal
Kategoria: junior młodszy
✓ Zgoda rodzica dołączona
Popraw
Wyślij
PotwierdzenieActivity — dane z intencji
12:11
Potwierdzenie
Kuba, 12 lat
Konkurencja: Bieg 100 m
Kategoria: młodzik
✗ Brak zgody rodzica — zgłoszenie nie zostanie przyjęte
Popraw
Wyślij
inne dane — ten sam ekran

Przygotowanie projektu

  1. Nowy projekt Empty Views Activity, nazwa Zawody, Kotlin, API 24.
  2. Kreatorem dodaj aktywność PotwierdzenieActivity (New → Activity → Empty Views Activity).
  3. Podmień zawartość obu układów i obu klas.
res/layout/activity_main.xml — pełny plik
<?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>
MainActivity.kt — pełny plik
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)
    }
}
res/layout/activity_potwierdzenie.xml — pełny plik
<?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>
PotwierdzenieActivity.kt — pełny plik
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ę przez return — intencja nie powstaje.
  • Napis wybranej konkurencji pochodzi z zaznaczonego RadioButton. W grupie zawsze coś jest zaznaczone, bo pierwszy przycisk ma android:checked="true".
  • Cztery putExtra z różnymi typami: dwa napisy, liczba (wiek po sprawdzeniu jest już Int, nie Int?) i Boolean.
  • Drugi ekran przepisuje dane z intent do swoich pól w odczytajDane(). Potem korzystają z nich kategoria() i pokazDane().
  • 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

  1. W MainActivity zmień klucz na putExtra("Imie", imie) (wielka litera). Na potwierdzeniu pojawi się „?” — klucze rozróżniają wielkość liter.
  2. Zamień putExtra("wiek", wiek) na putExtra("wiek", wiek.toString()). Ekran pokaże „0 lat”: wysłano napis, a getIntExtra szuka liczby i oddaje wartość domyślną.
  3. Wypełnij formularz, przejdź dalej i kliknij Popraw. Wszystkie pola i zaznaczenia zostały — nic nie trzeba wpisywać od nowa.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
intencja.putExtra("klucz", wartosc)metodaWkłada wartość (String, Int, Double, Boolean…) pod kluczem.
intent (w aktywności)IntentIntencja, która otworzyła ten ekran.
intent.getStringExtra("klucz")String?Napis albo null.
intent.getIntExtra("klucz", domyślna)IntLiczba albo wartość domyślna.
intent.getBooleanExtra("klucz", domyślna)BooleanWartość logiczna albo domyślna.
intent.getDoubleExtra("klucz", domyślna)DoubleLiczba z przecinkiem albo domyślna.
intent.hasExtra("klucz")BooleanCzy klucz został wysłany.
companion object { const val K = "k" }składniaStałe klasy — klucze bez literówek.
x !in 7..19operator„x nie należy do przedziału 7–19”.
CZĘSTE BŁĘDY

Zanim utkniesz

Zapis / objawProblem
drugi ekran pokazuje null, „?” albo 0Różne klucze przy wysyłaniu i odbiorze (literówka, wielkość liter).
getIntExtra zwraca 0, choć wysłano wiekWysłano napis ("15" albo et.text.toString()). Zamień na liczbę przed putExtra.
val imie: String = intent.getStringExtra("imie") → błąd kompilacjiMetoda zwraca String?. Dodaj ?: "".
getIntExtra("wiek") → błąd kompilacjiBrak drugiego argumentu — wartości domyślnej.
putExtra po startActivityIntencja 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 kompilacjiNajpierw sprawdź null — po sprawdzeniu Kotlin traktuje zmienną jako Int.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Empty Views Activity; drugi ekran twórz kreatorem. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Powitanie★☆☆

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"), potem if (imie.isNullOrBlank()).
ZAD 2Jeden ekran dla trzech przedmiotów★☆☆

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 MainActivity metodę otworz(nazwa: String, nauczyciel: String, sala: Int) i wywołuj ją z trzech przycisków.
ZAD 3Kalkulator BMI w dwóch ekranach★★☆

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).
ZAD 4Lista → szczegóły★★☆

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 ekranie val nr = intent.getIntExtra("nr", -1); gdy -1 — finish().
ZAD 5Zamówienie pizzy★★☆

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ślij joinToString(", ") jako jeden napis.
ZAD 6Zapis na zawody ze stałymi★★★

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 samo KLASA.
PODSUMOWANIE

Co trzeba zapamiętać

  • Wysyłanie: intencja.putExtra("klucz", wartosc) — przed startActivity.
  • Odbiór: intent.get…Extra("klucz"); napis może być null, liczba i Boolean mają 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.

Postęp zapisuje się w tej przeglądarce.