Skip to content

Your First Flutter App — The Counter

When you run flutter create my_app, the generated app is a counter — the same “Hello World” that React tutorials use to introduce useState. The logic is identical; the syntax is different. This lesson walks through both side by side so you see exactly where your React knowledge maps.

React
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div style={{ textAlign: 'center', marginTop: 60 }}>
<h2>Count: {count}</h2>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
Flutter
// Flutter counter — the default generated app
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: CounterPage());
}
}
// StatefulWidget = component with useState
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0; // ≈ const [count, setCount] = useState(0)
void _increment() {
setState(() { // ≈ setCount(c => c + 1)
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Count: $_count',
style: const TextStyle(fontSize: 28)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
),
),
);
}
}

The app below is the default Flutter counter with minor polish. Run it, press the button, and watch the count update — exactly like the React version.

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: 'Flutter Counter',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const CounterPage(title: 'Flutter Counter'),
    );
  }
}

class CounterPage extends StatefulWidget {
  final String title;
  const CounterPage({super.key, required this.title});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.displayMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

setState works fine for local UI state — exactly like useState. But when state needs to be shared across widgets, Flutter developers reach for Riverpod (or Provider/Bloc) — the same reason React developers move from useState to Zustand or Redux for shared state. The State & Lifecycle module covers this in depth.

A Flutter StatefulWidget is split into two classes. What does each hold?
What is the Flutter equivalent of calling a React state setter (`setCount`)?
What does the `Scaffold` widget provide?
In Dart, what does a leading underscore on a field name (e.g. `_counter`) signify?