Aller au contenu

Comment créer un formulaire de contact Android avec Kotlin et Jetpack Compose

Un écran de contact Jetpack Compose qui vous envoie chaque message par e-mail via votre propre boîte mail – sans backend, sans mot de passe SMTP dans l’APK. Avec le SDK Kotlin de Frontmail.

Mis à jour:

La vidéo est en anglais ; le texte ci-dessous est le tutoriel complet.

Vous allez ajouter un écran de contact à une application Android écrite en Kotlin avec Jetpack Compose. L’application ne contient qu’une clé publique – l’e-mail est envoyé par Frontmail via votre boîte mail, donc aucun mot de passe SMTP ne se retrouve dans l’APK, où n’importe qui pourrait l’extraire.

Il vous faut : un compte Frontmail gratuit, un projet Android avec Compose et environ quinze minutes.

1. Connectez votre boîte mail et créez le modèle

  1. Services d’envoi → Ajouter un service : connectez Gmail, Outlook, SMTP ou un fournisseur transactionnel, cochez Définir par défaut, puis Vérifier et enregistrer.
  2. Modèles → Depuis la galerie → Formulaire de contact → Utiliser ce modèle. Paramètres obligatoires : name, email, message ; facultatifs : phone, topic.
  3. Copiez l’ID du modèle (tpl_…) depuis l’onglet Extraits de code.

2. Autorisez les applications mobiles

  • Sécurité → Applications mobiles → Autoriser les applications mobiles → Enregistrer. Les applications n’envoient pas d’en-tête Origin : tant que ce n’est pas fait, l’API les rejette avec origin_not_allowed.
  • Modèle → Paramètres → Protection → Exiger un CAPTCHA (Turnstile). Les applications utilisent le widget Turnstile partagé de Frontmail – aucune clé Cloudflare n’est nécessaire.

3. Ajoutez le SDK

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

Ajoutez <uses-permission android:name="android.permission.INTERNET" /> à AndroidManifest.xml. Copiez ensuite les helpers WebView.showTurnstile(…) et WebView.resetTurnstile() depuis la section Turnstile de la documentation du SDK Android dans votre projet – un seul fichier, qui vous appartient.

4. Créez l’écran

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)
    }
}

Les erreurs réseau, les délais dépassés et les réponses 5xx sont relancés automatiquement avec la même clé d’idempotence : un signal faible ne vous enverra jamais deux fois le même message.

5. Testez

Lancez l’application sur un appareil ou un émulateur, remplissez le formulaire et touchez Send. L’e-mail arrive dans votre boîte de réception et apparaît dans l’Historique du tableau de bord.

  • origin_not_allowed – Autoriser les applications mobiles est désactivé ou n’a pas été enregistré.
  • captcha_required – pas encore de jeton Turnstile ; le bouton reste désactivé jusqu’à la fin de la vérification.
  • invalid_template_params – un champ obligatoire est vide ou l’e-mail n’est pas valide.

Plus de crédits ? Le message est accepté avec le statut HELD et envoyé automatiquement dès que vous rechargez – consultez result.status si vous souhaitez en informer l’utilisateur.

Étapes suivantes

  • Ajoutez le modèle Réponse automatique : merci pour votre message depuis la galerie pour confirmer le message à l’utilisateur.
  • Passez SendOptions(locale = Locale.getDefault().toLanguageTag()) pour utiliser la variante linguistique du modèle correspondant à la langue du téléphone.
Tous les tutoriels