Non-members, click here:

Every developer has that late-night crazy thought. Mine was simple:

"What if I just cut the internet and see how my app behaves?"

So I did it.

I reached behind my desk, pulled the Ethernet cable out of my router, and watched the little Wi-Fi light keep blinking like everything was fine.

But my app knew the truth.

The login screen locked me out instantly. The feed showed nothing but an endless spinner. Even the Settings page failed — as if toggling Dark Mode needed the internet.

That's when it hit me.

My app was not really an app. It was just a puppet, with the internet pulling all the strings.

And when I cut the strings, the puppet fell flat.

So I made a decision: I would rebuild my app to work offline only.

No excuses. No "try again later" messages. Just local-first architecture.

And that choice changed everything about how I design apps.

When the Internet Was Gone

Here's what broke first:

  • Login: Without the server, I couldn't even get past the welcome screen.
  • API Calls: Every list, detail, and action depended on the cloud.
  • Navigation: Some screens only worked if the API sent data.
  • Caching: My so-called "cache" was just leftover JSON, not real storage.
  • Error Handling: Instead of a friendly "You're offline" message, I got scary error messages.

Basically, my app was a browser tab in disguise.

So I made a bold decision:

I will force this app to work offline only.

And that's where the real journey started.

The Big Architecture Shift

I had to rethink everything. Here's how my mindset changed.

1. From API-First → Local-First

Before:

  • Every screen waited for an API response.
  • The server was the "truth."
  • Local storage was just a short cache.

After:

  • The local database became the "truth."
  • Every piece of data lived on the device first.
  • The server was only there for syncing later.

Or in simple words: My app stopped depending on the cloud. It started to stand on its own.

2. The Local Database Becomes King

I had ignored databases for years. Now they became my best friend.

  • iOS: I used Core Data. A bit painful to set up, but very powerful.
  • Flutter: I tried Hive for simple storage and Drift when I needed SQL.

New rule: The UI always shows what's in the local DB.

3. Sync Layer: The New Heart

This was the biggest shift in thinking.

Old way:

UI -> Call API -> Show Results

New way:

UI -> Save to Local DB -> Queue for Sync -> Push to Server (when online)

The app worked even without internet. When the device came online again, the sync worker pushed the changes.

4. Conflict Resolution: The Tough Part

This part made me lose sleep.

  • What if the user edits something offline… but the server also has a new update?
  • Whose version is correct?

I tried different strategies:

  • Last Write Wins: Easy but dangerous — user edits could vanish.
  • Timestamps and Merge: Better, but messy.
  • CRDTs (fancy algorithms): Too complex for my case.

In the end, I chose a mix:

  • For text: last edit wins.
  • For lists: merge items.
  • For counters: add values.

Not perfect. But much better than silent data loss.

5. UX Needs to Change Too

Offline apps feel different. I had to redesign the experience.

  • Optimistic UI: Show changes immediately, don't wait for server.
  • Sync Icons: Small cloud symbol to show syncing state.
  • Conflict Alerts: Ask the user which version they want to keep.
  • Retry Queues: Keep operations safe until internet returns.

My Two Experiments: iOS and Flutter

Since I build in both iOS and Flutter, here's how I solved it.

iOS (Swift + Core Data + Background Sync)

// Save note offline first
func addNoteOffline(title: String, content: String) {
    let note = NoteEntity(context: context)
    note.title = title
    note.content = content
    note.isSynced = false
    saveContext()
    queueForSync(note)
}

// Sync later when online
func syncNotes() {
    let unsyncedNotes = fetchUnsyncedNotes()
    for note in unsyncedNotes {
        api.upload(note) { success in
            if success {
                note.isSynced = true
                saveContext()
            }
        }
    }
}

Flutter (Hive + Background Worker)

// Save task offline
void addTask(String title) {
  final task = Task(title, false, DateTime.now(), false);
  hiveBox.add(task.toJson());
  queueForSync(task);
}
// Sync later
Future<void> syncTasks() async {
  final tasks = hiveBox.values.where((t) => t['synced'] == false);
  for (var task in tasks) {
    final success = await api.upload(task);
    if (success) {
      task['synced'] = true;
      hiveBox.put(task['id'], task);
    }
  }
}

Both stacks followed the same rule: Local first. Sync later.

The Surprises

None
Photo by Guido Jansen on Unsplash

Here's what I didn't expect.

  1. Performance got better. Everything felt instant since UI didn't wait for API.
  2. Users loved it. They could use the app on flights, trains, or bad network zones.
  3. Testing got funny. I kept switching to Airplane Mode like a maniac.
  4. Bugs increased. Sync bugs are evil — fix one, three more appear.
  5. My thinking changed. I stopped saying "This is a cloud app." Now I say: "This is a local app that syncs when possible."

Lessons Learned

None
Photo by Iana Dmytrenko on Unsplash
  • Build offline-first, and your online mode becomes stronger automatically.
  • Sync is not a feature — it is the core architecture.
  • Conflict resolution matters as much as design.
  • Respect your local database. It's your best friend.
  • Test offline regularly. Pull the Wi-Fi plug and see what breaks.

Closing Thoughts

What started as a silly experiment turned into a total redesign of how I think about apps.

By forcing my app to run offline, I discovered its real shape. It became stronger, faster, and more user-friendly.

And I learned one big lesson:

The internet is not a feature. It's a luxury. Offline is survival.

So if you are building an app in 2025, try it once. Turn off your Wi-Fi, open your app, and watch what happens.

That's when the real architecture lessons will show up.

A message from our Founder

Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.

Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don't receive any funding, we do this to support the community. ❤️

If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, Instagram. You can also subscribe to our weekly newsletter.

And before you go, don't forget to clap and follow the writer️!