Skip to main content

How to Make a Delivery App

Copied to clipboard!
No boilerplate memorization required

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 Philosophy

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.
Execution

The 4-Step Blueprint

From an empty Compose activity to an interactive storefront and live delivery tracker on your phone.

Step 01

Spawning the Empty Compose Canvas

Open Android Studio and select New Project > Empty Activity (with Compose logo). Name it VibeDelivery.

πŸ’‘ Pro Vibe Tip: Prototype your route map with a custom Compose Canvas first so you can test the courier animation without waiting on map SDK keys.
Step 02

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.

πŸ”‘ Key Constraint: Tell the AI: "Include both the Storefront/Cart screen and the Live Delivery Tracker screen inside one MainActivity.kt."
Step 03

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.

✨ Iteration Secret: Use a coroutine timer to advance the order status every few seconds so you can demo the full delivery lifecycle live.
Step 04

Run on Your Real Phone

Connect your Android phone over Wireless Debugging, hit Run (Shift+F10), and walk through placing and tracking an order.

πŸ† The Grand Payoff: You have a tangible, investor- or client-ready delivery app prototype running natively on physical hardware.
Interactive Tool

Vibe Prompt Configurator

Select your delivery vertical, visual style, and tracking features to generate your custom Compose prompt.

Special Feature Vibe
Optional custom details added straight into your master prompt.
Generated Master Prompt
Ready to paste into Claude, Gemini, or ChatGPT Single-file Compose ready
Product Spec

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.

UX Checklist Before Shipping: Keep the cart summary bar pinned at the bottom of the catalog so users always see their item count and total price without leaving the menu.
Starter Template

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.

app/src/main/java/.../MainActivity.kt
Kotlin + Compose
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
                        )
                    }
                }
            }
        }
    }
}
Troubleshooting

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.

Extensions

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
Prompt Template

"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
Prompt Template

"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
Prompt Template

"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."

We use cookies for ads and analytics.