<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hazuki | Indie Dev</title>
    <description>The latest articles on DEV Community by Hazuki | Indie Dev (@hazuki_tech).</description>
    <link>https://dev.to/hazuki_tech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3899529%2F3a64ef53-1b4e-4879-ba70-8fc6bd874b0b.png</url>
      <title>DEV Community: Hazuki | Indie Dev</title>
      <link>https://dev.to/hazuki_tech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hazuki_tech"/>
    <language>en</language>
    <item>
      <title>Shipping an iOS App Without a Mac — Flutter Firebase Gemini AI</title>
      <dc:creator>Hazuki | Indie Dev</dc:creator>
      <pubDate>Mon, 27 Apr 2026 02:19:21 +0000</pubDate>
      <link>https://dev.to/hazuki_tech/shipping-an-ios-app-without-a-mac-flutter-x-firebase-x-gemini-ai-19a8</link>
      <guid>https://dev.to/hazuki_tech/shipping-an-ios-app-without-a-mac-flutter-x-firebase-x-gemini-ai-19a8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm Hazuki — an indie developer based in Japan.&lt;/p&gt;

&lt;p&gt;I built and shipped &lt;strong&gt;Smart Inventory&lt;/strong&gt;, a family grocery management app, using Flutter, Firebase, and the Gemini AI API — entirely from a Windows machine, without ever touching a Mac.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk through the challenges I ran into and how I solved them. If you're in a similar situation, I hope this helps.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Smart Inventory — Family Grocery &amp;amp; Home Tracker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A real-time inventory and shopping list sharing app designed for busy, dual-income families.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Management&lt;/strong&gt;: Organize food and household items by category (fridge, freezer, pantry, spices, daily essentials)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry Alerts&lt;/strong&gt;: Color-coded warnings for items expiring soon (🔴 today / 🟠 within 3 days)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Shopping List&lt;/strong&gt;: Real-time sync across the family — shows who added what&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Recipe Suggestions&lt;/strong&gt;: Gemini AI suggests personalized recipes based on what's currently in stock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Household Profile&lt;/strong&gt;: Recipes are personalized based on allergies, cooking time, skill level, and cuisine preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium Subscription&lt;/strong&gt;: Applied at the household level — one purchase covers the whole family
### Tech Stack&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Flutter / Riverpod / Freezed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Firebase (Auth / Firestore / Cloud Functions / FCM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Google Gemini API (proxied via Cloud Functions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;RevenueCat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;Codemagic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev Environment&lt;/td&gt;
&lt;td&gt;Windows / VSCode (no Mac)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;It started with small but persistent frustrations of dual-income household life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "bought it again" problem&lt;/strong&gt;: Three bottles of soy sauce. Two packs of eggs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "what's in the fridge?" problem&lt;/strong&gt;: Having to text your partner before every grocery run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "what's for dinner?" problem&lt;/strong&gt;: Coming home exhausted and having no idea what to cook
I tried existing inventory apps, but none combined real-time family sharing with AI-powered recipe suggestions in one place. So I decided to build it myself.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Building an iOS App Without a Mac
&lt;/h3&gt;

&lt;p&gt;Buying a Mac just for indie development is a big investment. After some research, I found &lt;strong&gt;Codemagic&lt;/strong&gt; — a cloud CI/CD service that builds on real Mac hardware in the cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# codemagic.yaml overview&lt;/span&gt;
&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ios-release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iOS Release&lt;/span&gt;
    &lt;span class="na"&gt;instance_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mac_mini_m2&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ios_signing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;distribution_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_store&lt;/span&gt;
        &lt;span class="na"&gt;bundle_identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;com.hazuki-tech.smartInventory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codemagic handles the build on a cloud Mac, so &lt;strong&gt;everything stays on Windows&lt;/strong&gt;. Certificate management is handled automatically using an App Store Connect API key — no Mac required at any step.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Provisioning Profile Missing In-App Purchase Entitlement
&lt;/h3&gt;

&lt;p&gt;After adding IAP support, my builds started failing with this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Provisioning profile doesn't include 
the com.apple.developer.in-app-payments entitlement.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: The In-App Purchase capability wasn't enabled on the App ID in Apple Developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apple Developer → Identifiers → Select your App ID&lt;/li&gt;
&lt;li&gt;Enable In-App Purchase under Capabilities&lt;/li&gt;
&lt;li&gt;Regenerate the provisioning profile&lt;/li&gt;
&lt;li&gt;Set Codemagic Code Signing to "Automatic"
### 3. RevenueCat Webhook Bug: CANCELLATION vs EXPIRATION&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I had a critical bug in my subscription webhook logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: I was treating CANCELLATION and EXPIRATION the same way, which caused users to lose Premium access immediately after cancelling — even though they'd paid for the rest of the billing period.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct behavior&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CANCELLATION&lt;/code&gt; = User cancelled, but they're still in the paid period → keep Premium&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPIRATION&lt;/code&gt; = Billing period actually ended → downgrade to Free
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CANCELLATION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// Still within paid period — keep Premium&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateSubscriptionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;householdId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plan_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;premium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;premium_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expirationAtMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EXPIRATION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// Billing period ended — downgrade to Free&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateSubscriptionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;householdId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plan_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;premium_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Gemini API Ignoring Allergy Constraints
&lt;/h3&gt;

