Skip to content
Home
Flutter: Cross-Platform Apps with Dart

Flutter: Cross-Platform Apps with Dart

Mobile Development Mobile Development 7 min read 1467 words Beginner ExcellentWiki Editorial Team

Flutter is Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. Unlike React Native, which bridges JavaScript to native widgets, Flutter draws its own widgets using the Skia rendering engine — now transitioning to Impeller on iOS and Android. This gives Flutter complete control over every pixel on the screen, resulting in consistent, high-performance interfaces across platforms.

Released in 2018, Flutter has grown to become one of the most popular cross-platform frameworks, used by Google, BMW, eBay, Alibaba, and thousands of other companies. It uses Dart as its programming language — a client-optimized language with fast compilation, garbage collection, and an expressive type system. According to Google’s official Flutter documentation, the framework powers over 500,000 apps across app stores globally.

Setting Up Flutter

Install the Flutter SDK from flutter.dev, then verify the installation:

flutter doctor

The flutter doctor command checks your system for required tools — Android Studio, Xcode (for iOS), Chrome (for web), and any missing components. Follow its recommendations to complete the setup. The Flutter SDK includes the Dart SDK, the Flutter framework, and command-line tools for building, testing, and deploying apps.

Create a new project:

flutter create my_app
cd my_app
flutter run

Flutter supports multiple platforms out of the box. Add --platforms web,android,ios,linux,macos,windows to restrict or expand target platforms during project creation.

Dart Essentials for Flutter

Dart is designed to be familiar to developers coming from Java, JavaScript, or C#. It features sound null safety, pattern matching introduced in Dart 3, and a rich standard library:

var name = 'Alice';          // Type inference
String title = 'Hello';       // Explicit type
final age = 30;               // Single assignment (runtime constant)
const pi = 3.14159;           // Compile-time constant

// Sound null safety — non-nullable by default
String? maybeName = null;     // Nullable type
int length = maybeName?.length ?? 0;

// Functions are objects, can be passed as parameters
int add(int a, int b) => a + b;

// Classes with initializing formals
class User {
  final String name;
  final int age;

  User(this.name, this.age);

  String greet() => 'Hello, $name';
---

// Records (Dart 3+)
(String, int) userRecord = ('Alice', 30);

// Pattern matching (Dart 3+)
switch (userRecord) {
  case ('Alice', var age) when age > 18:
    print('Adult Alice');
  default:
    print('Unknown');
---

Dart’s sound null safety ensures that variables are non-nullable by default, eliminating null pointer exceptions at compile time. The type system is flow-sensitive, meaning the compiler narrows types automatically after null checks.

Widget Architecture

Everything in Flutter is a widget. A widget describes a part of the UI — structural (button, menu), stylistic (font, color), or layout-related (padding, alignment). Widgets are composed together to build complex interfaces. This composition-over-inheritance approach is central to Flutter’s design philosophy.

Stateless vs Stateful Widgets

A StatelessWidget describes UI that does not change over time:

class Greeting extends StatelessWidget {
  final String name;

  const Greeting({super.key, required this.name});

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name!');
  }
---

A StatefulWidget maintains mutable state that changes over time:

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
---

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: () {
            setState(() => _count++);
          },
          child: const Text('Increment'),
        ),
      ],
    );
  }
---

The setState() call marks the widget as dirty, triggering a rebuild of its subtree. Flutter’s framework efficiently diffs the widget tree and only updates the parts of the UI that changed.

Essential Built-in Widgets

Flutter provides a rich library of widgets covering every common UI pattern:

Scaffold(
  appBar: AppBar(title: const Text('My App')),
  body: Center(
    child: Text('Hello, Flutter!'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
);

Key widgets include Text, Row, Column, Container, ListView, GridView, Stack, and Scaffold. Each widget accepts a child or children parameter and optional style parameters for customization. For Material Design 3 support, Flutter includes widgets like NavigationBar, NavigationDrawer, and SearchBar that adapt to the latest Material spec.

Layout System

Layout in Flutter is widget-based and works through composition. Flutter’s layout algorithm has a single pass: each widget receives constraints from its parent and returns a size. This simple model avoids the complex layout passes of the web and produces predictable, performant layouts:

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    const Icon(Icons.star, size: 64, color: Colors.amber),
    const SizedBox(height: 16),
    Text(
      'Flutter',
      style: Theme.of(context).textTheme.headlineMedium,
    ),
  ],
)

