DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

I Built a Real Phone App With Flutter — One Codebase for iOS + Android

For two days the series went mobile-adjacent (on-device Whisper), and today it goes fully mobile: I built a real phone app — Country Explorer — with Flutter, the framework that ships one codebase to both iOS and Android. And the whole thing is three widgets in a trench coat.

This is Day 48 of TechFromZero — a new technology every day, built from scratch.

The pitch: one codebase, every platform

Flutter lets you write your UI once in Dart and ship it to iOS, Android, web, and desktop from the same code. No separate Swift and Kotlin teams. It draws every pixel itself with its own rendering engine, so the app looks and behaves identically everywhere.

flutter create country_explorer
flutter run        # hot-reloads straight onto your phone or emulator
Enter fullscreen mode Exit fullscreen mode

That flutter run with hot reload is the magic moment — change a line, see it on the device in under a second.

Everything is a widget

In Flutter the entire UI is a tree of widgets. The button is a widget, the padding around it is a widget, the screen is a widget. You build interfaces by nesting widgets — no XML, no storyboards. Once that clicks, the whole framework feels consistent.

Scaffold(
  appBar: AppBar(title: Text('🌍 Countries')),
  body: CountryList(),
)
Enter fullscreen mode Exit fullscreen mode

Stateless vs Stateful + setState

A StatelessWidget just draws from its inputs (a country row). A StatefulWidget holds changing data — the search text, the loaded list. When that data changes you call setState(), and Flutter re-runs build() to repaint only what changed. That's the entire reactivity model.

setState(() => query = newText);   // build() runs again with the new query
Enter fullscreen mode Exit fullscreen mode

FutureBuilder for async data

Apps live on network calls. FutureBuilder takes a Future (your API request) and rebuilds itself as it resolves — spinner while waiting, list when data arrives, error if it fails. No manual loading flags:

FutureBuilder(
  future: fetchCountries(),       // http.get(restcountries.com)
  builder: (ctx, snap) => snap.hasData
    ? CountryListView(snap.data!)
    : CircularProgressIndicator(),
)
Enter fullscreen mode Exit fullscreen mode

Country Explorer pulls real data from the free REST Countries API — no key required.

ListView.builder + Navigator

You might have 250 countries, so building every row up front is wasteful. ListView.builder is lazy — it only constructs the rows on screen and recycles them as you scroll. Tapping a row pushes a detail screen via Navigator, and the back gesture pops it, with animated transitions for free:

ListView.builder(
  itemCount: countries.length,
  itemBuilder: (ctx, i) => ListTile(
    title: Text(countries[i].name),
    onTap: () => Navigator.push(ctx,
      MaterialPageRoute(builder: (_) => DetailScreen(countries[i]))),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Try it without installing Flutter

A Flutter app can't run inside a blog, so on the live page I mocked Country Explorer in a phone frame — real countries, search, tap-for-detail — so you can feel the app in your browser. The actual Dart project (the one that compiles to iOS + Android) is on GitHub.

Why Flutter took over

List + detail + navigation + async data is the skeleton of nearly every mobile app, and you've now seen all of it. Add one codebase for two platforms, sub-second hot reload, and a huge widget catalog, and you understand why so many teams reach for Flutter when they need an app on both stores without doubling the work.

👉 Try the live demo + full walkthrough: https://dev48v.infy.uk/tech/day48-flutter.html

💻 Code: https://github.com/dev48v/flutter-from-zero

🌐 All 48 days: https://dev48v.infy.uk/techfromzero.php

This is Day 48 of a 50-day series. Day 49 lands next.

Top comments (0)