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

school-it.pl // android + kotlin // rozdział 2 // lekcja 06

EditText — pole tekstowe

EditText to pole, w które użytkownik wpisuje tekst: imię, hasło, kwotę, liczbę. W tej lekcji wybierzesz odpowiednią klawiaturę dla każdego pola, odczytasz wpisany tekst, zamienisz go na liczbę bez ryzyka awarii i pokażesz komunikat błędu dokładnie przy polu, które wypełniono źle.

Kotlin XML EditText inputType hint toIntOrNull error 2 × 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Dodasz pole tekstowe z podpowiedzią (hint).
  • Dobierzesz rodzaj klawiatury atrybutem inputType.
  • Odczytasz tekst z pola i usuniesz z niego zbędne spacje.
  • Zamienisz tekst na liczbę funkcjami toIntOrNull i toDoubleOrNull.
  • Sprawdzisz dane i pokażesz błąd przy polu (error).
TEORIA

Pole w XML

pole na imię
<EditText
    android:id="@+id/etImie"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Imię"
    android:inputType="textPersonName"
    android:maxLength="30" />
AtrybutZnaczenie
android:hintSzary tekst podpowiedzi widoczny, gdy pole jest puste. Znika po wpisaniu pierwszej litery. To nie jest wartość pola.
android:textTekst wpisany na start (prawdziwa wartość, którą użytkownik może zmienić).
android:inputTypeRodzaj danych — decyduje o klawiaturze (tabela niżej).
android:maxLengthMaksymalna liczba znaków.
android:minLines / maxLinesWysokość pola wielowierszowego.

Ładniejsze pola: TextInputLayout

W wielu aplikacjach zobaczysz pola w ramce z napisem, który „wjeżdża” nad pole przy pisaniu. To TextInputLayout z biblioteki Material. Działa na tej samej zasadzie — w środku jest pole tekstowe — ale ma więcej kodu w XML. W kursie zostajemy przy zwykłym EditText.

TEORIA

Rodzaj klawiatury

inputType mówi telefonowi, co będzie wpisywane. Telefon pokazuje wtedy pasującą klawiaturę, a czasem blokuje nieprawidłowe znaki:

inputTypeKlawiatura i zachowaniePrzykład użycia
textzwykła, z literamitytuł, opis
textPersonNamezwykła, wielka litera na początkuimię, nazwisko
textEmailAddressz klawiszem @e-mail
textPasswordznaki ukryte kropkamihasło
numbertylko cyfry, bez minusa i przecinkawiek, liczba osób
numberDecimalcyfry i separator dziesiętnykwota, waga
numberSignedcyfry i minustemperatura
phoneklawiatura telefonicznanumer telefonu
textMultiLineEnter tworzy nową linięwiadomość, uwagi

Wartości można łączyć kreską: numberDecimal|numberSigned pozwala wpisać liczbę ujemną z przecinkiem.

12:30
12
1
2
3
4
5
6
7
8
9
⌫
0
✓
inputType=”number”
12:30
anna.nowak@
q w e r t y u i o p
a s d f g h j k l
⇧ z x c v b n m ⌫
@spacja.
inputType=”textEmailAddress”
12:30
Login
••••••••
textPassword: znaki są ukryte
hint i hasło

wygląd klawiatury zależy od telefonu; inputType decyduje o jej rodzaju

inputType nie zastępuje sprawdzania danych

Klawiatura number nie przepuści liter, ale pole może być puste, a liczba — za duża lub zerowa. Użytkownik może też wkleić tekst ze schowka. Dane z pola zawsze sprawdzasz w kodzie.

TEORIA

Odczyt tekstu

Tekst w polu odczytujesz zwykle po kliknięciu przycisku — wtedy użytkownik już coś wpisał:

MainActivity.kt — fragment onCreate
val etImie = findViewById<EditText>(R.id.etImie)
val btnPowitaj = findViewById<Button>(R.id.btnPowitaj)
val tvWynik = findViewById<TextView>(R.id.tvWynik)