Understanding Flutter’s layout constraints is essential. A widget receives a BoxConstraints from its parent (maxWidth, maxHeight, minWidth, minHeight), then sizes itself within those bounds. The LayoutBuilder widget gives you access to the constraints for responsive layouts.

State Management Approaches

For simple apps, setState() is sufficient. As your app grows, dedicated state management solutions help organize logic and improve testability.

Provider is Google’s recommended approach for most apps. It wraps InheritedWidget and provides dependency injection:

class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
---

Riverpod is a newer, compile-safe alternative with better testability and no BuildContext dependency. It supports code generation for providers, reducing boilerplate. Bloc uses streams and is popular in larger enterprise applications requiring strict separation of concerns with events and states.

Navigation and Routing

Flutter provides declarative routing through the Navigator 2.0 API and the go_router package:

MaterialApp(
  routes: {
    '/': (context) => const HomeScreen(),
    '/details': (context) => const DetailsScreen(),
  },
);

The go_router package supports deep linking, path parameters, redirect guards, and nested navigation — essential for production applications. It implements the Navigator 2.0 API with a simpler declarative interface:

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) => DetailsScreen(
            id: state.pathParameters['id']!,
          ),
        ),
      ],
    ),
  ],
);

Platform Channels and Native Integration

When you need platform-specific functionality that Flutter does not expose, use method channels to call native Kotlin or Swift code:

static const platform = MethodChannel('com.example/battery');

Future<void> getBatteryLevel() async {
  try {
    final result = await platform.invokeMethod('getBatteryLevel');
    print(result);
  } on PlatformException catch (e) {
    print(e.message);
  }
---

For more type-safe native communication, consider using Pigeon, Flutter’s code generation tool for platform channels. Pigeon generates Dart, Kotlin, and Swift code from a shared interface definition, eliminating the error-prone manual serialization of raw MethodChannel calls.

Testing Flutter Applications

Flutter provides a comprehensive testing framework with three levels: unit tests for isolated logic, widget tests for individual widget behavior, and integration tests for full app workflows:

// Widget test example
void main() {
  testWidgets('Counter increments on tap', (tester) async {
    await tester.pumpWidget(const Counter());
    expect(find.text('Count: 0'), findsOneWidget);
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
    expect(find.text('Count: 1'), findsOneWidget);
  });
---

The Flutter documentation emphasizes testing as a first-class concern. The flutter_test package provides matchers, finders, and pump utilities that make widget testing productive.

FAQ

Is Flutter good for production apps?

Yes. Flutter powers production apps for Google (Google Pay), BMW, eBay, Alibaba, and thousands of other companies. The framework is mature, well-documented, and receives frequent updates. Flutter 3.x introduced support for six platforms with a stable rendering engine.

How does Flutter compare to React Native?

Flutter compiles to native ARM code and renders its own widgets using Skia or Impeller, giving better performance consistency. React Native bridges JavaScript to native widgets, providing a more native look-and-feel at the cost of the bridge overhead. Flutter’s hot reload is generally faster than React Native’s fast refresh, and Flutter’s widget system provides more consistent rendering across platforms.

Does Flutter support web and desktop?

Yes. Flutter supports web (via CanvasKit or HTML renderers), macOS, Windows, and Linux. The same Dart codebase can target all platforms with minimal platform-specific adjustments. Desktop support reached stable status in Flutter 3.0, and web support continues to improve with each release.

Do I need to know native development to use Flutter?

Not necessarily for standard apps, but knowledge of Kotlin or Swift becomes necessary when you need platform-specific features that the Flutter ecosystem does not cover. The Flutter documentation provides guides for creating platform channels and custom platform-specific implementations.

What is Impeller and why is it replacing Skia?

Impeller is Flutter’s new rendering runtime. It precompiles shaders at build time, eliminating the jank caused by shader compilation at runtime. Apple deprecated OpenGL and Metal’s first-use shader compilation penalties made Impeller necessary for smooth iOS performance. Impeller is the default on iOS and is rolling out on Android.

Conclusion

Flutter is a powerful, productive framework for cross-platform development. Its widget-based architecture, hot reload, efficient rendering engine, and growing ecosystem make it an excellent choice for building beautiful, performant applications across mobile, web, and desktop. With Dart’s modern language features, comprehensive testing support, and strong community backing, Flutter represents one of the most complete cross-platform development solutions available today.

For deeper exploration, see the Flutter App Lifecycle guide and Offline Mobile Apps Guide.

Section: Mobile Development 1467 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top