All topics
Mobile · Learning hub

Flutter notes for developers

Master Flutter with a curated set of 4 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Mobile notes
Flutter

Dart Basics & Flutter Setup

Flutter: Dart Basics & Setup Flutter is Google's cross-platform UI toolkit for iOS, Android, Web, Desktop from a single Dart codebase. Dart is a strongly-typed,

Flutter: Dart Basics & Setup

Flutter is Google's cross-platform UI toolkit for iOS, Android, Web, Desktop from a single Dart codebase. Dart is a strongly-typed, null-safe language with async/await. Flutter renders its own widgets via Skia/Impeller — no platform UI components.

Dart Language Essentials

// Variables & types
var name = 'Alice';           // type inferred
String greeting = 'Hello';
int count = 42;
double pi = 3.14;
bool isActive = true;

// Null safety — all types non-nullable by default
String? nullable = null;      // ? makes it nullable
String nonNull = nullable!;   // ! force unwrap (throws if null)
String safe = nullable ?? 'default';  // null coalescing
int? length = nullable?.length;       // null-aware access

// final and const
final name2 = 'Alice';        // runtime constant (set once)
const pi2 = 3.14159;          // compile-time constant

// Collections
var list = [1, 2, 3];
var map = {'name': 'Alice', 'age': 30};
var set = {1, 2, 3};

// String interpolation
var msg = 'Hello, $name! You are ${30 + 1} years old.';

// Functions
int add(int a, int b) => a + b;              // arrow function
void greet({required String name}) { }       // named parameter

// Async/await
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  return 'data';
}

// Pattern matching (Dart 3)
var result = switch (status) {
  'active' => 'Active',
  'inactive' => 'Inactive',
  _ => 'Unknown',
};

Classes & Mixins

// Class
class Animal {
  final String name;
  int _age;                     // private (convention: _ prefix)

  Animal(this.name, this._age); // shorthand constructor

  int get age => _age;          // getter
  set age(int value) {          // setter
    if (value > 0) _age = value;
  }

  String describe() => '$name, age $_age';

  @override
  String toString() => describe();
}

// Named constructor
class Color {
  final int r, g, b;
  const Color(this.r, this.g, this.b);
  const Color.red() : this(255, 0, 0);    // named constructor
  const Color.fromHex(String hex) :
    r = int.parse(hex.substring(1, 3), radix: 16),
    g = int.parse(hex.substring(3, 5), radix: 16),
    b = int.parse(hex.substring(5, 7), radix: 16);
}

// Mixin
mixin Flyable {
  void fly() => print('Flying!');
}

class Bird extends Animal with Flyable {
  Bird(super.name, super.age);
}

// Record (Dart 3)
var point = (x: 3.0, y: 4.0);
print(point.x);  // 3.0

Flutter Setup & Project Structure

# Install Flutter
# https://flutter.dev/docs/get-started/install

flutter --version
flutter doctor           # check environment setup
flutter create my_app    # create new project
cd my_app
flutter run              # run on connected device/emulator
flutter run -d chrome    # run as web app
flutter build apk        # build Android APK
flutter build ipa        # build iOS (macOS required)
flutter build web        # build web app
flutter test             # run tests
flutter pub get          # install dependencies
flutter pub add http     # add package
Project structure:
  lib/
    main.dart            — entry point (runApp)
    app.dart             — MaterialApp / CupertinoApp
    features/            — feature-based organization
      auth/
        screens/
        widgets/
        providers/
    core/
      theme/
      services/
      models/
  test/                  — widget + unit tests
  assets/                — images, fonts, data files
  pubspec.yaml           — dependencies and assets config
Flutter

Widgets: Stateless, Stateful & Layout

Flutter: Widgets & Layout Everything in Flutter is a widget. The widget tree is rebuilt efficiently by Flutter's rendering engine. StatelessWidget for static UI

Flutter: Widgets & Layout

Everything in Flutter is a widget. The widget tree is rebuilt efficiently by Flutter's rendering engine. StatelessWidget for static UI; StatefulWidget for UI that changes over time.

StatelessWidget & StatefulWidget

// StatelessWidget — no mutable state
class GreetingCard extends StatelessWidget {
  final String name;
  final VoidCallback onTap;

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Text('Hello, $name!',
            style: Theme.of(context).textTheme.headlineSmall),
        ),
      ),
    );
  }
}

// StatefulWidget — mutable state with setState
class Counter extends StatefulWidget {
  const Counter({super.key});

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

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

  void _increment() {
    setState(() { _count++; });  // triggers rebuild
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$_count', style: const TextStyle(fontSize: 48)),
        ElevatedButton(onPressed: _increment, child: const Text('+')),
      ],
    );
  }

  @override
  void initState() {
    super.initState();
    // called once when widget is inserted into tree
  }

  @override
  void dispose() {
    // cleanup: cancel timers, close streams
    super.dispose();
  }
}

Layout Widgets

// Column and Row — flex containers
Column(
  mainAxisAlignment: MainAxisAlignment.center,   // vertical axis
  crossAxisAlignment: CrossAxisAlignment.start,  // horizontal axis
  children: [
    Text('Title'),
    const SizedBox(height: 8),                   // spacer
    Text('Subtitle'),
  ],
)

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: [
    Text('Left'),
    Expanded(child: Text('Takes remaining space')),
    Text('Right'),
  ],
)