btnPowitaj.setOnClickListener {
    val imie = etImie.text.toString().trim()
    if (imie.isEmpty()) {
        tvWynik.text = "Wpisz imię"
    } else {
        tvWynik.text = "Cześć, $imie!"
    }
}
FragmentZnaczenie
etImie.textZawartość pola — obiekt typu Editable (tekst, który da się edytować).
.toString()Zamienia go na zwykły napis String.
.trim()Usuwa spacje z początku i końca. „ Anna ” staje się „Anna”, a pole z samymi spacjami — pustym napisem.
.isEmpty()true, gdy napis nie ma żadnego znaku.
etImie.setText("…")Wpisuje tekst do pola. setText("") czyści pole.

Kolejność zawsze ta sama: odczytaj → sprawdź → użyj. Najpierw zamieniasz zawartość pola na napis, sprawdzasz, czy ma sens, a dopiero potem go wykorzystujesz.

TEORIA

Tekst na liczbę

Zawartość pola to zawsze tekst, nawet przy klawiaturze numerycznej. Żeby liczyć, trzeba zamienić tekst na liczbę. Kotlin ma dwie wersje każdej zamiany:

dwie wersje zamiany
val a = "12".toInt()            // 12
val b = "".toInt()              // AWARIA: NumberFormatException — aplikacja się zamyka

val c = "12".toIntOrNull()      // 12
val d = "".toIntOrNull()        // null — brak liczby, ale bez awarii
val e = "abc".toIntOrNull()     // null

W aplikacjach zawsze używamy wersji …OrNull. Gdy tekst nie jest liczbą, dostajesz null („brak wartości”) i możesz pokazać błąd zamiast zamykać aplikację:

bezpieczny odczyt liczby
val wiek = etWiek.text.toString().trim().toIntOrNull()
if (wiek == null) {
    tvWynik.text = "To nie jest liczba"
} else {
    tvWynik.text = "Za rok będziesz mieć ${wiek + 1} lat"   // tu wiek na pewno jest liczbą
}
FunkcjaWynik
toIntOrNull()liczba całkowita Int albo null
toDoubleOrNull()liczba z częścią ułamkową Double albo null
toLongOrNull()duża liczba całkowita Long albo null

Przecinek czy kropka?

Kotlin rozumie tylko kropkę: "12.5".toDoubleOrNull() to 12.5, a "12,5".toDoubleOrNull() to null. Polska klawiatura może jednak podać przecinek. Dlatego przed zamianą robimy .replace(',', '.').

TEORIA

Błąd przy polu

Zamiast ogólnego „Błędne dane” najlepiej pokazać błąd przy tym polu, które jest źle wypełnione. EditText ma do tego właściwość error — pokazuje czerwony wykrzyknik i dymek z twoim tekstem:

błąd przy polu i przerwanie
btnOblicz.setOnClickListener {
    val wiek = etWiek.text.toString().trim().toIntOrNull()
    if (wiek == null) {
        etWiek.error = "Podaj wiek"
        etWiek.requestFocus()             // kursor przechodzi do tego pola
        return@setOnClickListener         // przerwij — dalszy kod się nie wykona
    }
    // tu wiemy, że wiek jest liczbą
}

return@setOnClickListener oznacza „wyjdź z tych klamer”. Zwykłe return w lambdzie nie zadziała — kompilator zgłosi błąd. Błąd przy polu znika sam, gdy użytkownik zacznie w nim pisać.

PRZYKŁAD

Podział rachunku

Klasa po wycieczce idzie na pizzę. Aplikacja dzieli rachunek: kwota, liczba osób i napiwek w procentach (pole można zostawić puste — wtedy napiwku nie ma). Każde pole jest sprawdzane osobno, a błąd pojawia się przy tym, które jest źle wypełnione. Wyczyść opróżnia pola.

