<?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: Abhishek Goyal</title>
    <description>The latest articles on DEV Community by Abhishek Goyal (@abhishek_goyal_c97dbf2eea).</description>
    <link>https://dev.to/abhishek_goyal_c97dbf2eea</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4141403%2F8d0b3bdd-e83d-4a13-8fb0-2a06714194df.png</url>
      <title>DEV Community: Abhishek Goyal</title>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhishek_goyal_c97dbf2eea"/>
    <language>en</language>
    <item>
      <title>Freelancer vs agency: why one senior engineer often beats a team</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/freelancer-vs-agency-why-one-senior-engineer-often-beats-a-team-3gi1</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/freelancer-vs-agency-why-one-senior-engineer-often-beats-a-team-3gi1</guid>
      <description>&lt;p&gt;I’m an independent mobile engineer, so you know which side of this I’m on. But I’d rather win your project with an argument than with a pitch, so here it is, including the one case where I’d tell you to hire an agency.&lt;/p&gt;

&lt;p&gt;First, the word “freelancer” asks the wrong question. It describes how someone is employed, not how good they are. The real question is this: &lt;strong&gt;who will make the decisions about your app, and will that person still be there when those decisions matter?&lt;/strong&gt; For most first releases, the best answer is one senior engineer who owns the app from the first call to the store.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agency fee pays for
&lt;/h2&gt;

&lt;p&gt;An agency quote covers more than the code. It pays for sales, account management and project management, and for a team whose members can change during your project. Some of that is useful coordination. A lot of it is the cost of running an agency, not the cost of building your app.&lt;/p&gt;

&lt;p&gt;That structure has three side effects worth knowing about before you sign:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The person who sells the project often isn’t the person who builds it.&lt;/strong&gt; The senior people are on the sales call. Who writes the code is decided after you’ve signed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context gets lost in the handoffs.&lt;/strong&gt; What you explain to a salesperson goes to a project manager, and then to a developer. Every handoff loses some detail, and the detail is usually what matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your app is one of many.&lt;/strong&gt; Developers move between projects, and the one who understood your app best can be moved to another client halfway through.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this makes agencies bad. It’s simply how the model works. But it means a lot of your budget goes into keeping a team coordinated, rather than into your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one senior engineer gives you instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One person, start to finish.&lt;/strong&gt; The engineer on the first call is the one who designs the data model, writes the code and handles the store release. Nothing gets lost, because there are no handoffs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions made by the person who lives with them.&lt;/strong&gt; If I choose an architecture, I’m the one who has to make it work at the next release. That’s a strong reason to choose well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your budget goes into the work.&lt;/strong&gt; You pay for engineering, not for layers of coordination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experience you can’t get from a general team.&lt;/strong&gt; An app that must work offline, share code between Android and iOS, or be rescued from a codebase nobody wants to touch needs someone who has done it before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve owned apps this way across 13 years on Android and iOS. A few of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HelperBook&lt;/strong&gt; , a household payroll app I built from the first commit to a public release on Google Play: product, architecture, UI, Hindi localisation and store submission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A video-first hiring platform&lt;/strong&gt; , where I built 100% of the Android client from concept to production release: 30+ screens across the candidate and recruiter flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulse&lt;/strong&gt; , an inspection platform I owned on mobile, Android and iOS, from an early prototype to 100+ B2B clients and 50,000+ users in 10+ countries. Its Android app is rated 4.7★ from 3,300+ reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In each one, a single engineer was accountable for the mobile app from the first decision to the release. That’s what you should be buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost difference: compare the total, not the rate
&lt;/h2&gt;

&lt;p&gt;An agency’s day rate and an independent engineer’s day rate look like two prices for the same thing. They aren’t. Look at what each one pays for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An agency’s rate carries its overheads.&lt;/strong&gt; Sales, account and project managers, offices, people waiting between projects, and the agency’s own margin are built into the rate for every developer on your project. You pay for them whether your app needs them or not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A blended rate can hide who does the work.&lt;/strong&gt; Agencies often charge one rate for the whole team. If junior developers do most of the hours, you pay a rate set by the seniors you met for work done by the juniors you didn’t.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordination is billable time.&lt;/strong&gt; Status meetings, handoffs and re-explaining your app to each new developer all take hours, and you pay for those hours. With one engineer, most of them disappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rework is the most expensive line, and it’s never on the quote.&lt;/strong&gt; A cheaper hour that produces code someone has to rewrite later isn’t cheaper. A senior engineer’s hour costs more than a junior’s, but it’s spent on the right thing the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI savings reach you directly.&lt;/strong&gt; When AI saves me time, your release comes sooner. With an agency, ask whether the time AI saves shows up in your estimate, or only in their margin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fair comparison is the &lt;strong&gt;total cost of getting a working release into the store&lt;/strong&gt; , for the same scope. That’s hard to do when every quote is based on a different idea of what you’re building. It’s one reason I start every project with a written plan: you can take it to an agency and compare quotes for the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI has shifted the balance further
&lt;/h2&gt;

&lt;p&gt;An agency’s main advantage used to be headcount: more developers meant more code, faster. AI coding tools have weakened that advantage. A senior engineer with AI handling the routine work, such as boilerplate, first drafts of screens and test cases, can now own a scope that used to need a small team.&lt;/p&gt;

&lt;p&gt;What AI hasn’t made any cheaper is judgement. It can draft a screen in minutes. It can’t decide what happens when an upload fails with no signal, fill in Google Play’s Data Safety form truthfully, notice that the app runs out of memory on a cheap phone, or decide whether a release is safe to roll out. Those calls make or break an app, and they need experience, not more people.&lt;/p&gt;

&lt;p&gt;So the trade has changed. &lt;strong&gt;More people now mostly means more code to review.&lt;/strong&gt; One experienced engineer using AI well means the code gets written fast, and checked by someone who knows what to look for.&lt;/p&gt;

&lt;p&gt;I use AI tools every day, and I review everything they produce before it goes into a client’s app. Ask anyone you’re considering, agency or independent, who reviews their AI-written code, and where your code goes when they use these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  “What if you disappear?”
&lt;/h2&gt;

&lt;p&gt;It’s the right question to ask an independent engineer, and it deserves a real answer rather than reassurance. Here’s mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The code is yours, and you can see it from day one.&lt;/strong&gt; I work in my repository, give you read access from the start so you can see every commit, and transfer the code to yours at each paid milestone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions are written down&lt;/strong&gt; , so another developer could pick the app up later. I build apps your own team can take over, not ones only I understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get working builds every week or two&lt;/strong&gt; on a test track, so you always know where things stand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The store accounts and signing keys stay in your name.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s also worth asking the same question of an agency. With an agency, the risk that the person who knows your app leaves doesn’t go away. It’s just less visible, because a developer can be moved to another project without you being told.&lt;/p&gt;

&lt;h2&gt;
  
  
  Already have an agency or a team? Hire a lead, not more builders
&lt;/h2&gt;

&lt;p&gt;If someone is already building your app, you may not need another builder. You may need someone senior on your side. That’s what I do as a &lt;strong&gt;fractional mobile lead&lt;/strong&gt; : part-time ownership of your mobile side, working alongside your developers or agency, not replacing them.&lt;/p&gt;

&lt;p&gt;It fits when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your developers are junior, offshore or at an agency&lt;/strong&gt; , and nobody senior is setting the direction or saying no to shortcuts that will hurt later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agency is building, and nobody on your side can check the work.&lt;/strong&gt; I review the code and the releases for you, not for the agency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You’re about to make your first mobile hire.&lt;/strong&gt; I set up the architecture and help you interview, so the first engineer joins a codebase worth joining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every release is a manual event&lt;/strong&gt; and bugs keep coming back, but you don’t need a full-time lead yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve done this job full-time. At Pulse, after starting as the founding engineer, I became Head of Mobility and led 4–6 mobile engineers: I interviewed candidates, set the review and sprint process, and introduced automated releases with GitHub Actions and Fastlane.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I’d point you to an agency
&lt;/h2&gt;

&lt;p&gt;Honestly, there’s one case: &lt;strong&gt;a big scope with a date that can’t move&lt;/strong&gt; , where you need several developers working in parallel from the first week. One engineer can’t work in parallel with themselves, however good they are.&lt;/p&gt;

&lt;p&gt;Even then, I’d hire someone senior on your side to review what the agency builds, and make sure the code, store accounts and signing keys are yours. A fractional lead does exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions to ask anyone you’re considering
&lt;/h2&gt;

&lt;p&gt;Ask these on the first call, including to me. Here are my answers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;My answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who will write the code?&lt;/td&gt;
&lt;td&gt;I will. The person on the call is the person building your app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who owns the code?&lt;/td&gt;
&lt;td&gt;You do. Read access from day one, transferred to you at each paid milestone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When will I see the app on a real phone?&lt;/td&gt;
&lt;td&gt;Every week or two, on a test track.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who handles the store release?&lt;/td&gt;
&lt;td&gt;I do: signing, Play’s Data Safety form, and a staged rollout.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who reviews AI-written code?&lt;/td&gt;
&lt;td&gt;I do, before it goes into your app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What if we stop working together?&lt;/td&gt;
&lt;td&gt;You keep the code, the accounts, and the written decisions behind them.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If an answer is vague, that tells you something too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next step
&lt;/h2&gt;

&lt;p&gt;You don’t have to commit to a whole build to find out whether working with me suits you. Every project starts with a paid discovery sprint: a fixed ₹60,000 (US$900) for about a week. It ends in a written plan and an estimate that you own and can use with me, with an agency, or with anyone else. If you’re comparing options, it’s the cheapest way to compare them fairly.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://abhishekgoyal.me/services/mvp" rel="noopener noreferrer"&gt;MVP page&lt;/a&gt; explains how a build runs from there. If you already have a team and need a lead, see the &lt;a href="https://abhishekgoyal.me/services#fractional" rel="noopener noreferrer"&gt;fractional mobile lead&lt;/a&gt; service, or just&lt;a href="https://abhishekgoyal.me/contact" rel="noopener noreferrer"&gt;tell me about your app&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>startup</category>
      <category>mvp</category>
    </item>
    <item>
      <title>Two native apps into one React Native codebase in six months</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Thu, 08 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/two-native-apps-into-one-react-native-codebase-in-six-months-543j</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/two-native-apps-into-one-react-native-codebase-in-six-months-543j</guid>
      <description>&lt;p&gt;I owned mobile for Pulse, an offline-first inspection app for auditors, on Android and iOS, from an early prototype to 100+ B2B enterprise clients. For most of that time it was two native apps: Kotlin on Android, Swift on iOS, kept in feature parity. In 2025 I led a full rewrite of both into a single React Native codebase. I architected it and built it alongside the team, and we completed it in six months, before I left in May 2025.&lt;/p&gt;

&lt;p&gt;This isn’t a “native versus React Native” post. The native apps worked. The earlier posts on&lt;a href="https://abhishekgoyal.me/writing/offline-first-sync-lessons" rel="noopener noreferrer"&gt;Pulse’s sync engine&lt;/a&gt; and on&lt;a href="https://abhishekgoyal.me/writing/offline-first-without-a-server" rel="noopener noreferrer"&gt;a local-only app&lt;/a&gt; cover why they were built the way they were. This one is about the rewrite: why we did it, what we kept, and how it reached users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why rewrite two apps that worked
&lt;/h2&gt;

&lt;p&gt;Keeping two native apps in feature parity has a simple cost: every feature is built twice, once in Kotlin and once in Swift, and every fix has to land in both. That was the reason for the rewrite, in two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One codebase to maintain&lt;/strong&gt; instead of two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping faster&lt;/strong&gt; , because a feature or a fix reaches Android and iOS at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither reason says the native apps were bad. They say that keeping two of them had a cost, and one codebase removes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replicate the behaviour, don’t redesign it
&lt;/h2&gt;

&lt;p&gt;The native apps’ behaviour had years of field use behind it: records that get a temporary local ID and are remapped to a server ID when they sync, photos uploaded before the form that references them, upload progress the auditor can see, and inspection forms built dynamically. So the React Native app replicated that behaviour rather than redesigning it.&lt;/p&gt;

&lt;p&gt;The alternative is harder than it looks. A rewrite that also changes how sync works is two projects at once, and every difference between the old app and the new one is something to explain to an auditor in the field. Replicating proven behaviour keeps the rewrite to one question: does the new app do what the old one did?&lt;/p&gt;

&lt;h2&gt;
  
  
  I built the core, the team built on top
&lt;/h2&gt;

&lt;p&gt;I set the architecture and built the core myself: sync, offline storage and uploads. The team built the screens and features on top of it.&lt;/p&gt;

&lt;p&gt;In an offline-first app, that core is where the risk sits. Everything on screen depends on the core behaving the way the native apps did, and in an offline-first app the core is where data gets lost if something is wrong. Screens are easier to see, test and fix; a sync bug can hide until an auditor’s work goes missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling out gradually
&lt;/h2&gt;

&lt;p&gt;The new app didn’t replace the native ones for everyone at once. It rolled out gradually, with the native app still available while users moved over.&lt;/p&gt;

&lt;p&gt;For an offline-first app that isn’t caution for its own sake. Auditors work without signal, so at any moment someone may have work on the phone that hasn’t reached the server yet. A gradual rollout means a problem in the new app reaches a few users rather than all of them, and there is still a working app to fall back on while it’s fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d tell another team
&lt;/h2&gt;

