How to Send OTP in Jetpack Compose (Android) — 2026
Jetpack Compose OTP tutorial using StartMessaging. Retrofit/Ktor client, Compose state, Android SMS Retriever auto-fill, and a clean two-screen flow.
StartMessaging Team
Engineering
Jetpack Compose is the modern Android UI stack. This tutorial wires StartMessaging with Ktor client + Compose state.
Overview
- Network layer hits your backend.
- ViewModel holds phone / code / requestId.
- Compose screens for phone and OTP entry.
- SMS Retriever for auto-fill.
Network Layer (Ktor Client)
// data/AuthApi.kt
class AuthApi(private val client: HttpClient) {
suspend fun sendOtp(phone: String): SendResp =
client.post("https://your-backend.example.com/auth/send-otp") {
contentType(ContentType.Application.Json)
setBody(mapOf("phoneNumber" to phone))
}.body()
suspend fun verifyOtp(requestId: String, code: String): VerifyResp =
client.post("https://your-backend.example.com/auth/verify-otp") {
contentType(ContentType.Application.Json)
setBody(mapOf("requestId" to requestId, "otpCode" to code))
}.body()
}
@Serializable data class SendResp(val requestId: String, val expiresAt: String)
@Serializable data class VerifyResp(val verified: Boolean)AuthViewModel
class AuthViewModel(private val api: AuthApi) : ViewModel() {
var phone by mutableStateOf("")
var code by mutableStateOf("")
var requestId by mutableStateOf<String?>(null)
var error by mutableStateOf<String?>(null)
fun sendOtp() = viewModelScope.launch {
runCatching { api.sendOtp(phone) }
.onSuccess { requestId = it.requestId }
.onFailure { error = it.message }
}
fun verifyOtp(onSuccess: () -> Unit) = viewModelScope.launch {
runCatching { api.verifyOtp(requestId!!, code) }
.onSuccess { onSuccess() }
.onFailure { error = it.message }
}
}Compose Screens
@Composable
fun LoginScreen(vm: AuthViewModel = viewModel(), onAuthed: () -> Unit) {
if (vm.requestId == null) {
Column {
TextField(value = vm.phone, onValueChange = { vm.phone = it }, label = { Text("Phone") })
Button(onClick = { vm.sendOtp() }) { Text("Send OTP") }
}
} else {
Column {
TextField(value = vm.code, onValueChange = { vm.code = it }, label = { Text("OTP") })
Button(onClick = { vm.verifyOtp(onAuthed) }) { Text("Verify") }
}
}
}SMS Retriever Auto-Fill
Use Google’s SmsRetriever client to listen for an OTP SMS that ends with the app-hash. See our auto-fill guide.
FAQ
Plain Kotlin (non-Compose) version at our Kotlin Android guide.
Related Articles
Send and verify SMS OTPs from a Kotlin Ktor backend or Android app using the StartMessaging API. Includes Ktor client examples, retrofit option, and Android best practices.
Flutter OTP tutorial using StartMessaging on a Firebase Functions backend. Riverpod state, http client, secure cookie session via Firebase Auth custom token.
React Native OTP tutorial using Expo and StartMessaging. Includes secure storage, auto-fill (Android SMS Retriever / iOS keychain), and a Node backend pattern.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.