How to Make a Delivery App
How to Vibe Code a Native On-Demand Delivery App
Stay in your creative flow. Direct AI models with high-level intent, build a menu/catalog browser, cart checkout, and live courier route tracker in Jetpack Compose, and ship a working APK on your phone in under 30 minutes.
Core Stack
Kotlin Compose
Order Flow
Live Status & Cart
AI Partner
Claude / Gemini
Target Time
25β30 Mins
The Vibe Coding Mobile Philosophy
Whether you are prototyping a local food delivery app, a courier package dispatcher, or a grocery retailer app, traditional development stalls on multi-screen cart state, Google Maps API keys, and checkout forms. Vibe coding lets you prototype the entire customer journey first: catalog filtering, instant cart badge updates, and a live animated courier progress map in a single Compose file.
Old-School Mobile Stress
- Getting blocked for hours configuring Google Maps billing keys before building the menu UI.
- Passing cart item counts across 5 Fragment arguments and losing state on rotation.
- Writing complex RecyclerView view-types for category chips, store cards, and checkout totals.
- Hardcoding static order screens that don't feel like a real live delivery tracker.
The Vibe Coder Way
- Reactive Compose cart state that automatically computes subtotal, delivery fee, and ETA.
- Describe the vibe: "UberEats/DoorDash style dark storefront with sticky checkout bar and live courier route canvas."
- Simulate the 4-stage delivery pipeline (Confirmed → Preparing → Courier En Route → Arrived) with coroutines.
- Test the full ordering and tracking flow on your physical phone in minutes.
The 4-Step Blueprint
From an empty Compose activity to an interactive storefront and live delivery tracker on your phone.
Spawning the Empty Compose Canvas
Open Android Studio and select New Project > Empty Activity (with Compose logo). Name it VibeDelivery.
Canvas first so you can test the courier animation without waiting on map SDK keys.
The "One-Shot Architect" Prompt
Prompt the AI for a unified storefront + tracker flow: category filter chips, menu item cards with +/- quantity steppers, a sticky cart bar, and a live order tracking view.
MainActivity.kt."
Live Courier Map & Order Stepper
Add an animated courier pin moving along a route curve, ETA countdown minutes, promo code input, and driver contact buttons.
Run on Your Real Phone
Connect your Android phone over Wireless Debugging, hit Run (Shift+F10), and walk through placing and tracking an order.
Vibe Prompt Configurator
Select your delivery vertical, visual style, and tracking features to generate your custom Compose prompt.
Essential Elements of a Delivery App
Whether you are building a local restaurant delivery app, a package courier service, or a retail storefront, every successful delivery app needs these core systems:
-
Catalog & Delivery Request Flow
Let customers browse local restaurants, groceries, or package tiers, customize quantities, and submit a delivery request to their doorstep in seconds.
-
End-to-End Order Management
Receive, route, and update orders across every stageβfrom store confirmation and preparation to courier pickup and doorstep handoff.
-
Interactive Route Map & ETA
Visualize the path from the store or warehouse to the customer's address alongside a dynamically updated estimated arrival time.
-
Real-Time Status Tracking
Give customers peace of mind with live stage progress bars and push notifications as their order is prepared, picked up, and arriving nearby.
-
Frictionless & Secure Payments
Support digital wallets, cards, promo coupons, driver tips, and Cash on Delivery with transparent breakdown of subtotal and delivery fees.
-
Courier Contact & Customer Support
Provide quick drop-off instructions, one-tap courier contact, and issue resolution if an item is delayed or missing.
The "Golden" Single-File Compose Template
Replace the contents of your MainActivity.kt with this to run a full storefront, cart, and live courier tracker right now.
package com.example.vibedelivery
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import java.util.Locale
data class MenuItem(
val id: String,
val name: String,
val category: String,
val price: Double,
val prepMins: Int
)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
colorScheme = darkColorScheme(
background = Color(0xFF0B101B),
surface = Color(0xFF151E2E),
primary = Color(0xFF10B981)
)
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
VibeDeliveryApp()
}
}
}
}
}
@Composable
fun VibeDeliveryApp() {
val catalog = remember {
listOf(
MenuItem("1", "Artisan Truffle Burger", "Burgers", 14.50, 15),
MenuItem("2", "Crispy Spicy Chicken Bowl", "Bowls", 12.90, 12),
MenuItem("3", "Matcha Oat Iced Latte", "Drinks", 5.80, 5),
MenuItem("4", "Loaded Parmesan Fries", "Sides", 6.50, 8)
)
}
var cart by remember { mutableStateOf(mapOf("1" to 1, "3" to 1)) }
var isTracking by remember { mutableStateOf(false) }
var orderStage by remember { mutableIntStateOf(0) }
val stages = listOf("Order Confirmed", "Kitchen Preparing", "Courier En Route", "Arriving at Door")
LaunchedEffect(isTracking) {
if (isTracking) {
orderStage = 0
while (orderStage < stages.lastIndex) {
delay(3500)
orderStage++
}
}
}
val totalItems = cart.values.sum()
val subtotal = catalog.sumOf { (cart[it.id] ?: 0) * it.price }
val deliveryFee = if (subtotal > 0) 2.99 else 0.0
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.navigationBarsPadding()
.padding(20.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text("Vibe Express π΅", fontSize = 26.sp, fontWeight = FontWeight.ExtraBold, color = Color.White)
Text("Delivering to 742 Evergreen Terrace β’ 18β25 mins", fontSize = 12.sp, color = Color(0xFF94A3B8))
}
if (isTracking) {
TextButton(onClick = { isTracking = false }) {
Text("Back to Menu", color = Color(0xFF10B981))
}
}
}
Spacer(modifier = Modifier.height(14.dp))
if (!isTracking) {
// Catalog List
LazyColumn(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(catalog, key = { it.id }) { item ->
val qty = cart[item.id] ?: 0
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(18.dp))
.background(Color(0xFF151E2E))
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(item.category.uppercase(), fontSize = 10.sp, color = Color(0xFF10B981), fontWeight = FontWeight.Bold)
Text(item.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White)
Text(
text = "$" + String.format(Locale.US, "%.2f", item.price) + " β’ ~${item.prepMins}m prep",
fontSize = 13.sp,
color = Color(0xFF94A3B8)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedButton(
onClick = {
if (qty > 0) cart = cart + (item.id to (qty - 1))
},
modifier = Modifier.size(36.dp),
contentPadding = PaddingValues(0.dp)
) { Text("-") }
Text(qty.toString(), color = Color.White, fontWeight = FontWeight.Bold)
Button(
onClick = { cart = cart + (item.id to (qty + 1)) },
modifier = Modifier.size(36.dp),
contentPadding = PaddingValues(0.dp)
) { Text("+", color = Color.Black) }
}
}
}
}
// Sticky Checkout Bar
if (totalItems > 0) {
Button(
onClick = { isTracking = true },
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp)
.height(58.dp),
shape = RoundedCornerShape(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("$totalItems items β’ Place Order", fontWeight = FontWeight.ExtraBold, color = Color.Black, fontSize = 16.sp)
Text(
text = "$" + String.format(Locale.US, "%.2f", subtotal + deliveryFee),
fontWeight = FontWeight.Black,
color = Color.Black,
fontSize = 16.sp
)
}
}
}
} else {
// Live Delivery Route Tracker View
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Simulated Route Map Canvas
Box(
modifier = Modifier
.fillMaxWidth()
.height(210.dp)
.clip(RoundedCornerShape(22.dp))
.background(Color(0xFF151E2E))
.padding(20.dp)
) {
val progress = (orderStage + 1) / stages.size.toFloat()
Canvas(modifier = Modifier.fillMaxSize()) {
val start = Offset(40f, size.height - 40f)
val end = Offset(size.width - 40f, 40f)
drawLine(
color = Color(0xFF334155),
start = start,
end = end,
strokeWidth = 8f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(18f, 12f))
)
val courierPos = Offset(
start.x + (end.x - start.x) * progress,
start.y + (end.y - start.y) * progress
)
drawLine(color = Color(0xFF10B981), start = start, end = courierPos, strokeWidth = 10f)
drawCircle(color = Color(0xFF38BDF8), radius = 14f, center = start)
drawCircle(color = Color(0xFFF59E0B), radius = 14f, center = end)
drawCircle(color = Color(0xFF10B981), radius = 20f, center = courierPos)
}
Text(
text = "ETA: ${(4 - orderStage) * 5} mins",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.TopStart)
.clip(CircleShape)
.background(Color(0xFF0B101B).copy(alpha = 0.8f))
.padding(horizontal = 12.dp, vertical = 6.dp)
)
}
// Stepper Stages
stages.forEachIndexed { idx, label ->
val done = idx <= orderStage
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(14.dp))
.background(if (idx == orderStage) Color(0xFF1E293B) else Color(0xFF151E2E))
.padding(14.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "${idx + 1}. $label",
color = if (done) Color.White else Color(0xFF64748B),
fontWeight = if (idx == orderStage) FontWeight.ExtraBold else FontWeight.Medium
)
Text(
text = if (done) "Active" else "Pending",
color = if (done) Color(0xFF10B981) else Color(0xFF64748B),
fontSize = 12.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}
How to Debug Without Losing Your Vibe
Keep cart state and route tracking rock-solid with these three vibe debugging rules:
The Error Dump Rule
Copy any compiler error from the Android Studio Build output and prompt: "Here is my Gradle build error. Fix the missing imports without altering my cart or tracker logic."
Derived Cart Totals
Never store `subtotal` as a separate mutable variable that can get out of sync. Prompt: "Compute subtotal and item counts directly from the immutable cart map so totals never desync."
Instant Wireless Run
Pair your physical phone via Wi-Fi and test one-handed thumb reach on the sticky bottom checkout bar.
The Vibe Prompt Vault
Copy-paste prompts to add modifier sheets, promo codes, and courier chat.
π Prompt: Add Item Customization & Add-On Modifiers Bottom Sheet
"When tapping a menu item in my Jetpack Compose Delivery app, open a ModalBottomSheet allowing the user to pick size options (Regular, Large), checkboxes for extra add-ons (+$1.50), and special kitchen notes before adding to the cart."
ποΈ Prompt: Add Promo Code Validation & Driver Tip Selector
"Add a checkout summary sheet to my Compose Delivery app with a promo code input (e.g., 'VIBE20' for 20% off), driver tip percentage chips (10%, 15%, 20%), and an itemized receipt breakdown (Subtotal, Discount, Delivery Fee, Tip, Total)."
π¬ Prompt: Add an In-App Courier Chat & Drop-Off Instruction Card
"On the Live Delivery Tracker screen of my Compose app, add a courier profile card with quick-reply chips ('Leave at door', 'Ring doorbell', 'Gate code 1234') and a simulated real-time courier chat drawer."