&lt;p&gt;If you’re rewriting an offline-first app, roll it out gradually. Keep the old app available and move users over in stages. A big-bang switch bets every auditor’s unsynced work on the new app being right on day one.&lt;/p&gt;

&lt;p&gt;More on Pulse is in the &lt;a href="https://abhishekgoyal.me/work/pulse" rel="noopener noreferrer"&gt;case study&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>android</category>
      <category>ios</category>
      <category>offlinefirst</category>
    </item>
    <item>
      <title>How to hire an app developer: what to have ready before the first call</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Tue, 06 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/how-to-hire-an-app-developer-what-to-have-ready-before-the-first-call-4k2g</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/how-to-hire-an-app-developer-what-to-have-ready-before-the-first-call-4k2g</guid>
      <description>&lt;p&gt;Most people who want an app built don’t write code, and they shouldn’t have to. But the first call with a developer goes much better when you’ve done a little homework. Not a spec, and not a tech stack. Just a clear picture of the problem, and a few questions of your own.&lt;/p&gt;

&lt;p&gt;I’ve been building Android and iOS apps for 13 years, and I’ve owned four greenfield Android builds end to end. This is what I ask on a first call, what helps if you bring it, and what I’d ask if I were the one hiring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring two things
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A one-paragraph problem.&lt;/strong&gt; Who the app is for, and what it fixes for them, in plain words. “Households that employ domestic staff lose track of attendance, advances and half-days, so the monthly salary settlement is hard to get right” is a good paragraph. “An app like Uber, but for X” is a starting point, not a problem.&lt;/p&gt;

&lt;p&gt;If you can’t write the paragraph yet, that’s useful to know too. It means the first conversation should be about the problem, not about the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A short, ranked list of must-have features.&lt;/strong&gt; Write down what the app has to do, put the most important thing first, and keep the nice-to-haves on a separate list. The ranking matters more than the length. When time or budget runs short, and it usually does, the ranking decides what ships first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you don’t need: a full spec document
&lt;/h2&gt;

&lt;p&gt;You don’t need a 40-page requirements document before you talk to anyone. Turning your problem and your feature list into a plan is part of the work. When I take on an MVP, we agree what the first version must do and what can wait in a paid discovery sprint, and it ends in a written plan and an estimate you can use with me or with anyone else.&lt;/p&gt;

&lt;p&gt;A good developer will help you write that plan. Be wary of one who skips it and quotes a price straight from your feature list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’ll ask you
&lt;/h2&gt;

&lt;p&gt;These are the questions I ask on every first call. Expect a good developer to ask something similar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who are the users?&lt;/strong&gt; Not “everyone”. Who opens the app first, on what kind of phone, and in what situation? A field worker on patchy signal needs a different app from an office team on Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What’s your budget range?&lt;/strong&gt; A rough range is enough. It isn’t there to set the price. It’s there so the plan fits what you can spend, instead of a plan you then have to cut in half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What already exists?&lt;/strong&gt; Designs, a website, a backend, an earlier version of the app, store accounts, anything. Each of these changes the plan, sometimes a lot, and it’s far better to know on day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to ask the developer
&lt;/h2&gt;

&lt;p&gt;Turn the questions around. These are the ones I’d want answered if I were hiring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who owns the code?&lt;/strong&gt; The answer should be you. In my case, you get read access to the repository from the start so you can see every commit, and the code is transferred to your repository at each paid milestone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When will I see the app on a real phone?&lt;/strong&gt; You should get working builds on a test track every week or two as the app grows, not slides and a big reveal at the end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who handles the store release?&lt;/strong&gt; Getting an app into Google Play or the App Store involves signing, store listings, privacy declarations like Play’s Data Safety form, and ideally a staged rollout. Ask whether that’s included, or whether it’s a surprise at the end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Could someone else take over later?&lt;/strong&gt; You want an app your own team, or another developer, can pick up later, not one only its builder understands. Ask how the decisions get written down.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the one-paragraph problem.&lt;/li&gt;
&lt;li&gt;Rank your must-have features, and keep the nice-to-haves separate.&lt;/li&gt;
&lt;li&gt;Know your budget range, and list what already exists.&lt;/li&gt;
&lt;li&gt;Skip the spec. Plan it with the developer, and ask who owns the code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’d like to go through this with me, the &lt;a href="https://abhishekgoyal.me/services/mvp" rel="noopener noreferrer"&gt;MVP page&lt;/a&gt; explains how a build starts with a discovery sprint and what you get at the end of it.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>startup</category>
      <category>mvp</category>
    </item>
    <item>
      <title>Native, Kotlin Multiplatform or React Native: how I'd choose</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/native-kotlin-multiplatform-or-react-native-how-id-choose-24lf</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/native-kotlin-multiplatform-or-react-native-how-id-choose-24lf</guid>
      <description>&lt;p&gt;“Should we build this in Kotlin Multiplatform or React Native?” is a question teams often ask before anyone has written down what the app has to do. That’s backwards. I’ve shipped all three approaches to production: native Android and iOS apps, a shared Kotlin Multiplatform data layer in HelperBook, and a React Native rewrite of two native apps at Pulse. The framework has never been the first decision. The features are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the features, not the framework