// Stack — overlapping widgets
Stack(
  alignment: Alignment.bottomCenter,
  children: [
    Image.network(imageUrl),
    Container(
      color: Colors.black54,
      padding: const EdgeInsets.all(8),
      child: const Text('Caption', style: TextStyle(color: Colors.white)),
    ),
  ],
)

// ListView — scrollable list
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(
    leading: const Icon(Icons.person),
    title: Text(items[index].name),
    subtitle: Text(items[index].email),
    trailing: const Icon(Icons.chevron_right),
    onTap: () => Navigator.pushNamed(context, '/detail', arguments: items[index]),
  ),
)

// GridView
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 1.5,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

Common Widgets

// Scaffold — basic screen structure
Scaffold(
  appBar: AppBar(
    title: const Text('Home'),
    actions: [IconButton(icon: const Icon(Icons.search), onPressed: () {})],
  ),
  body: const Center(child: Text('Content')),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
  bottomNavigationBar: BottomNavigationBar(
    items: const [
      BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
      BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
    ],
    currentIndex: _selectedIndex,
    onTap: (index) => setState(() => _selectedIndex = index),
  ),
)

// Input
TextField(
  controller: _emailController,
  keyboardType: TextInputType.emailAddress,
  decoration: const InputDecoration(
    labelText: 'Email',
    hintText: 'alice@example.com',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(),
  ),
  validator: (v) => v?.contains('@') == true ? null : 'Invalid email',
)

// Async image loading
Image.network(
  imageUrl,
  loadingBuilder: (context, child, progress) =>
    progress == null ? child : const CircularProgressIndicator(),
  errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
)
Flutter

State Management: Provider, Riverpod & Bloc

Flutter: State Management Provider (simple, official recommendation for beginners) // pubspec.yaml: provider: ^6.1.0 // ChangeNotifier model class CartModel ext

Flutter: State Management

Provider (simple, official recommendation for beginners)

// pubspec.yaml: provider: ^6.1.0

// ChangeNotifier model
class CartModel extends ChangeNotifier {
  final List<Product> _items = [];
  List<Product> get items => List.unmodifiable(_items);
  int get count => _items.length;

  void add(Product product) {
    _items.add(product);
    notifyListeners();           // rebuilds all listening widgets
  }

  void remove(Product product) {
    _items.remove(product);
    notifyListeners();
  }
}

// Provide at top of tree
void main() => runApp(
  ChangeNotifierProvider(
    create: (_) => CartModel(),
    child: const MyApp(),
  ),
);

// Multiple providers
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => CartModel()),
    ChangeNotifierProvider(create: (_) => AuthModel()),
  ],
  child: const MyApp(),
)

// Consume
class CartIcon extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // watch — rebuilds when model changes
    final count = context.watch<CartModel>().count;
    // read — get value without subscribing (in callbacks)
    // context.read<CartModel>().add(product);
    return Badge(label: Text('$count'), child: const Icon(Icons.shopping_cart));
  }
}

// Consumer — fine-grained rebuild scope
Consumer<CartModel>(
  builder: (context, cart, child) => Text('Items: ${cart.count}'),
)

Riverpod (modern, recommended)

// pubspec.yaml: flutter_riverpod: ^2.5.0, riverpod_annotation: ^2.3.0

// Provider definitions (global, not in widget tree)
final cartProvider = NotifierProvider<CartNotifier, List<Product>>(() => CartNotifier());

class CartNotifier extends Notifier<List<Product>> {
  @override
  List<Product> build() => [];

  void add(Product p) => state = [...state, p];
  void remove(Product p) => state = state.where((x) => x != p).toList();
}

// Async provider (auto-fetches and caches)
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
  return ref.watch(apiServiceProvider).getUser(userId);
});

// Widget — extends ConsumerWidget (like StatelessWidget)
class CartScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final items = ref.watch(cartProvider);
    return ListView(
      children: items.map((p) => ProductTile(product: p)).toList(),
    );
  }
}

// Async provider with loading/error states
class UserProfile extends ConsumerWidget {
  final String userId;
  const UserProfile(this.userId);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncUser = ref.watch(userProvider(userId));
    return asyncUser.when(
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
      data: (user) => Text(user.name),
    );
  }
}

Bloc / Cubit

// pubspec.yaml: flutter_bloc: ^8.1.0

// Cubit (simpler Bloc — no events, just methods)
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

// Full Bloc (events + states — for complex flows)
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email, password;
  LoginRequested(this.email, this.password);
}
class LogoutRequested extends AuthEvent {}

abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState { final User user; AuthAuthenticated(this.user); }
class AuthFailure extends AuthState { final String message; AuthFailure(this.message); }

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthService _authService;

  AuthBloc(this._authService) : super(AuthInitial()) {
    on<LoginRequested>((event, emit) async {
      emit(AuthLoading());
      try {
        final user = await _authService.login(event.email, event.password);
        emit(AuthAuthenticated(user));
      } catch (e) {
        emit(AuthFailure(e.toString()));
      }
    });
    on<LogoutRequested>((event, emit) => emit(AuthInitial()));
  }
}

// Provide and consume
BlocProvider(
  create: (_) => CounterCubit(),
  child: BlocBuilder<CounterCubit, int>(
    builder: (context, count) => Text('$count'),
  ),
)

Keep your Flutter knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever