Getting Started with Flutter

If you’ve been curious about cross-platform mobile development — building one codebase that runs on both iOS and Android — Flutter is one of the most compelling options available today. This guide covers what Flutter and Dart are, how they fit together, how to create your first project, and — critically — where Flutter shines and where it falls short.


What Is Flutter?

Flutter is an open-source UI toolkit developed by Google. Rather than wrapping native platform controls, Flutter draws every pixel of its UI itself using its own rendering engine (Skia, and now Impeller on newer versions). This means a Flutter app looks and behaves identically on iOS, Android, web, macOS, Windows, and Linux — from a single codebase.

Flutter was first released publicly in 2018 and has grown into a mature, production-ready framework used by companies like BMW, Alibaba, and eBay Motors.

Key characteristics:

  • Single codebase — write once, deploy to multiple platforms
  • Compiled to native ARM code — not interpreted, not a WebView
  • Rich widget library — Material Design (Android-style) and Cupertino (iOS-style) widgets built in
  • Hot reload — see UI changes in under a second without losing app state
  • Strong ecosystem — thousands of packages on pub.dev

What Is Dart?

Dart is the programming language that powers Flutter. It was also created by Google, first appearing in 2011, and has been significantly refined over the years. Dart is a statically typed, object-oriented language with a syntax that will feel familiar if you’ve worked with Swift, Kotlin, Java, or C#.

Why Dart instead of JavaScript, Swift, or Kotlin?

Google designed Dart specifically to meet the demands of UI development:

  • Ahead-of-time (AOT) compilation produces fast native binaries for release builds
  • Just-in-time (JIT) compilation during development enables hot reload
  • Strong typing with null safety catches errors at compile time, not runtime
  • Async/await support is built in and idiomatic — essential for network calls and I/O
  • Single-threaded with isolates — Dart avoids shared-memory concurrency bugs by using message-passing between isolates

If you’re coming from Swift, you’ll find Dart’s syntax comfortable. Generics, optionals (via null safety), closures, and protocol-like abstract class patterns are all present. The learning curve is gentle.

A quick Dart example:

class Location {
  final double latitude;
  final double longitude;
  final String? label; // nullable String

  const Location({
    required this.latitude,
    required this.longitude,
    this.label,
  });

  @override
  String toString() => label ?? '$latitude, $longitude';
}

void main() async {
  final loc = Location(latitude: 33.9137, longitude: -97.1384, label: 'Klyde Warren Park, Dallas, TX');
  print(loc); // Output: Klyde Warren Park, Dallas, TX
}

Basic Project Structure

A Flutter project follows a consistent directory layout:

my_app/
├── lib/
│   └── main.dart          # Entry point — your app starts here
├── test/
│   └── widget_test.dart   # Widget tests
├── ios/                   # iOS-specific native code and project
├── android/               # Android-specific native code and Gradle project
├── web/                   # Web target (if enabled)
├── macos/                 # macOS target (if enabled)
├── pubspec.yaml           # Project manifest — dependencies, assets, metadata
├── pubspec.lock           # Locked dependency versions
└── README.md

The key files to understand:

  • lib/main.dart — This is where void main() lives and where your root widget is defined. Everything flows from here.
  • pubspec.yaml — Your project’s manifest. You declare dependencies (like httpprovidergo_router), fonts, and asset paths here.
  • ios/ and android/ — These are full native project directories. You’ll rarely need to edit them directly, but they’re required for platform-specific capabilities like signing, push notifications, and entitlements.

As a project grows, most Flutter developers organize lib/ into subdirectories by feature or layer:

lib/
├── main.dart
├── src/
│   ├── features/
│   │   ├── settings/
│   │   └── home/
│   ├── models/
│   ├── services/
│   └── widgets/

Getting Started: Your First Flutter Project

1. Install Flutter

Download the Flutter SDK from flutter.dev and follow the installation instructions for your platform. Then run the diagnostic tool to confirm your environment is ready:

flutter doctor

This checks for Xcode (for iOS), Android Studio (for Android), connected devices, and any missing dependencies.

2. Create a New Project

flutter create my_app

This scaffolds a working counter app with all platform runners in place. You can control the output with flags:

flutter create \
  --org com.yourcompany \
  --template skeleton \
  --platforms ios,android \
  my_app

The --template skeleton flag is recommended over the default — it produces a more realistic starting structure with a list/detail view, localization setup, and theming, rather than the toy counter demo.

3. Run the App

cd my_app
flutter run

Flutter will detect connected devices or simulators and launch the app. With a simulator running, you’ll have hot reload available — press r in the terminal to reload, or R for a full restart.

4. What You’re Looking At in main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: Scaffold(
        appBar: AppBar(title: const Text('Hello Flutter')),
        body: const Center(child: Text('Welcome')),
      ),
    );
  }
}

Everything in Flutter is a widget — the app itself, the scaffold, the text, the padding. Widgets are immutable descriptions of UI; Flutter rebuilds them efficiently when state changes.


App Store and Play Store Compatibility

Flutter apps can be submitted to both the Apple App Store and Google Play Store, but there are important nuances to understand before committing to it.

Apple App Store

Flutter apps compile to native ARM binaries via Xcode and are packaged as standard .ipa files. Apple has no special restrictions on Flutter as a framework — it is treated the same as any other native app.

What works:

  • Full App Store submission and distribution
  • TestFlight beta distribution
  • In-App Purchases via in_app_purchase package (StoreKit 2 support has improved but lags behind native Swift implementations)
  • Push Notifications via firebase_messaging or flutter_local_notifications
  • Sign in with Apple
  • App Clips (with extra native configuration)
  • Privacy manifest requirements (Flutter 3.19+ includes the required PrivacyInfo.xcprivacy)

Things to be aware of:

  • Xcode is still required to build and sign iOS apps. You cannot build for iOS on a non-Mac machine.
  • Privacy Nutrition Labels — you’ll need to accurately disclose what data Flutter and any third-party packages collect. Some popular packages (Firebase, for example) collect data that must be declared.
  • App Review — Apple reviews Flutter apps the same way as any other app. There are no Flutter-specific rejection reasons, but reviewers do test on real devices.
  • Binary size — Flutter apps have a larger minimum binary size (~8–10 MB baseline) compared to a minimal native SwiftUI app, due to the embedded Flutter engine.

Google Play Store

Flutter has excellent Android support and Google Play submission is straightforward.

What works:

  • Full Play Store submission
  • App Bundle (.aab) format, which Play uses for size-optimized delivery
  • Play Billing (In-App Purchases)
  • Firebase integration
  • Adaptive icons and splash screens

Things to be aware of:

  • Target API level requirements — Google Play enforces minimum target SDK levels. Flutter’s toolchain keeps pace with these requirements, but you need to stay on a reasonably current Flutter version.
  • 64-bit requirement — Flutter produces 64-bit binaries by default, so this is handled for you.

What Flutter Is Good For

Flutter excels in scenarios where UI consistency, development speed, and cross-platform reach matter most:

  • Content-driven apps — news readers, recipe apps, catalog browsers, dashboards
  • E-commerce and retail apps — product listings, carts, checkout flows
  • Business and enterprise internal tools — forms, data displays, workflows
  • Startups and MVPs — ship to both stores from one codebase, fast
  • Apps where you own the full UI — if your design system is custom rather than strictly platform-native, Flutter’s pixel-perfect rendering is an advantage
  • Cross-platform ambitions — a single codebase that also targets web, macOS, and Windows with relatively little additional work
  • Teams with web/backend backgrounds — Dart is easier to onboard for JS/Java/C# developers than Swift or Kotlin

What Flutter Is Not Good For

This is the section most Flutter tutorials skip, and it matters — especially if your background is in native iOS development for professional workflows.

Deep Device Hardware Access

Flutter’s plugin architecture creates a bridge between Dart and native platform code. For well-supported hardware like the camera and GPS, this works fine. But for lower-level hardware, you either need to write your own platform channel code (in Swift/Kotlin) or depend on a third-party package that may not be maintained.

Hardware that is awkward or unsupported without custom native code:

  • Accelerometers and gyroscopes — the sensors_plus package provides basic access, but raw, high-frequency sensor data for applications like aerobatics recording or precision motion analysis is much better handled natively
  • Barometer / altimeter — limited, single-value access; not suitable for precision altitude work
  • CoreMotion / CoreLocation advanced features — significant gravity, device motion, heading with calibration, and background location modes require native Swift code
  • ARKit / ARCore — augmented reality is technically possible but painful; native implementations are dramatically more capable
  • Core Bluetooth (BLE) — the flutter_blue_plus package covers common cases, but complex GATT profiles and foreground service management hit limitations quickly
  • CoreNFC / UWB — minimal Flutter support; essentially requires native plugin code
  • HealthKit / Health Connect — covered by packages, but deep integration (workouts, ECG, background delivery) often requires dropping to native

Platform-Native Feel and Behavior

Flutter renders its own widgets — it does not use UIKit, SwiftUI, or Android’s native Views. This means:

  • Native navigation gestures may not feel identical to a pure UIKit/SwiftUI app, though Flutter’s Cupertino widgets approximate them well
  • Accessibility (VoiceOver / TalkBack) is functional but requires deliberate effort and testing — it does not come for free the way it does with native controls
  • Platform-specific UI conventions (like share sheets, system alerts, and action sheets) may look slightly different unless you use the Cupertino widget set carefully
  • Text rendering and selection behavior can differ subtly from platform-native behavior

Performance-Critical or CPU-Intensive Work

Flutter UI runs on the main thread with a separate GPU thread. For most apps this is fine. However:

  • Heavy computation (signal processing, image analysis, large data transforms) should be offloaded to Dart isolates, which adds complexity
  • Rust FFI — something I use heavily in GeoLog and GeoReturn via Rust libraries I written  location-kitastrometry-kit, and photography-kit — are not supported in Flutter/Dart in the way it is in Swift. Dart FFI exists but the toolchain integration for complex Rust libraries is considerably more involved than Swift’s bindgen-based approach.

Apps That Are Already Deep in Apple Frameworks

If your app is already tightly coupled to SwiftUI, SwiftData, CloudKit, StoreKit 2, Live Activities, Dynamic Island, WidgetKit, or Core Data, a Flutter rewrite would mean abandoning all of that work and re-implementing it through plugins — many of which have partial coverage of the native API surface.


Summary

FlutterNative (Swift/Kotlin)
Cross-platform from one codebase❌ (separate codebases)
App Store / Play Store compatible
Deep hardware access⚠️ Partial✅ Full
Platform-native UI conventions⚠️ Approximate✅ Native
Rust FFI integration⚠️ Complex✅ First-class
SwiftData / CloudKit / Live Activities
Good for MVPs and cross-platform reach⚠️ Costly
Learning curve (Swift/Kotlin background)Low–MediumFamiliar

Flutter is an excellent tool for the right project. For apps that prioritize cross-platform reach, custom UIs, and content-driven experiences, it is hard to beat. For apps that need deep integration with Apple’s platform frameworks, precision sensor access, or tight Rust FFI workflows, native Swift remains the stronger choice.


Have questions about Flutter or cross-platform mobile development? Drop a comment below.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *