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.
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
toIntOrNullitoDoubleOrNull. - Sprawdzisz dane i pokażesz błąd przy polu (
error).
Pole w XML
<EditText
android:id="@+id/etImie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Imię"
android:inputType="textPersonName"
android:maxLength="30" />
| Atrybut | Znaczenie |
|---|---|
android:hint | Szary tekst podpowiedzi widoczny, gdy pole jest puste. Znika po wpisaniu pierwszej litery. To nie jest wartość pola. |
android:text | Tekst wpisany na start (prawdziwa wartość, którą użytkownik może zmienić). |
android:inputType | Rodzaj danych — decyduje o klawiaturze (tabela niżej). |
android:maxLength | Maksymalna liczba znaków. |
android:minLines / maxLines | Wysokość 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.
Rodzaj klawiatury
inputType mówi telefonowi, co będzie wpisywane. Telefon pokazuje wtedy pasującą klawiaturę, a czasem blokuje nieprawidłowe znaki:
| inputType | Klawiatura i zachowanie | Przykład użycia |
|---|---|---|
text | zwykła, z literami | tytuł, opis |
textPersonName | zwykła, wielka litera na początku | imię, nazwisko |
textEmailAddress | z klawiszem @ | |
textPassword | znaki ukryte kropkami | hasło |
number | tylko cyfry, bez minusa i przecinka | wiek, liczba osób |
numberDecimal | cyfry i separator dziesiętny | kwota, waga |
numberSigned | cyfry i minus | temperatura |
phone | klawiatura telefoniczna | numer telefonu |
textMultiLine | Enter tworzy nową linię | wiadomość, uwagi |
Wartości można łączyć kreską: numberDecimal|numberSigned pozwala wpisać liczbę ujemną z przecinkiem.
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.
Odczyt tekstu
Tekst w polu odczytujesz zwykle po kliknięciu przycisku — wtedy użytkownik już coś wpisał:
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!"
}
}
| Fragment | Znaczenie |
|---|---|
etImie.text | Zawartość 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.
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:
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ę:
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ą
}
| Funkcja | Wynik |
|---|---|
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(',', '.').
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:
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ć.
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.
186,50 zł + 10% = 205,15 zł; podzielone na 5 osób daje 41,03 zł
Przygotowanie projektu
- Nowy projekt Empty Views Activity, nazwa
PodzialRachunku, Kotlin, API 24. - Podmień
activity_main.xmliMainActivity.kt.
<?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>
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 —numberz 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
kwotaiosobynie są jużnull— dlatego dalej można na nich liczyć. - Napiwek jest opcjonalny. Tu wyjątkowo używamy
toInt(): niepuste pole z klawiaturąnumberi 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
- Kliknij Oblicz przy pustych polach. Błąd pojawi się tylko przy kwocie — sprawdzanie zatrzymuje się na pierwszym złym polu.
- Wpisz kwotę z przecinkiem, a potem z kropką. Oba warianty działają dzięki
replace(',', '.'). - Zamień w kwocie
toDoubleOrNull()natoDouble()i kliknij Oblicz przy pustym polu. Aplikacja się zamknie — w Logcat znajdziesz NumberFormatException. Przywróć wersjęOrNull.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
<EditText> | kontrolka | Pole do wpisywania tekstu. |
android:hint | atrybut | Podpowiedź w pustym polu. |
android:inputType | atrybut | Rodzaj danych i klawiatury. |
android:maxLength | atrybut | Limit znaków. |
et.text.toString() | String | Zawartość pola jako napis. |
.trim() | String | Napis bez spacji na brzegach. |
.isEmpty(), .isNotEmpty() | Boolean | Czy napis jest pusty / niepusty. |
.replace(',', '.') | String | Zamienia każdy przecinek na kropkę. |
.toIntOrNull(), .toDoubleOrNull() | Int?, Double? | Liczba albo null — bez awarii. |
et.setText("…") | metoda | Wpisuje tekst do pola. |
et.error = "…" | właściwość | Błąd przy polu. |
et.requestFocus() | metoda | Przenosi kursor do pola. |
return@setOnClickListener | instrukcja | Kończy kod w klamrach przycisku. |
String.format("%.2f", x) | String | Liczba z dwoma miejscami po przecinku. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
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 przycisku | Błąd kompilacji: ’return’ is not allowed here. Pisz return@setOnClickListener. |
odczyt pola w onCreate poza listenerem | Zawsze pusty napis — w chwili startu użytkownik nic nie wpisał. Czytaj w kodzie przycisku. |
sprawdzanie hint zamiast tekstu | hint nie jest wartością pola. Pole z samą podpowiedzią ma tekst "". |
osoby / 3 daje 0 przy osoby = 2 | Dzielenie dwóch liczb Int obcina ułamek. Zamień jedną na Double: osoby.toDouble() / 3. |
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.
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().
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.
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ń przeztoIntOrNull()i sprawdź zakres. Pomiń puste kawałki (podwójne spacje).
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(".").
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/varprzed listenerem (lekcja 05). Historię dopisuj przeztvHistoria.append("$liczba ↑, ").
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)zimport kotlin.math.pow. Zero sprawdź osobnymif— inaczej dzielisz przez zero.
Co trzeba zapamiętać
hintto podpowiedź, nie wartość;inputTypewybiera klawiaturę.- Odczyt:
et.text.toString().trim(); zapis:et.setText("…"). - Liczby tylko przez
toIntOrNull()/toDoubleOrNull()—nulloznacza błędne dane. - Przed zamianą na
Double:replace(',', '.'). - Błąd pokazuj przy polu (
error) i przerywaj przezreturn@setOnClickListener.
Dokumentacja: EditText, Rodzaj klawiatury (inputType).