Preskočiť na obsah

Ako vytvoriť kontaktný formulár pre Android v Kotline a Jetpack Compose

Kontaktná obrazovka v Jetpack Compose, ktorá vám každú správu pošle e-mailom cez vašu vlastnú schránku – bez backendu a bez SMTP hesla v APK. S Kotlin SDK od Frontmailu.

Aktualizované:

Video je v angličtine; nižšie nájdete celý návod v slovenčine.

Do aplikácie pre Android napísanej v Kotline a Jetpack Compose pridáte kontaktnú obrazovku. Aplikácia obsahuje iba verejný kľúč – e-mail odošle Frontmail cez vašu schránku, takže v APK nie je žiadne SMTP heslo, ktoré by si z neho mohol ktokoľvek vytiahnuť.

Potrebujete: bezplatný účet Frontmail, projekt pre Android s Compose a zhruba pätnásť minút.

1. Pripojte schránku a vytvorte šablónu

  1. E-mailové služby → Pridať službu: pripojte Gmail, Outlook, SMTP alebo transakčného poskytovateľa, zaškrtnite Nastaviť ako predvolenú, Overiť a uložiť.
  2. Šablóny → Z galérie → Kontaktný formulár → Použiť šablónu. Povinné parametre: name, email, message; nepovinné: phone, topic.
  3. Z karty Kód skopírujte ID šablóny (tpl_…).

2. Povoľte mobilné aplikácie

  • Zabezpečenie → Mobilné aplikácie → Povoliť mobilné aplikácie → Uložiť. Aplikácie neposielajú hlavičku Origin, takže ich API dovtedy odmieta s chybou origin_not_allowed.
  • Šablóna → Nastavenia → Ochrana → Vyžadovať CAPTCHA (Turnstile). Aplikácie používajú zdieľaný widget Turnstile od Frontmailu – kľúče od Cloudflare nepotrebujete.

3. Pridajte SDK

// app/build.gradle.kts
dependencies {
    implementation("dev.frontmail:frontmail:0.1.0")
}

Do AndroidManifest.xml pridajte <uses-permission android:name="android.permission.INTERNET" />. Potom si z časti o Turnstile v dokumentácii Android SDK skopírujte do projektu pomocné funkcie WebView.showTurnstile(…) a WebView.resetTurnstile() – je to jeden súbor, ktorý patrí vám.

4. Vytvorte obrazovku

import android.webkit.WebView
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import dev.frontmail.Frontmail
import dev.frontmail.FrontmailException
import dev.frontmail.SendOptions
import dev.frontmail.Turnstile
import dev.frontmail.TurnstileWidget
import kotlinx.coroutines.launch

val frontmail = Frontmail("pk_…") // create once, reuse (thread-safe)

@Composable
fun ContactScreen() {
    var name by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }
    var message by remember { mutableStateOf("") }
    var widget by remember { mutableStateOf<TurnstileWidget?>(null) }
    var token by remember { mutableStateOf<String?>(null) }
    var webView by remember { mutableStateOf<WebView?>(null) }
    var info by remember { mutableStateOf("") }
    val scope = rememberCoroutineScope()

    LaunchedEffect(Unit) { widget = Turnstile.sharedWidget() }

    Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
        OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
        OutlinedTextField(email, { email = it }, label = { Text("Email") }, modifier = Modifier.fillMaxWidth())
        OutlinedTextField(message, { message = it }, label = { Text("Message") }, minLines = 4, modifier = Modifier.fillMaxWidth())
        widget?.let { w ->
            AndroidView(
                factory = { context ->
                    WebView(context).apply {
                        showTurnstile(w, onToken = { token = it }, onExpire = { token = null })
                        webView = this
                    }
                },
                modifier = Modifier.fillMaxWidth().height(70.dp),
            )
        }
        Button(
            enabled = token != null,
            onClick = {
                scope.launch {
                    try {
                        frontmail.send(
                            templateId = "tpl_…",
                            params = mapOf("name" to name, "email" to email, "message" to message),
                            options = SendOptions(turnstileToken = token, turnstileKey = widget?.turnstileKey),
                        )
                        info = "Thank you! We’ll get back to you soon."
                    } catch (e: FrontmailException) {
                        info = e.message ?: "Sending failed"
                    } finally {
                        token = null // a token works only once
                        webView?.resetTurnstile()
                    }
                }
            },
        ) { Text("Send") }
        Text(info)
    }
}

Sieťové chyby, timeouty a odpovede 5xx sa automaticky zopakujú s rovnakým idempotency kľúčom, takže ani slabý signál vám nepošle tú istú správu dvakrát.

5. Vyskúšajte to

Spustite aplikáciu na zariadení alebo v emulátore, vyplňte formulár a ťuknite na Send. E-mail vám príde do schránky a v dashboarde sa zobrazí v časti História.

  • origin_not_allowed – Povoliť mobilné aplikácie je vypnuté alebo sa neuložilo.
  • captcha_required – zatiaľ chýba token Turnstile; tlačidlo je neaktívne, kým sa overenie nedokončí.
  • invalid_template_params – povinné pole je prázdne alebo e-mail nie je platný.

Došli vám kredity? Správa sa prijme so stavom HELD a po dobití sa automaticky odošle – ak to chcete oznámiť používateľovi, skontrolujte result.status.

Ďalšie kroky

  • Pridajte z galérie šablónu Automatická odpoveď: ďakujeme za správu a potvrďte tak používateľovi prijatie správy.
  • Odovzdajte SendOptions(locale = Locale.getDefault().toLanguageTag()) a použije sa jazyková verzia šablóny podľa jazyka telefónu.
Všetky návody