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

school-it.pl // android + kotlin // rozdział 8 // lekcja 34

Intencje niejawne

Twoja aplikacja nie musi umieć dzwonić, wyświetlać stron ani pokazywać map — w telefonie są już aplikacje, które robią to świetnie. Wystarczy powiedzieć Androidowi, co chcesz zrobić („zadzwoń pod ten numer”), a nie który ekran otworzyć. Taka intencja nazywa się niejawna. Zbudujesz wizytówkę szkoły z przyciskami: zadzwoń, napisz e-mail, otwórz stronę, pokaż na mapie i udostępnij.

Kotlin XML ACTION_DIAL ACTION_VIEW ACTION_SENDTO ACTION_SEND Uri try / catch 2 × 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Odróżnisz intencję jawną (konkretny ekran) od niejawnej (czynność).
  • Otworzysz wybieranie numeru, przeglądarkę, mapę i program pocztowy.
  • Zbudujesz adres Uri dla każdej czynności.
  • Udostępnisz tekst z oknem wyboru aplikacji (createChooser).
  • Zabezpieczysz aplikację przed brakiem odpowiedniej aplikacji: try / catch.
TEORIA

Jawna a niejawna

Intencja jawna (lekcje 30–33)Intencja niejawna (ta lekcja)
co podajeszklasę ekranu: Intent(this, DzwonkiActivity::class.java)czynność i dane: Intent(Intent.ACTION_DIAL, Uri.parse("tel:…"))
kto otwierazawsze ten jeden ekran twojej aplikacjiaplikacja w telefonie, która potrafi to zrobić
przykład z życia„Ola, przynieś kredꔄNiech ktoś przyniesie kredę”
twoja aplikacja
„chcę zadzwonić
pod 59 000 00 00”
→
Android
szuka aplikacji,
która to obsługuje
→
aplikacja Telefon
otwiera się z wpisanym
numerem

Gdy pasuje kilka aplikacji (np. dwie przeglądarki), Android zapyta użytkownika, której użyć. Gdy nie pasuje żadna — zgłosi błąd, który trzeba przechwycić (sekcja „Gdy nie ma takiej aplikacji”).

TEORIA

Akcja i adres (Uri)

Intencja niejawna ma dwie części: akcję (co zrobić) i adres danych typu Uri (na czym). Początek adresu, np. tel: czy https:, mówi Androidowi, jakiego rodzaju to dane.

CzynnośćAkcjaAdres Uri
wybierz numerIntent.ACTION_DIAL"tel:590000000"
otwórz stronęIntent.ACTION_VIEW"https://school-it.pl"
pokaż na mapieIntent.ACTION_VIEW"geo:0,0?q=" + Uri.encode("Lębork, Polska")
napisz e-mailIntent.ACTION_SENDTO"mailto:sekretariat@szkola.example"
cztery intencje niejawne
// numer pojawi się w aplikacji Telefon — dzwoni dopiero użytkownik
startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:590000000")))

// ta sama akcja VIEW — o aplikacji decyduje początek adresu
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://school-it.pl")))
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=" + Uri.encode("Lębork, Polska"))))

// e-mail z gotowym tematem
val mail = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:sekretariat@szkola.example"))
mail.putExtra(Intent.EXTRA_SUBJECT, "Pytanie o rekrutację")
startActivity(mail)

Uri.encode zamienia spacje i polskie litery na zapis bezpieczny w adresie. Bez niego adres z nazwą ulicy może się „urwać” na pierwszej spacji.

DIAL, a nie CALL

Istnieje też ACTION_CALL, która dzwoni od razu. Wymaga jednak uprawnienia CALL_PHONE i zgody użytkownika w czasie działania aplikacji. ACTION_DIAL tylko wpisuje numer — przycisk połączenia naciska użytkownik — i nie wymaga żadnych uprawnień. W prawie każdej aplikacji to właściwy wybór.

TEORIA

Udostępnianie tekstu

„Udostępnij” wysyła tekst do dowolnej aplikacji: komunikatora, poczty, notatek. Tu nie ma adresu — jest typ danych i sam tekst w extras:

udostępnianie z oknem wyboru
val intencja = Intent(Intent.ACTION_SEND)
intencja.type = "text/plain"                                   // wysyłamy zwykły tekst
intencja.putExtra(Intent.EXTRA_TEXT, "Dzień otwarty: 20 marca, godz. 10:00")

startActivity(Intent.createChooser(intencja, "Udostępnij przez"))

Intent.createChooser zawsze pokazuje systemowe okno wyboru aplikacji z podanym tytułem — także wtedy, gdy użytkownik kiedyś zaznaczył „zawsze używaj tej aplikacji”. Przy udostępnianiu właśnie tego chcemy.

TEORIA

Gdy nie ma takiej aplikacji

