0
0
Fluttermobile~20 mins

BLoC pattern basics in Flutter - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Counter with BLoC
A simple counter app that uses the BLoC pattern to manage state. The user can increase or decrease the counter value by pressing buttons.
Target UI
---------------------
|     Counter App    |
|                   |
|       0           |
|                   |
|  [-]       [+]    |
---------------------
Use BLoC pattern to manage counter state
Display current counter value in the center
Two buttons: '+' to increment, '-' to decrement
UI updates automatically when state changes
Starter Code
Flutter
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: CounterScreen(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // TODO: Implement UI and connect to CounterBloc
    return Scaffold(
      appBar: AppBar(title: const Text('Counter App')),
      body: const Center(
        child: Text('0', style: TextStyle(fontSize: 48)),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {},
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 16),
          FloatingActionButton(
            onPressed: () {},
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

// TODO: Create CounterBloc class here
Task 1
Task 2
Task 3
Task 4
Solution
Flutter
import 'dart:async';
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: CounterScreen(),
    );
  }
}

class CounterBloc {
  int _counter = 0;

  final _counterController = StreamController<int>.broadcast();
  Stream<int> get counterStream => _counterController.stream;

  void increment() {
    _counter++;
    _counterController.sink.add(_counter);
  }

  void decrement() {
    _counter--;
    _counterController.sink.add(_counter);
  }

  void dispose() {
    _counterController.close();
  }
}

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

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  final CounterBloc _bloc = CounterBloc();

  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter App')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _bloc.counterStream,
          initialData: 0,
          builder: (context, snapshot) {
            return Text(
              '${snapshot.data}',
              style: const TextStyle(fontSize: 48),
            );
          },
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _bloc.decrement,
            tooltip: 'Decrement',
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 16),
          FloatingActionButton(
            onPressed: _bloc.increment,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

This app uses the BLoC pattern to separate business logic from UI.

The CounterBloc class manages the counter state internally and exposes a Stream<int> to notify listeners about changes.

The UI listens to this stream using StreamBuilder and rebuilds the counter text whenever the value changes.

Increment and decrement buttons call methods on the BLoC to update the counter, which then emits the new value through the stream.

This keeps the UI simple and reactive, while the BLoC handles all state management.

Final Result
Completed Screen
---------------------
|     Counter App    |
|                   |
|       0           |
|                   |
|  [-]       [+]    |
---------------------
Tapping [+] increases the number in the center by 1
Tapping [-] decreases the number in the center by 1
The number updates immediately when buttons are pressed
Stretch Goal
Add a reset button that sets the counter back to zero using the BLoC
💡 Hint
Add a new method in CounterBloc to reset the counter and add a new FloatingActionButton for reset