&lt;/h2&gt;

&lt;p&gt;Before choosing anything, I list what the app actually has to do, and which of those things lean on the platform. Camera capture, video playback, background work, offline data, sensors, OS integrations. Then I weigh each option against that list, along with the team that will maintain it and the time and money available.&lt;/p&gt;

&lt;p&gt;Most of the heat in “Kotlin Multiplatform vs React Native” debates comes from people comparing the tools in the abstract. For a specific app, the answer is usually much clearer than the debate suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I’d stay fully native
&lt;/h2&gt;

&lt;p&gt;Sharing code is not free, so sometimes the right amount to share is none.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The market is on one platform.&lt;/strong&gt; HelperBook is for Indian households, where Android is the phone most people carry, so it shipped on Android first. Its records still live in a shared Kotlin Multiplatform module with SQLDelight, so an iOS app could reuse that data layer rather than rewrite it, but the app itself is native Android.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The app leans hard on platform APIs.&lt;/strong&gt; On a video-first hiring platform I built 100% of the Android client, and the core of it was CameraX capture, Media3 playback and on-device speech transcription. That is exactly the kind of work where you want the platform’s own APIs directly, not through a layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The two apps look and behave differently.&lt;/strong&gt; If the Android and iOS versions don’t share much UI or flow, a shared UI codebase saves less than it costs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Kotlin Multiplatform fits
&lt;/h2&gt;

&lt;p&gt;Kotlin Multiplatform shares business logic and data while each platform keeps a fully native UI. I reach for it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native apps already exist.&lt;/strong&gt; You can add a shared module to the apps you have and move code into it gradually, instead of rewriting both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The team writes Kotlin.&lt;/strong&gt; Android developers can own the shared code without learning a new stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The business logic is complex and must match on both platforms.&lt;/strong&gt; Rules, calculations and offline data that behave slightly differently on Android and iOS are a bug factory. Writing them once removes a whole class of problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native feel matters.&lt;/strong&gt; The UI stays Jetpack Compose on Android and SwiftUI or UIKit on iOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When React Native fits
&lt;/h2&gt;

&lt;p&gt;React Native gives you one codebase for the UI as well as the logic. I recommend it when the timeline is short and the budget is small, and one team has to ship both platforms quickly. It also makes more sense when the screens are the same on both platforms, and when there’s already a React web team whose skills carry over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance, honestly
&lt;/h2&gt;

&lt;p&gt;People search for “Kotlin Multiplatform vs React Native performance” a lot, so here’s my view. Kotlin Multiplatform keeps the UI fully native, and native UI is smoother. For most business apps (forms, lists, dashboards) React Native is fine, and the difference users feel comes far more from the architecture than from the framework.&lt;/p&gt;

&lt;p&gt;I haven’t shipped a Flutter app, so I won’t argue for or against it from experience. It’s a real option; it’s just not one I can speak to first-hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Pulse taught me
&lt;/h2&gt;

&lt;p&gt;At Pulse I owned mobile, Android and iOS, from an early prototype to 100+ B2B enterprise clients. For years it ran as two native apps, Kotlin on Android and Swift on iOS, kept at feature parity. Keeping two native apps at parity meant building every feature twice.&lt;/p&gt;

&lt;p&gt;In 2025 I led the rewrite of both apps into one React Native codebase, architected and co-built with the team in six months, and it replaced both native apps in production. React Native fitted the situation: one codebase suited the timeline and budget, the web app was already built in React, and the screens were the same on both platforms.&lt;/p&gt;

&lt;p&gt;The hardest part of the port was reliable background uploads, which the offline engine depended on. The inspections could run to 500+ questions and 1,000+ photos, captured offline and uploaded in the background once a connection came back, so the upload behaviour had to be as dependable after the rewrite as before it.&lt;/p&gt;