Na emulatorze bez Map Google albo na tablecie bez aplikacji Telefon startActivity zgłasza wyjątek ActivityNotFoundException i aplikacja się zamyka. Wyjątek to „zgłoszony błąd”, który można przechwycić i obsłużyć po swojemu:

bezpieczne uruchamianie intencji
private fun uruchom(intencja: Intent) {
    try {
        startActivity(intencja)                 // spróbuj…
    } catch (e: ActivityNotFoundException) {
        // …a gdy się nie uda — zamiast awarii komunikat
        Toast.makeText(this, "Brak aplikacji, która to obsłuży", Toast.LENGTH_SHORT).show()
    }
}
try { … }
wykonaj kod, który
może się nie udać
udało się
catch jest pomijany
catch (e: …)
wyjątek tego typu?
wykonaj ten kod zamiast awarii

Wszystkie przyciski w przykładzie wywołują uruchom(…) — obsługa braku aplikacji jest napisana raz.

PRZYKŁAD

Wizytówka szkoły

Ekran z nazwą szkoły, adresem, telefonem i e-mailem. Pięć przycisków uruchamia intencje niejawne: wybieranie numeru, e-mail z tematem, stronę WWW, mapę i udostępnienie danych kontaktowych. Brak aplikacji kończy się komunikatem, nie awarią.

9:00
Technikum nr 1
ul. Szkolna 1, Lębork
📞 59 000 00 00
✉️ sekretariat@szkola.example
🌐 https://school-it.pl
Zadzwoń
Napisz e-mail
Otwórz stronę szkoły
Pokaż na mapie
Udostępnij kontakt
MainActivity — wizytówka
→ACTION_DIAL
9:00
Telefon
590 000 000
123456789*0#
📞
aplikacja Telefon — numer już wpisany
9:01
Technikum nr 1
ul. Szkolna 1, Lębork
📞 59 000 00 00
✉️ sekretariat@szkola.example
🌐 https://school-it.pl
Zadzwoń
Napisz e-mail
Otwórz stronę szkoły
Pokaż na mapie
Udostępnij kontakt
Udostępnij przez
💬Wiadomości ✉️Poczta 📝Notatki 📋Kopiuj
Udostępnij — okno createChooser

lista aplikacji w oknie wyboru zależy od telefonu — Android pokazuje te, które przyjmują tekst

Przygotowanie projektu

  1. Nowy projekt Empty Views Activity, nazwa Wizytowka, Kotlin, API 24.
  2. Podmień activity_main.xml i MainActivity.kt. Manifestu nie trzeba zmieniać — nie otwieramy własnych ekranów i nie potrzebujemy uprawnień.
  3. Do testów użyj emulatora z obrazem Google Play albo prawdziwego telefonu — tam są aplikacje Telefon, Chrome i Mapy.
res/layout/activity_main.xml — pełny plik
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

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

        <TextView
            android:id="@+id/tvAdres"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/tvKontakt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:lineSpacingExtra="4dp"
            android:textSize="16sp" />

        <Button
            android:id="@+id/btnZadzwon"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Zadzwoń" />

        <Button
            android:id="@+id/btnEmail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Napisz e-mail" />

        <Button
            android:id="@+id/btnStrona"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Otwórz stronę szkoły" />

        <Button
            android:id="@+id/btnMapa"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Pokaż na mapie" />

        <Button
            android:id="@+id/btnUdostepnij"
            style="@style/Widget.Material3.Button.OutlinedButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Udostępnij kontakt" />
    </LinearLayout>

</ScrollView>
MainActivity.kt — pełny plik
package pl.schoolit.wizytowka

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    // ---------- kontrolki ----------
    private lateinit var tvNazwa: TextView
    private lateinit var tvAdres: TextView
    private lateinit var tvKontakt: TextView
    private lateinit var btnZadzwon: Button
    private lateinit var btnEmail: Button
    private lateinit var btnStrona: Button
    private lateinit var btnMapa: Button
    private lateinit var btnUdostepnij: Button

    // ---------- dane szkoły (przykładowe) ----------
    private val NAZWA = "Technikum nr 1"
    private val ADRES = "ul. Szkolna 1, Lębork"
    private val TELEFON = "590000000"
    private val EMAIL = "sekretariat@szkola.example"
    private val STRONA = "https://school-it.pl"

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

    private fun initUI() {
        tvNazwa = findViewById(R.id.tvNazwa)
        tvAdres = findViewById(R.id.tvAdres)
        tvKontakt = findViewById(R.id.tvKontakt)
        btnZadzwon = findViewById(R.id.btnZadzwon)
        btnEmail = findViewById(R.id.btnEmail)
        btnStrona = findViewById(R.id.btnStrona)
        btnMapa = findViewById(R.id.btnMapa)
        btnUdostepnij = findViewById(R.id.btnUdostepnij)
    }

    private fun ustawZdarzenia() {
        btnZadzwon.setOnClickListener { zadzwon() }
        btnEmail.setOnClickListener { napiszEmail() }
        btnStrona.setOnClickListener { otworzStrone() }
        btnMapa.setOnClickListener { pokazMape() }
        btnUdostepnij.setOnClickListener { udostepnij() }
    }

    // ---------- intencje niejawne ----------
    private fun zadzwon() {
        val intencja = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$TELEFON"))
        uruchom(intencja)
    }

    private fun napiszEmail() {
        val intencja = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:$EMAIL"))
        intencja.putExtra(Intent.EXTRA_SUBJECT, "Pytanie z aplikacji szkolnej")
        uruchom(intencja)
    }

    private fun otworzStrone() {
        uruchom(Intent(Intent.ACTION_VIEW, Uri.parse(STRONA)))
    }

    private fun pokazMape() {
        val adresMapy = Uri.parse("geo:0,0?q=" + Uri.encode(ADRES))   // spacje i „ę” zakodowane
        uruchom(Intent(Intent.ACTION_VIEW, adresMapy))
    }

    private fun udostepnij() {
        val tekst = "$NAZWA\n$ADRES\ntel. ${telefonCzytelny()}\n$EMAIL\n$STRONA"

        val intencja = Intent(Intent.ACTION_SEND)
        intencja.type = "text/plain"
        intencja.putExtra(Intent.EXTRA_TEXT, tekst)
        uruchom(Intent.createChooser(intencja, "Udostępnij przez"))
    }

    // jedno miejsce, które chroni przed brakiem aplikacji
    private fun uruchom(intencja: Intent) {
        try {
            startActivity(intencja)
        } catch (e: ActivityNotFoundException) {
            Toast.makeText(this, "Brak aplikacji, która to obsłuży", Toast.LENGTH_SHORT).show()
        }
    }

    // ---------- dane → ekran ----------
    // "590000000" → "59 000 00 00"
    private fun telefonCzytelny(): String =
        "${TELEFON.substring(0, 2)} ${TELEFON.substring(2, 5)} ${TELEFON.substring(5, 7)} ${TELEFON.substring(7)}"

    private fun pokazDane() {
        tvNazwa.text = NAZWA
        tvAdres.text = ADRES
        tvKontakt.text = "📞 ${telefonCzytelny()}\n✉️ $EMAIL\n🌐 $STRONA"
    }
}

Co dzieje się po kolei

  • Dane szkoły są w polach — zmiana numeru czy adresu to zmiana jednej linii, a nie szukanie po całym kodzie.
  • Każdy przycisk buduje intencję z akcją i adresem Uri, a potem przekazuje ją do uruchom().
  • Zadzwoń: aplikacja Telefon otwiera się z wpisanym numerem; użytkownik sam decyduje, czy dzwonić.
  • Napisz e-mail: ACTION_SENDTO z adresem mailto: otwiera tylko programy pocztowe; temat jest już wpisany.
  • Pokaż na mapie: geo:0,0?q=… to „wyszukaj ten adres”. Uri.encode chroni przed spacjami i polskimi literami.
  • Udostępnij: tekst w EXTRA_TEXT, typ text/plain i okno wyboru aplikacji.
  • Gdy żadna aplikacja nie przyjmie intencji, catch pokazuje Toast zamiast awarii.