&lt;p&gt;Even when I included allergy restrictions in the prompt, Gemini occasionally suggested recipes containing those ingredients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: The allergy constraint was buried among other conditions with no special priority signaling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Move the constraint to the very top of the prompt as a standalone section with strong emphasis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ ABSOLUTE PRIORITY — MUST FOLLOW WITHOUT EXCEPTION:
The following dietary restrictions must NEVER appear in any recipe,
even in small amounts, as hidden ingredients, or in alternative forms.
This rule overrides all other instructions.

[FORBIDDEN INGREDIENTS]
- Eggs and egg-based products (mayonnaise, egg yolk, egg white, etc.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;code&gt;gemini-2.5-flash&lt;/code&gt; as the primary model with &lt;code&gt;gemini-2.5-pro&lt;/code&gt; as a fallback, with a 120-second timeout to handle more complex recipe generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Riverpod Late Initialization Bug
&lt;/h3&gt;

&lt;p&gt;User preferences from the household profile weren't being reflected in Gemini's recipe suggestions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: &lt;code&gt;RecipeRepository&lt;/code&gt; was being instantiated before &lt;code&gt;HouseholdProfileProvider&lt;/code&gt; had finished loading from Firestore, resulting in &lt;code&gt;profile: null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Explicitly await the Firestore data before accessing the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: profile is null when RecipeRepository is created&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipeRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After: wait for Firestore data before accessing the repository&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;householdProfileProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipeRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Preventing Duplicate Recipe Suggestions
&lt;/h3&gt;

&lt;p&gt;Gemini was occasionally suggesting the same recipes repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Fetch the 5 most recent recipe titles from Firestore and inject them into the prompt as a strict prohibition list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recentRecipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getRecentRecipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;householdId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prohibitedList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;recentRecipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
STRICT RULE — DO NOT SUGGEST:
The following recipes (and anything similar) must not be suggested:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prohibitedList&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Recipe Usage Limits
&lt;/h2&gt;

&lt;p&gt;Since AI recipe generation calls the Gemini API via Cloud Functions, cost control matters. Usage is tracked server-side in Firestore to prevent client-side manipulation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Reset&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;10/month&lt;/td&gt;
&lt;td&gt;1st of each month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium&lt;/td&gt;
&lt;td&gt;10/day&lt;/td&gt;
&lt;td&gt;Midnight each day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The counter is only incremented after a successful API response — failed requests don't count against the limit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dev Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;VSCode (Windows)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version Control&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS Builds&lt;/td&gt;
&lt;td&gt;Codemagic (cloud CI/CD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;RevenueCat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Key Management&lt;/td&gt;
&lt;td&gt;Firebase Secret Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legal Pages&lt;/td&gt;
&lt;td&gt;GitHub Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Coding Assistant&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Journey to Launch
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Planning &amp;amp; Design&lt;/strong&gt;: Requirements, Firestore schema design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Features&lt;/strong&gt;: Inventory, shopping list, Firebase integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Features&lt;/strong&gt;: Gemini API proxied through Cloud Functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: RevenueCat integration, Webhook setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt;: Codemagic pipeline, automated builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App Store Prep&lt;/strong&gt;: Screenshots, descriptions, privacy policy&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  7. &lt;strong&gt;Submission&lt;/strong&gt;: Review approved → manual release
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What went well&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Mac, no problem&lt;/strong&gt;: Codemagic removed the biggest barrier to iOS development on Windows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase + Riverpod is a great combo&lt;/strong&gt;: StreamProvider made real-time sync surprisingly easy&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claude Code accelerated development significantly&lt;/strong&gt;: Passing a CLAUDE.md spec file and letting it handle complex implementations saved a huge amount of time&lt;br&gt;
&lt;strong&gt;What was hard&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RevenueCat subscription lifecycle&lt;/strong&gt;: The CANCELLATION vs EXPIRATION distinction caught me off guard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt engineering&lt;/strong&gt;: Getting an LLM to reliably follow strict constraints is harder than it looks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - &lt;strong&gt;IAP testing without a Mac&lt;/strong&gt;: Sandbox testing for in-app purchases is tricky without direct device access
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;You don't need a Mac to ship an iOS app as an indie developer. With tools like Codemagic, the barrier is much lower than it used to be.&lt;/p&gt;

&lt;p&gt;Smart Inventory is live on the App Store — search for &lt;strong&gt;"Smart Inventory"&lt;/strong&gt; or grab it at the link below. Feedback is always welcome!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apps.apple.com/us/app/smart-inventory-family-pantry/id6760807269" rel="noopener noreferrer"&gt;https://apps.apple.com/us/app/smart-inventory-family-pantry/id6760807269&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to reach out on X: &lt;a href="https://x.com/hazuki_tech_dev" rel="noopener noreferrer"&gt;@hazuki_tech_dev&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this was useful, a like or comment goes a long way 🙏&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ios</category>
      <category>indiedev</category>
      <category>firebase</category>
    </item>
  </channel>
</rss>