16:20
Podział rachunku
Kwota rachunku (zł)
Liczba osób
Napiwek % (opcjonalnie)
Wyczyść
Oblicz
puste pola — widać podpowiedzi
16:21
Podział rachunku
186,50
0!
Liczba osób musi być większa od zera
Napiwek % (opcjonalnie)
Wyczyść
Oblicz
błąd przy polu liczby osób
16:21
Podział rachunku
186,50
5
10
Wyczyść
Oblicz
Razem z napiwkiem: 205,15 zł
Każdy płaci: 41,03 zł
poprawne dane — wynik

186,50 zł + 10% = 205,15 zł; podzielone na 5 osób daje 41,03 zł

Przygotowanie projektu

  1. Nowy projekt Empty Views Activity, nazwa PodzialRachunku, Kotlin, API 24.
  2. Podmień activity_main.xml i MainActivity.kt.
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:layout_marginBottom="8dp"
        android:text="Podział rachunku"
        android:textSize="24sp"
        android:textStyle="bold" />

    <EditText
        android:id="@+id/etKwota"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Kwota rachunku (zł)"
        android:inputType="numberDecimal" />

    <EditText
        android:id="@+id/etOsoby"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Liczba osób"
        android:inputType="number"
        android:maxLength="3" />

    <EditText
        android:id="@+id/etNapiwek"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Napiwek % (opcjonalnie)"
        android:inputType="number"
        android:maxLength="2" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnWyczysc"
            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="Wyczyść" />

        <Button
            android:id="@+id/btnOblicz"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Oblicz" />
    </LinearLayout>

    <TextView
        android:id="@+id/tvRazem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tvNaOsobe"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="26sp"
        android:textStyle="bold" />

</LinearLayout>
MainActivity.kt — pełny plik
package pl.schoolit.podzialrachunku

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val etKwota = findViewById<EditText>(R.id.etKwota)
        val etOsoby = findViewById<EditText>(R.id.etOsoby)
        val etNapiwek = findViewById<EditText>(R.id.etNapiwek)
        val btnOblicz = findViewById<Button>(R.id.btnOblicz)
        val btnWyczysc = findViewById<Button>(R.id.btnWyczysc)
        val tvRazem = findViewById<TextView>(R.id.tvRazem)
        val tvNaOsobe = findViewById<TextView>(R.id.tvNaOsobe)

        btnOblicz.setOnClickListener {
            // 1. ODCZYT: tekst z pól, przecinek zamieniony na kropkę
            val kwota = etKwota.text.toString().trim().replace(',', '.').toDoubleOrNull()
            val osoby = etOsoby.text.toString().trim().toIntOrNull()
            val napiwekTekst = etNapiwek.text.toString().trim()

            // 2. SPRAWDZENIE: każde pole osobno, błąd przy złym polu
            if (kwota == null || kwota <= 0) {
                etKwota.error = "Podaj kwotę większą od zera"
                etKwota.requestFocus()
                return@setOnClickListener
            }
            if (osoby == null || osoby < 1) {
                etOsoby.error = "Liczba osób musi być większa od zera"
                etOsoby.requestFocus()
                return@setOnClickListener
            }

            // napiwek jest opcjonalny: puste pole = 0%
            var napiwek = 0
            if (napiwekTekst.isNotEmpty()) {
                napiwek = napiwekTekst.toInt()   // bezpieczne: inputType="number" i maxLength="2"
            }

            // 3. UŻYCIE: obliczenia i wynik
            val razem = kwota * (100 + napiwek) / 100
            val naOsobe = razem / osoby

            tvRazem.text = "Razem z napiwkiem: ${String.format("%.2f", razem)} zł"
            tvNaOsobe.text = "Każdy płaci: ${String.format("%.2f", naOsobe)} zł"
        }

        btnWyczysc.setOnClickListener {
            etKwota.setText("")
            etOsoby.setText("")
            etNapiwek.setText("")
            tvRazem.text = ""
            tvNaOsobe.text = ""
            etKwota.requestFocus()
        }
    }
}