&lt;p&gt;Looking back, React Native was a reasonable call for that timeline, budget and team. Knowing what I know now, I’d weigh Kotlin Multiplatform harder for an app with that much offline logic. The sync rules and the upload queue are exactly the kind of code you want written once and shared, while each platform keeps its own native background APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I’d decide for your app
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If your app…&lt;/th&gt;
&lt;th&gt;I’d lean towards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Targets one platform first, or leans hard on camera, video, sensors&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already has native apps, or complex logic that must match on both&lt;/td&gt;
&lt;td&gt;Kotlin Multiplatform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs both platforms fast, on a small budget, with the same screens&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these is a rule. They’re where I start, and the feature list decides the rest. If you’re weighing this for a real app, &lt;a href="https://abhishekgoyal.me/services/kmp" rel="noopener noreferrer"&gt;here’s how I help teams adopt Kotlin Multiplatform&lt;/a&gt;, and every engagement starts with a paid discovery sprint that ends in a written recommendation you can use with anyone.&lt;/p&gt;

</description>
      <category>kotlinmultiplatform</category>
      <category>reactnative</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Offline-first with a server: lessons from Pulse's sync engine</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Thu, 24 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/offline-first-with-a-server-lessons-from-pulses-sync-engine-dng</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/offline-first-with-a-server-lessons-from-pulses-sync-engine-dng</guid>
      <description>&lt;p&gt;The &lt;a href="https://abhishekgoyal.me/writing/offline-first-without-a-server" rel="noopener noreferrer"&gt;local-only HelperBook&lt;/a&gt; post is about an app that never talks to a server, and what that buys you. Pulse is the opposite case: an offline-first inspection app for auditors that has to reach a server eventually, because the whole point is that the data ends up somewhere other people can see it. I owned mobile for Pulse, Android and iOS, from an early prototype to 100+ B2B enterprise clients. The sync contract (payloads, upload semantics, failure behaviour) was something I designed with the backend and web teams before implementing both native clients against it. This is what I learned building it, not a walkthrough of how it works end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Records need an identity before the server has one
&lt;/h2&gt;

&lt;p&gt;An auditor can start an inspection with no signal at all. The form has to exist as soon as they start filling it in, which means it needs an ID before the server has assigned one. The app solved this with a temporary local ID, created on the device the moment the record starts. When the device reconnects and the record is created on the server, the app gets back a server ID and remaps the form’s answers and any media against it.&lt;/p&gt;

&lt;p&gt;That remapping is the part that’s easy to underestimate. Every piece of local state that referred to the temporary ID — form answers, photo attachments, upload queue entries — has to be repointed to the server ID without losing anything or duplicating anything. And it has to survive retries: if the same record tries to sync again after a failure, it has to reuse the server ID it already has rather than asking for a new one, or the server would end up with two records for the same inspection. As a simplified illustration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified illustration, not Pulse's actual code.&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;idForUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalRecord&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;RecordId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverId&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;assigned&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRecordOnServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assigned&lt;/span&gt;
        &lt;span class="n"&gt;assigned&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost of this is real: you’re carrying two ID spaces through the whole client, and every piece of code that touches a record needs to know which one it’s holding. But the alternative — waiting for a server ID before letting the auditor start work — isn’t available to an offline-first app. If there’s no signal, there’s no server round trip to wait for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload order is a correctness decision, not a performance one
&lt;/h2&gt;

&lt;p&gt;Inspection forms come with photos, and photos are larger and slower to upload than form data. It would be tempting to submit the form first, since that’s the thing the auditor is waiting on, and let the photos trail in behind it. Pulse did the opposite: photos were uploaded first, in the background, while the auditor was still working, and the form was only submitted once every photo attached to it was confirmed on the server.&lt;/p&gt;

&lt;p&gt;The reason is what that ordering prevents. If the form went first, the server would briefly, or not so briefly on a bad connection, hold a submitted inspection with photos missing. Anyone looking at that record from the web or backend side would see a completed inspection with holes in it, and there’d be no clean way to tell “still uploading” apart from “photo was never taken.” Uploading media first and gating the form submission on it means the server never has to represent that in-between state at all. The trade-off is that a form can be finished on the device and still not “done” from the server’s point of view, which is exactly the kind of thing that has to be visible to the person waiting on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status has to be visible, or people don’t trust the sync
&lt;/h2&gt;

