In 2014, I submitted my first app to the App Store. I still remember the exact moment the review approval email arrived. Objective-C, Xcode, late nights — and finally something the world could download. Twelve years and over 50 million total downloads later, I decided to test whether Rork Max could meaningfully change how I build.
I was skeptical. The idea that an AI tool could handle everything from UI generation to AdMob integration to App Store submission felt more like marketing copy than engineering reality. So I ran an experiment: I rebuilt one of my existing wallpaper apps (2 million+ downloads) from scratch using Rork Max, and I tracked every hour.
Here's what I found — the good, the disappointing, and what actually surprised me.
Why I Chose to Rebuild Rather Than Add Features
The trigger wasn't curiosity. It was maintenance pain.
My React Native wallpaper app had accumulated what I'd call "dependency debt" — each Expo SDK upgrade created native module conflicts that took half a day to untangle. I was spending more time confirming the app still worked than building anything new. After 12 years of indie development, I recognize this pattern: when maintenance cost exceeds new-build cost, rebuilding is the rational call.
The conditions I set for the experiment:
- Target app: Wallpaper browser with categories, pinch-zoom detail view, favorites, and subscription monetization
- Baseline: My actual logged hours from building the same feature set from scratch in React Native + TypeScript previously
- Measurements: Per-task dev time, code quality, App Store review outcome, AdMob display, and 30-day revenue metrics post-launch
Baseline: How Long the Traditional Build Actually Took
These are real numbers from a previous project of equivalent scope. This is experienced-developer time — someone who already knows the stack.
- Project setup, Expo initialization, library selection: 4 hours
- UI design (Figma wireframes): 8 hours
- Wallpaper grid and category screens: 12 hours
- Image optimization and caching (expo-image): 6 hours
- Favorites feature (AsyncStorage): 3 hours
- RevenueCat subscription integration: 8 hours
- AdMob implementation (banner, interstitial, ATT compliance): 6 hours
- App Store submission prep (Privacy Manifest, screenshots): 4 hours
- Testing and bug fixes: 10 hours
Total: ~61 hours
Even moving quickly, that's a minimum of 8 full working days. For someone less familiar with the stack, multiply by two or three.
The Rork Max Build: A Day-by-Day Log
Day 1: Project Setup to Working UI (6 hours)
My first prompt was intentionally straightforward:
Build a wallpaper app.
Requirements:
- Category browsing (nature, city, abstract, anime)
- Pinch-to-zoom detail view
- Favorites toggle (heart icon)
- iOS and Android support
- Dark mode support
Design: minimal, dark background
The first thing that caught my attention was FlashList. Rork Max generated code using @shopify/flash-list rather than FlatList — a choice that matters significantly for image-heavy scroll performance. In my traditional builds, I've always started with FlatList and migrated to FlashList after hitting performance problems. Getting this right from the start is not a small thing.
Generated wallpaper grid component:
// WallpaperGrid.tsx — generated by Rork Max
import { FlashList } from '@shopify/flash-list';
import { Pressable, Dimensions, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
import { useRouter } from 'expo-router';
const { width } = Dimensions.get('window');
const ITEM_WIDTH = (width - 3) / 2;
interface WallpaperItem {
id: string;
thumbnailUrl: string;
category: string;
}
export default function WallpaperGrid({ category }: { category: string }) {
const router = useRouter();
return (
<FlashList
data={[]} // populated from Zustand store
numColumns={2}
estimatedItemSize={ITEM_WIDTH * 1.5}
renderItem={({ item }: { item: WallpaperItem }) => (
<Pressable
style={styles.item}
onPress={() => router.push(`/wallpaper/${item.id}`)}
>
<Image
source={{ uri: item.thumbnailUrl }}
style={styles.thumbnail}
contentFit="cover"
transition={200}
cachePolicy="memory-disk" // memory + disk cache, no extra config needed
/>
</Pressable>
)}
keyExtractor={(item: WallpaperItem) => item.id}
/>
);
}
const styles = StyleSheet.create({
item: { width: ITEM_WIDTH, height: ITEM_WIDTH * 1.5, margin: 0.5 },
thumbnail: { width: '100%', height: '100%' },
});The cachePolicy="memory-disk" default and the transition={200} fade are exactly the choices I'd make. This isn't beginner code.
Day 2: RevenueCat Subscription Integration (4 hours)
This was the phase I was most worried about. RevenueCat integration requires project-specific configuration — App IDs, Entitlement names, Offering structures — that an AI can't know in advance. I expected generic boilerplate.
What I got was more useful than that:
// hooks/useSubscription.ts
import Purchases, { CustomerInfo, PurchasesPackage } from 'react-native-purchases';
import { useEffect, useState } from 'react';
export function useSubscription() {
const [customerInfo, setCustomerInfo] = useState<CustomerInfo | null>(null);
const [packages, setPackages] = useState<PurchasesPackage[]>([]);
const [loading, setLoading] = useState(true);
// Check active entitlement named 'premium'
const isPremium = customerInfo?.entitlements.active['premium'] !== undefined;
useEffect(() => {
async function initialize() {
try {
const info = await Purchases.getCustomerInfo();
setCustomerInfo(info);
const offerings = await Purchases.getOfferings();
if (offerings.current?.availablePackages) {
setPackages(offerings.current.availablePackages);
}
} catch (error) {
console.error('RevenueCat init error:', error);
} finally {
setLoading(false);
}
}
initialize();
}, []);
async function purchasePackage(pkg: PurchasesPackage): Promise<boolean> {
try {
const { customerInfo: updated } = await Purchases.purchasePackage(pkg);
setCustomerInfo(updated);
return true;
} catch (error: any) {
if (!error.userCancelled) {
console.error('Purchase failed:', error);
}
return false;
}
}
async function restorePurchases() {
const updated = await Purchases.restorePurchases();
setCustomerInfo(updated);
}
return { isPremium, packages, loading, purchasePackage, restorePurchases };
}The purchase hook, restore flow, and error handling for user-cancelled purchases were all correct on first generation. The dashboard-side configuration in RevenueCat and App Store Connect still requires manual work — that part hasn't changed. For a detailed RevenueCat setup walkthrough, see RevenueCat Subscription Monetization Guide for Rork.
Days 3–4: AdMob and App Store Submission Prep (5 hours)
I asked Rork Max to include ATT (App Tracking Transparency) handling as part of the AdMob setup. It automatically proposed expo-tracking-transparency and generated UMP consent form integration alongside AdMob initialization — handling the full compliance flow that's now effectively required for App Store approval.
// app/_layout.tsx — ATT before AdMob initialization
import { useEffect } from 'react';
import { Platform } from 'react-native';
import mobileAds from 'react-native-google-mobile-ads';
async function requestTrackingPermission() {
if (Platform.OS !== 'ios') return;
try {
const { requestTrackingPermissionsAsync } =
await import('expo-tracking-transparency');
await requestTrackingPermissionsAsync();
// AdMob initializes regardless of result; targeting adjusts based on consent
} catch {
// Fail gracefully — ads still work without tracking permission
}
}
export default function RootLayout() {
useEffect(() => {
async function initAds() {
await requestTrackingPermission();
await mobileAds().initialize();
}
initAds();
}, []);
// ...
}Getting the ATT prompt timing right is one of the variables that most impacts AdMob opt-in rates — and therefore revenue. The generated code uses the correct initialization order. For how to optimize the prompt itself, Maximizing AdMob ATT Opt-In Rates in Rork Apps goes deeper on that.
The Time Comparison
Rork Max measured against the 61-hour baseline:
- Project setup and core UI: 24 hours → 6 hours (75% reduction)
- Image optimization and caching: 6 hours → 2 hours (67% reduction)
- Favorites feature (Zustand): 3 hours → 0.5 hours (83% reduction)
- RevenueCat integration: 8 hours → 4 hours (50% reduction)
- AdMob implementation: 6 hours → 3 hours (50% reduction)
- App Store submission prep: 4 hours → 3 hours (25% reduction)
- Testing and bug fixes: 10 hours → 4 hours (60% reduction)
Total: 61 hours → 22.5 hours (63% reduction)
The headline "1/10 the time" was an exaggeration. The real number is about one-third. But the feeling of working at 1/10 the cognitive load isn't wrong — the mental overhead of writing boilerplate dropped significantly, which means each decision gets more attention. That qualitative shift is real even if the raw hour count doesn't hit the marketing-friendly ratio.
The biggest gains came from UI implementation. Because I already know how I'd write this code, reviewing generated code is fast. A decade of experience makes Rork Max more useful, not less — you're not guessing whether the generated code is good.
Where Rork Max Fell Short
Three areas that didn't work well.
1. Project-specific business logic with edge cases
The app had a download limit rule: free users get 3 wallpaper downloads per day, resetting at midnight Japan time. The first generated implementation used UTC as the day boundary — a subtle bug that produces wrong behavior for Japanese users.
// ❌ First generated version (UTC-based, wrong for JST users)
const todayKey = new Date().toISOString().split('T')[0];
// ✅ Fixed version (JST-aware)
const getJSTDateKey = () => {
const now = new Date();
const jst = new Date(now.getTime() + 9 * 60 * 60 * 1000); // UTC+9
return jst.toISOString().split('T')[0];
};
const todayKey = getJSTDateKey();The fix is one line, but catching this bug required knowing that UTC-based date boundaries exist in the first place. Locale-specific time logic needs to be explicitly specified in the prompt — Rork Max won't infer it.
2. Adding to an existing codebase
This rebuild was greenfield. For an existing React Native project with established type definitions and state management patterns, I'd expect Rork Max to struggle with consistency. It can't read the context of code it didn't generate.
3. Deep native API integration
CoreML, HealthKit, and other non-Expo native frameworks required manual implementation. Rork Max can scaffold the React Native side, but the native bridge still needs hands-on work when you're outside the Expo managed workflow.
30-Day Revenue Metrics After Relaunch
The rebuilt app ran on TestFlight for two weeks before the App Store release. Comparing 30 days post-launch against the same period on the previous version:
- Crash rate: 0.8% → 0.2% (74% improvement)
- Launch time (P75): 2.1s → 1.3s (38% faster)
- Subscription conversion rate: 2.3% → 2.8% (+0.5 points)
- AdMob eCPM: $0.82 → $0.85 (essentially flat)
Whether the faster launch time directly caused the conversion improvement is hard to isolate. But the correlation makes sense — a more responsive app keeps users engaged through the trial period. The crash rate improvement is attributable to FlashList and the revised image cache configuration.
AdMob eCPM was flat, which is expected. Ad revenue depends far more on ATT opt-in rates and impression volume than on app architecture. From the period when AdMob was generating over ¥1.5 million monthly, I can say with confidence: the code quality matters less than the ad placement design and the ATT prompt UX. Those learnings are in AdMob Monthly Revenue Strategy for Indie Developers.
What 12 Years of App Development Tells Me About AI Tools
Every major tool shift in my career followed the same pattern: an early period of "this changes everything," followed by a clearer view of what changed and what didn't.
Rork Max is faster at the mechanical parts of development. The strategic parts — what to build, who to build it for, how to price it, how to keep users around — those remain entirely human decisions. When my grandfather, a temple carpenter, talked about "putting your hands to work as an act of devotion," he wasn't describing efficiency. He was describing intentionality in craft. The tool changes; the intentionality doesn't.
What I do feel, honestly, is the same kind of openness I had in 1997 when I first accessed the internet at 16 and realized I could connect with designers and engineers across the world using skills I'd taught myself. The tool was new; the possibility space expanded. That's what Rork Max feels like right now.
Decision Framework for Indie Developers
Based on this experiment, here's how I'd approach the decision:
Good fit:
- Apps with simple data structures (wallpaper, wellness, habit trackers)
- MVPs you need in the market within days to test demand
- Porting an existing app's feature set to a different platform
Poor fit:
- Large feature additions to existing React Native codebases
- Apps requiring complex offline sync or conflict resolution
- Game-engine-style rendering (Unity/Metal)
On cost: Rork Max's monthly fee pays for itself in saved developer hours within the first project for anyone billing their time. The question isn't whether it's worth the cost — for most indie developers, it clearly is. The question is what you do with the saved time.
My next Rork Max build is a new version of an attraction-type app — one of the categories in my 50-million-download portfolio. I'm curious whether the same time savings hold for a more content-heavy experience. I'll report back when it's live.
If you're deciding whether to try it: start with one feature, not a full rebuild. Find a screen you need to add to an existing app, build it in Rork Max, evaluate the output against what you'd write yourself. That's a lower-stakes test than committing a full project.