Co dzieje się po kolei

  • Pole kwoty ma klawiaturę numberDecimal, a pola osób i napiwku — number z ograniczoną długością. Klawiatura dużo ułatwia, ale nie zwalnia ze sprawdzania.
  • Odczyt: z każdego pola bierzemy tekst, obcinamy spacje, w kwocie zamieniamy przecinek na kropkę i robimy z tekstu liczbę wersją …OrNull.
  • Sprawdzenie: warunek kwota == null || kwota <= 0 łapie naraz puste pole, bzdurę i zero. Przy błędzie: komunikat przy polu, kursor do pola, wyjście z klamer.
  • Po obu sprawdzeniach Kotlin „wie”, że kwota i osoby nie są już null — dlatego dalej można na nich liczyć.
  • Napiwek jest opcjonalny. Tu wyjątkowo używamy toInt(): niepuste pole z klawiaturą number i limitem 2 znaków zawsze zawiera liczbę 0–99.
  • String.format("%.2f", razem) zaokrągla do dwóch miejsc po przecinku i — na polskim telefonie — używa przecinka.

Sprawdź trzy rzeczy

  1. Kliknij Oblicz przy pustych polach. Błąd pojawi się tylko przy kwocie — sprawdzanie zatrzymuje się na pierwszym złym polu.
  2. Wpisz kwotę z przecinkiem, a potem z kropką. Oba warianty działają dzięki replace(',', '.').
  3. Zamień w kwocie toDoubleOrNull() na toDouble() i kliknij Oblicz przy pustym polu. Aplikacja się zamknie — w Logcat znajdziesz NumberFormatException. Przywróć wersję OrNull.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
<EditText>kontrolkaPole do wpisywania tekstu.
android:hintatrybutPodpowiedź w pustym polu.
android:inputTypeatrybutRodzaj danych i klawiatury.
android:maxLengthatrybutLimit znaków.
et.text.toString()StringZawartość pola jako napis.
.trim()StringNapis bez spacji na brzegach.
.isEmpty(), .isNotEmpty()BooleanCzy napis jest pusty / niepusty.
.replace(',', '.')StringZamienia każdy przecinek na kropkę.
.toIntOrNull(), .toDoubleOrNull()Int?, Double?Liczba albo null — bez awarii.
et.setText("…")metodaWpisuje tekst do pola.
et.error = "…"właściwośćBłąd przy polu.
et.requestFocus()metodaPrzenosi kursor do pola.
return@setOnClickListenerinstrukcjaKończy kod w klamrach przycisku.
String.format("%.2f", x)StringLiczba z dwoma miejscami po przecinku.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
etKwota.text.toInt()Błąd kompilacji — text to Editable, a nie napis. Najpierw .toString().
etImie.text = "Anna"Błąd kompilacji: Type mismatch. Do pola wpisujesz przez setText("Anna").
"".toInt(), "12,5".toDouble()Awaria NumberFormatException. Używaj …OrNull, a przecinek zamieniaj na kropkę.
return w klamrach przyciskuBłąd kompilacji: ’return’ is not allowed here. Pisz return@setOnClickListener.
odczyt pola w onCreate poza listeneremZawsze pusty napis — w chwili startu użytkownik nic nie wpisał. Czytaj w kodzie przycisku.
sprawdzanie hint zamiast tekstuhint nie jest wartością pola. Pole z samą podpowiedzią ma tekst "".
osoby / 3 daje 0 przy osoby = 2Dzielenie dwóch liczb Int obcina ułamek. Zamień jedną na Double: osoby.toDouble() / 3.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Empty Views Activity. Cały kod pisz w onCreate. W każdym zadaniu sprawdzaj dane i pokazuj błędy przy polach. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Powitanie★☆☆

Pola Imię i Nazwisko oraz przycisk Powitaj. Wynik: „Dzień dobry, Anna Nowak!”, a pod spodem „Twoje inicjały: A.N.”. Puste pole — błąd przy tym polu.

pokaż wskazówkę
  • Pierwsza litera napisu: imie[0], wielka: imie[0].uppercaseChar().
ZAD 2Przelicznik temperatur★☆☆

Pole na temperaturę (może być ujemna i z przecinkiem) i dwa przyciski: °C → °F oraz °F → °C. Wynik z jedną cyfrą po przecinku, np. „-5,0 °C = 23,0 °F”.

pokaż wskazówkę
  • inputType="numberDecimal|numberSigned". Wzory: F = C × 9 / 5 + 32, C = (F − 32) × 5 / 9.
ZAD 3Średnia ocen★★☆

Jedno pole, w które wpisuje się oceny oddzielone spacjami: „5 4 6 3 5”. Przycisk Oblicz pokazuje liczbę ocen, średnią z dwoma miejscami po przecinku i ocenę najwyższą. Jeśli którakolwiek „ocena” nie jest liczbą 1–6, pole dostaje błąd „Niepoprawna ocena: 7”.

pokaż wskazówkę
  • tekst.split(" ") daje listę napisów. Przejdź po niej pętlą for, każdy element zamień przez toIntOrNull() i sprawdź zakres. Pomiń puste kawałki (podwójne spacje).
ZAD 4Rejestracja konta★★☆

Pola: login, e-mail, hasło, powtórz hasło, rok urodzenia. Przycisk Załóż konto sprawdza: login 3–15 znaków bez spacji; e-mail zawiera @ i kropkę po nim; hasło ma co najmniej 8 znaków i przynajmniej jedną cyfrę; hasła są takie same; rok urodzenia między 1920 a 2026. Każdy błąd pokazuje się przy swoim polu. Sukces: „Konto anowak utworzone”.

pokaż wskazówkę
  • Cyfra w haśle: haslo.any { it.isDigit() }. Spacja w loginie: login.contains(" "). Kropka po @: email.substringAfter("@").contains(".").
ZAD 5Zgadnij liczbę★★☆

Aplikacja losuje liczbę 1–100. Użytkownik wpisuje propozycję i klika Sprawdź: „Za mało”, „Za dużo” albo „Trafione w 7 próbach!”. Po każdej próbie pole się czyści. Pod polem lista dotychczasowych prób: „50 ↑, 75 ↓, 62 ↑…”. Liczba spoza zakresu — błąd przy polu (nie liczy się jako próba).

pokaż wskazówkę
  • Liczbę do zgadnięcia i licznik prób trzymaj w val/var przed listenerem (lekcja 05). Historię dopisuj przez tvHistoria.append("$liczba ↑, ").
ZAD 6Kalkulator raty★★★

Kalkulator zakupu na raty: cena telefonu, wpłata własna, liczba rat (3–48) i oprocentowanie roczne w procentach (może być 0 i z przecinkiem). Wpłata nie może być większa niż cena. Wynik: kwota kredytu, wysokość raty, całkowity koszt i ile przepłacasz. Rata liczona wzorem na ratę równą: R = K × r / (1 − (1 + r)−n), gdzie r to oprocentowanie miesięczne (roczne / 12 / 100). Przy oprocentowaniu 0 rata to K / n.

pokaż wskazówkę
  • Potęga w Kotlinie: Math.pow(1 + r, -n.toDouble()) albo (1 + r).pow(-n) z import kotlin.math.pow. Zero sprawdź osobnym if — inaczej dzielisz przez zero.
PODSUMOWANIE

Co trzeba zapamiętać

  • hint to podpowiedź, nie wartość; inputType wybiera klawiaturę.
  • Odczyt: et.text.toString().trim(); zapis: et.setText("…").
  • Liczby tylko przez toIntOrNull() / toDoubleOrNull() — null oznacza błędne dane.
  • Przed zamianą na Double: replace(',', '.').
  • Błąd pokazuj przy polu (error) i przerywaj przez return@setOnClickListener.

Dokumentacja: EditText, Rodzaj klawiatury (inputType).

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