&lt;p&gt;That last point led to one of the changes I’m gladdest we made, and it came out of a real problem: auditors weren’t sure whether their work had actually reached the server once they’d submitted a form. The submission felt finished on their screen, but the photos might still be uploading in the background, and there was nothing telling them that. From their side, “I submitted it” and “it’s on the server” looked identical, and they had no way to tell those two states apart.&lt;/p&gt;

&lt;p&gt;The fix was to stop treating sync as something that happens invisibly after submission and start showing it: once a form was submitted, the UI showed upload progress and a count of items still left to go up. It’s a small piece of UI, but it changed what “submitted” meant to the person using the app. Instead of a single state that quietly covered “done” and “still working on it,” there were two, and the auditor could see which one they were in. The lesson generalises past Pulse: any offline-first system is going to have a gap between “the user is finished” and “the server has it,” and if that gap isn’t visible, users will assume the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflicts are mostly designed away, but not entirely
&lt;/h2&gt;

&lt;p&gt;Most of the time two people weren’t editing the same inspection record at once, and the app was built so that was the common case rather than something handled after the fact. But an offline-first system can’t promise that never happens — a record can be edited on two devices before either has synced, and eventually both versions reach the server. When that happened, the default was last write wins: whichever version reaches the server second was the one that was kept.&lt;/p&gt;

&lt;p&gt;Last write wins is a reasonable default because it’s simple and it doesn’t block anyone waiting to sync. But it’s also exactly the kind of thing that can silently overwrite someone’s work, which is why it isn’t the whole answer here. When a conflict was detected, the user saw a warning and could choose to keep their local version instead of the one that had just landed. That warning is the important part. A conflict-resolution strategy that resolves quietly is only safe if you’re confident it never matters which side wins; the moment that’s not certain, the person who did the work needs the chance to say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing nothing is actually getting lost
&lt;/h2&gt;

&lt;p&gt;None of this is worth much if you can’t tell whether it’s working. The way I had visibility into that was logging and monitoring on the upload pipeline itself — failures were visible, not silent, so a stuck or failing upload showed up rather than disappearing. That’s a narrower claim than “we proved nothing was ever lost”; it’s closer to “we could see when something didn’t make it, instead of finding out from someone else much later.” Over four and a half years of field use, no data-loss incidents were reported. The ordering and remapping decisions above are what kept data safe; the monitoring is what makes “none reported” mean something, because a failed upload would have shown up in the logs rather than waiting for someone to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d tell another builder
&lt;/h2&gt;

&lt;p&gt;If you’re building sync into an offline-first app, the identity problem comes first: decide early how a record gets an ID before the server has assigned one, and plan for the remapping that follows, because it touches everything downstream.&lt;/p&gt;

&lt;p&gt;Second, think about upload order as a correctness tool, not just a performance one. Whatever order your uploads go in defines what invalid in-between states your server can end up holding, so pick the order that minimises those states rather than the order that feels fastest to the user.&lt;/p&gt;

&lt;p&gt;Third, don’t let “submitted” and “synced” collapse into one state in the UI. They’re different things, and the moment users can’t tell them apart is the moment they stop trusting the app to have their data.&lt;/p&gt;

&lt;p&gt;And last, pick a conflict strategy you can explain in one sentence, then make sure the person whose work might get overwritten by it gets a say. Last write wins is fine as a default. It’s only fine because there’s a warning attached to it.&lt;/p&gt;

&lt;p&gt;More on how the Pulse sync engine fits together is in the &lt;a href="https://abhishekgoyal.me/work/pulse" rel="noopener noreferrer"&gt;case study&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>offlinefirst</category>
      <category>sync</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Offline-first with no server: why HelperBook is local-only</title>
      <dc:creator>Abhishek Goyal</dc:creator>
      <pubDate>Thu, 24 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/abhishek_goyal_c97dbf2eea/offline-first-with-no-server-why-helperbook-is-local-only-13ha</link>
      <guid>https://dev.to/abhishek_goyal_c97dbf2eea/offline-first-with-no-server-why-helperbook-is-local-only-13ha</guid>
      <description>&lt;p&gt;A lot of my work has been on offline-first apps that still, eventually, talk to a server. At Pulse, I designed the sync contract with the backend and web teams: payloads, upload semantics, failure behaviour. The app worked without signal, but it was always working towards a moment when it would have signal again and reconcile.&lt;/p&gt;

&lt;p&gt;HelperBook, the app I’m building now for Indian households to track staff attendance, advances and salary settlements, doesn’t have that moment. There is no signup and no server. Records live on the device, full stop. I want to explain why that’s a design decision and not a missing feature, and what it costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data is the reason not to have a server
&lt;/h2&gt;