Sprawdź trzy rzeczy

  1. Kliknij Zadzwoń. Numer jest wpisany, ale połączenie się nie zaczęło — tak działa ACTION_DIAL.
  2. Zmień adres strony na "school-it.pl" (bez https://). Zobaczysz komunikat o braku aplikacji: bez początku adresu Android nie wie, że to strona WWW.
  3. W uruchom() usuń try/catch (zostaw samo startActivity) i powtórz punkt 2. Teraz aplikacja się zamknie. Przywróć try/catch i poprawny adres.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
Intent(akcja, uri)IntentIntencja niejawna: czynność i dane.
Intent.ACTION_DIAL + tel:akcjaWpisuje numer w aplikacji Telefon.
Intent.ACTION_VIEW + https: / geo:akcjaPokazuje stronę lub miejsce na mapie.
Intent.ACTION_SENDTO + mailto:akcjaNowa wiadomość e-mail do adresata.
Intent.ACTION_SEND + type = "text/plain"akcjaUdostępnianie tekstu.
Intent.EXTRA_SUBJECT, Intent.EXTRA_TEXTkluczeGotowe klucze: temat i treść.
Intent.createChooser(intencja, "tytuł")IntentOkno wyboru aplikacji.
Uri.parse("…")UriZamienia napis na adres danych.
Uri.encode("…")StringKoduje spacje i znaki specjalne w adresie.
try { … } catch (e: ActivityNotFoundException) { … }składniaPrzechwytuje brak aplikacji zamiast awarii.
napis.substring(od, do)StringFragment napisu od pozycji od do do (bez niej).
CZĘSTE BŁĘDY

Zanim utkniesz

Zapis / objawProblem
aplikacja zamyka się, Logcat: ActivityNotFoundException: No Activity found to handle IntentBrak aplikacji dla tej czynności (np. Map na emulatorze) lub zły adres. Uruchamiaj przez try/catch.
Uri.parse("school-it.pl") nic nie otwieraAdres strony musi zaczynać się od https:// (lub http://).
Uri.parse("590000000") dla telefonuBrak przedrostka tel:.
ACTION_CALL → awaria SecurityExceptionWymaga uprawnienia CALL_PHONE. Użyj ACTION_DIAL.
mapa szuka tylko „ul.”Adres ze spacjami bez Uri.encode.
udostępnianie bez typu → brak aplikacjiPrzy ACTION_SEND ustaw type = "text/plain".
sprawdzanie intencja.resolveActivity(packageManager) != null zawsze daje „brak”Od Androida 11 aplikacja nie widzi innych aplikacji bez wpisu <queries> w manifeście. Prościej: try/catch.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Empty Views Activity. Każdą intencję uruchamiaj przez metodę z try/catch. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Wyszukiwarka★☆☆

Pole tekstowe i przycisk Szukaj. Kliknięcie otwiera w przeglądarce wyniki wyszukiwania wpisanej frazy.

pokaż wskazówkę
  • Uri.parse("https://duckduckgo.com/?q=" + Uri.encode(fraza)) z ACTION_VIEW.
ZAD 2Telefony alarmowe★☆☆

Trzy duże przyciski: „112 — numer alarmowy”, „999 — pogotowie”, „998 — straż pożarna”. Każdy wpisuje numer w aplikacji Telefon (nie dzwoni od razu!).

pokaż wskazówkę
  • Jedna metoda wybierz(numer: String) wywoływana z trzech przycisków.
ZAD 3Udostępnij wynik quizu★★☆

Dołóż do dowolnej wcześniejszej aplikacji z wynikiem (np. quiz z lekcji 27 lub kalkulator BMI z lekcji 31) przycisk Pochwal się, który udostępnia tekst „Mój wynik: 4/5 w quizie z Androida!”.

pokaż wskazówkę
  • ACTION_SEND, type = "text/plain", EXTRA_TEXT, createChooser.
ZAD 4Zgłoszenie usterki★★☆

Formularz: numer sali (Spinner), opis usterki (wieloliniowy EditText). Wyślij otwiera program pocztowy z adresem administratora, tematem „Usterka — sala 12” i opisem w treści.

pokaż wskazówkę
  • Treść: putExtra(Intent.EXTRA_TEXT, opis) obok EXTRA_SUBJECT. Gdy opis pusty — error przy polu i bez wysyłania.
ZAD 5Lista miejsc na mapie★★☆

ListView z 6 ciekawymi miejscami w twoim mieście. Kliknięcie wiersza pokazuje miejsce na mapie, przytrzymanie — udostępnia jego nazwę i adres.

pokaż wskazówkę
  • Dwie tablice (nazwy i adresy) albo klasa danych z lekcji 28. Mapa: "geo:0,0?q=" + Uri.encode("$nazwa, $adres").
ZAD 6Kontakty z akcjami★★★

Połącz lekcje 29 i 34: lista kontaktów z własnym adapterem. W każdym wierszu imię, numer oraz dwa ImageButton: słuchawka (ACTION_DIAL) i koperta (ACTION_SENDTO z smsto: — nowy SMS na ten numer). Kliknięcie wiersza udostępnia kontakt jako tekst.

pokaż wskazówkę
  • Adapter nie uruchamia intencji sam — przekaż mu funkcje zadzwon: (String) -> Unit i sms: (String) -> Unit z aktywności (lekcja 29). SMS: Uri.parse("smsto:$numer").
PODSUMOWANIE

Co trzeba zapamiętać

  • Intencja niejawna = akcja + dane; Android sam wybiera aplikację.
  • ACTION_DIAL + tel:, ACTION_VIEW + https:/geo:, ACTION_SENDTO + mailto:.
  • Udostępnianie: ACTION_SEND, type, EXTRA_TEXT, createChooser.
  • Tekst w adresie koduj przez Uri.encode.
  • Zawsze try/catch (ActivityNotFoundException) — nie każdy telefon ma każdą aplikację.

To koniec rozdziału o intencjach. W następnym: menu na pasku aplikacji, cykl życia aktywności i zapisywanie ustawień.

Dokumentacja: Typowe intencje, Udostępnianie danych.

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