Zum Inhalt springen

Anfrageformular in Android mit Kotlin erstellen – Angebotsanfrage per E-Mail

Ein Anfrageformular in Jetpack Compose mit Auswahl von Leistung und Budget sowie Termin, das Ihnen jede Anfrage per E-Mail schickt und dem Kunden automatisch eine Bestätigung sendet.

Aktualisiert:

Das Video ist auf Englisch; die Schritte unten sind das vollständige Tutorial.

In diesem Tutorial bauen Sie einen Bildschirm für Angebotsanfragen in Jetpack Compose. Der Kunde wählt eine Leistung und ein Budget, gibt einen Termin und eine Beschreibung ein; Sie erhalten eine strukturierte E-Mail, der Kunde eine automatische Bestätigung.

Sie brauchen: ein kostenloses Frontmail-Konto, ein Android-Projekt mit Compose und etwa zwanzig Minuten.

1. Postfach verbinden und Vorlagen anlegen

  1. E-Mail-Dienste → Dienst hinzufügen: Verbinden Sie Ihr Postfach oder Ihren Anbieter, Als Standard festlegen, Prüfen & speichern.
  2. Vorlagen → Aus der Galerie → Angebotsanfrage (Kategorie Geschäftlich) → Vorlage verwenden.
  3. Aus der Galerie → Automatische Antwort: Danke für Ihre Nachricht → Vorlage verwenden; Standardwerte für company_name und response_time festlegen und die Vorlage dann in der Anfragevorlage unter Einstellungen → Zustellung → Vorlage für automatische Antwort auswählen.

Die Anfragevorlage braucht name, email, service und details; phone, company, budget und deadline (YYYY-MM-DD) sind optional. service und budget akzeptieren nur die Werte auf dem Tab Parameter – verwenden Sie in der App dieselben Zeichenketten. Das Codebeispiel nutzt die Werte der englischen Vorlage; eine auf Deutsch angelegte Vorlage hat Website, Onlineshop, Mobile App, Beratung, Sonstiges und Unter 5.000 €, 5.000–20.000 €, 20.000–50.000 €, Über 50.000 €.

2. Mobile Apps erlauben und CAPTCHA aktivieren

  • Sicherheit → Mobile Apps → Mobile Apps erlauben → Speichern.
  • Anfragevorlage → Einstellungen → Schutz → CAPTCHA verlangen (Turnstile) (das gemeinsame mobile Widget von Frontmail, keine Schlüssel nötig).

3. SDK hinzufügen

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

Ergänzen Sie die Berechtigung INTERNET und kopieren Sie die Hilfsfunktionen showTurnstile / resetTurnstile aus dem Turnstile-Abschnitt der Android-SDK-Dokumentation.

4. Bildschirm bauen

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
import java.time.LocalDate

val frontmail = Frontmail("pk_…")

val services = listOf("Website", "E-shop", "Mobile app", "Consulting", "Other")
val budgets = listOf("Under $5k", "$5k–20k", "$20k–50k", "Over $50k")

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Choices(options: List<String>, selected: String?, onSelect: (String?) -> Unit) {
    FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        options.forEach { option ->
            FilterChip(
                selected = option == selected,
                onClick = { onSelect(if (option == selected) null else option) },
                label = { Text(option) },
            )
        }
    }
}

@Composable
fun QuoteScreen() {
    var name by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }
    var service by remember { mutableStateOf<String?>(services.first()) }
    var budget by remember { mutableStateOf<String?>(null) }
    var deadline by remember { mutableStateOf("") }
    var details 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())
        Text("What do you need?")
        Choices(services, service) { service = it }
        Text("Budget (optional)")
        Choices(budgets, budget) { budget = it }
        OutlinedTextField(deadline, { deadline = it }, label = { Text("Deadline (YYYY-MM-DD, optional)") }, modifier = Modifier.fillMaxWidth())
        OutlinedTextField(details, { details = it }, label = { Text("Project details") }, 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 && service != null,
            onClick = {
                scope.launch {
                    try {
                        frontmail.send(
                            templateId = "tpl_…",
                            params = mapOf(
                                "name" to name,
                                "email" to email,
                                "service" to service,
                                "budget" to budget,
                                "deadline" to runCatching { LocalDate.parse(deadline).toString() }.getOrNull(),
                                "details" to details,
                            ),
                            options = SendOptions(turnstileToken = token, turnstileKey = widget?.turnstileKey),
                        )
                        info = "Thanks! We’ve emailed you a confirmation."
                    } catch (e: FrontmailException) {
                        info = e.message ?: "Sending failed"
                    } finally {
                        token = null // a token works only once
                        webView?.resetTurnstile()
                    }
                }
            },
        ) { Text("Request a quote") }
        Text(info)
    }
}

null-Werte (kein Budget, kein gültiger Termin) fehlen in der E-Mail einfach. Wenn Sie möchten, ersetzen Sie das Textfeld durch einen Material-3-DatePicker – senden Sie das Datum nur als YYYY-MM-DD.

5. Testen

Senden Sie eine Anfrage mit Ihrer eigenen E-Mail-Adresse: Sie erhalten die Anfrage und kurz darauf die Bestätigung. Beide erscheinen mit ihrem Zustellverlauf unter Verlauf.

Eine Anfrage mit Bestätigung kostet zwei Credits. Sind Ihre Credits aufgebraucht, werden Anfragen als HELD angenommen und nach dem Aufladen automatisch versendet.

Nächste Schritte

  • Aktivieren Sie Kontakte sammeln in den Einstellungen der Vorlage, um eine Liste aller Interessenten zu führen.
  • Hängen Sie Dateien an (zum Beispiel ein Briefing als PDF) mit SendOptions(attachments = …) – siehe SDK-Dokumentation.
Alle Tutorials