&lt;p&gt;HelperBook stores wage data: how many days someone worked, what they were advanced, what they’re owed at month end. That’s exactly the kind of data a household might hesitate to put into an app if it thought the numbers were going somewhere.&lt;/p&gt;

&lt;p&gt;Keeping wage data off any server keeps the app outside data-fiduciary scope under India’s DPDP Act, and it gives households a reason to enter real numbers. Those two things are connected. An app that can’t leak your payroll data because it never has it in the first place is a different trust proposition than one that promises to look after your payroll data on a server somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  No sync engine to design
&lt;/h2&gt;

&lt;p&gt;The technical consequence is that there’s no sync contract to design, no conflict resolution, no retry queue, no “what happens if the upload fails halfway through” states. Records live on-device via SQLDelight, inside a shared Kotlin Multiplatform module. Users own their data through export and restore.&lt;/p&gt;

&lt;p&gt;That’s a smaller surface area than Pulse’s sync engine, and it’s smaller on purpose. Every state a sync engine has to handle — offline, online, mid-sync, conflicting edits from two devices — is a state HelperBook doesn’t have. The trade-off is direct: no server means no multi-device. If a household member wants to check the ledger from a second phone, they can’t. And if the phone is lost or wiped without an export first, the data is gone. There’s no account to recover it from. That’s a real cost, and it’s one I chose deliberately rather than one I didn’t notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the honesty into the data model, not the UI
&lt;/h2&gt;

&lt;p&gt;A local-only app has one more responsibility than a server-backed one: the rules have to be right, because there’s no admin panel on the backend to fix a bad month for someone later. So the product rules that matter most live in the data model rather than as UI copy or a setting someone can miss.&lt;/p&gt;

&lt;p&gt;Unmarked days default to present, so a worker is never underpaid because someone forgot to tap. Half-days deduct exactly half, with no rounding surprises at month end. And settlement statements are generated bilingually, in English and Hindi, so the employer and the helper can both read every line without one of them having to trust the other’s translation.&lt;/p&gt;

&lt;p&gt;As a simplified illustration, the “unmarked day” rule looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified illustration of the rule, not HelperBook's actual code.&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;attendanceValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DayMark&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;DayMark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ABSENT&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="nc"&gt;DayMark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HALF_DAY&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="nc"&gt;DayMark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PRESENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part isn’t the code, it’s the default: a missing mark resolves to &lt;code&gt;1.0&lt;/code&gt;, not &lt;code&gt;0.0&lt;/code&gt;. In a local-only app, that decision is the whole safety net. There’s no support ticket, no server-side correction, no second system checking the math. Whatever the function returns is what the worker gets paid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release engineering without a backend to hide behind
&lt;/h2&gt;

&lt;p&gt;Going local-only doesn’t remove the need for release discipline, it just moves where it matters. HelperBook ships as a signed AAB with R8 rules written for SQLDelight and kotlinx-serialization, so obfuscation doesn’t break the database layer or the serialised export format. Crashlytics is wired in, but the logs carry no names or amounts. Play’s Data Safety declarations describe what the app actually does. And there’s a staged-rollout plan, so a wider release doesn’t reach everyone at once.&lt;/p&gt;

&lt;p&gt;None of that is specific to offline-first apps. But when there’s no backend team and no server logs to lean on for debugging, the discipline around what you ship and how you roll it out is the only safety net you have. It’s currently in closed beta on Google Play with 12–20 households, built with Jetpack Compose, min SDK 24, target API 35.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d tell another builder
&lt;/h2&gt;

&lt;p&gt;If you’re weighing whether a product needs a backend at all, the question I’d ask first isn’t “can we build the sync engine,” it’s “what does the user lose if there’s no sync engine.” For HelperBook, the loss is multi-device access and recovery-without-export, and I judged that a household tracking one home’s payroll would rather have neither of those than have their wage data sitting on a server. For a different product, that trade might go the other way.&lt;/p&gt;

&lt;p&gt;The second thing I’d say: local-only doesn’t mean less engineering. It means the engineering moves into the data model and the release pipeline instead of into a sync contract. You still have to get the rules right, you still have to obfuscate and log carefully, you still have to plan the rollout in stages. You just don’t get to blame a server for anything.&lt;/p&gt;

&lt;p&gt;More on how HelperBook is built is in the &lt;a href="https://abhishekgoyal.me/work/helperbook" rel="noopener noreferrer"&gt;case study&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>offlinefirst</category>
      <category>kotlinmultiplatform</category>
      <category>privacy</category>
      <category>android</category>
    </item>
  </channel>
</rss>
