Home » Blog » Flutter Counter – Bloc Pattern

Flutter Counter – Bloc Pattern

In this article, we are going to build counter app using bloc pattern.

First of all create the new project called flutter_counter. Now open pubspec.yaml file and replace with below code.

name: flutter_counter
description: A new Flutter project.
version: 1.0.0+1
publish_to: none

environment:
  sdk: ">=2.12.0-0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  bloc:
    path: ../../packages/bloc
  flutter_bloc:
    path: ../../packages/flutter_bloc

dependency_overrides:
  bloc:
    path: ../../packages/bloc
  flutter_bloc:
    path: ../../packages/flutter_bloc
  bloc_test:
    path: ../../packages/bloc_test

dev_dependencies:
  flutter_test:
    sdk: flutter
  bloc_test:
    path: ../../packages/bloc_test
  mocktail: ^0.1.0
  integration_test: ^1.0.0

flutter:
  uses-material-design: true

And then install all of our dependencies by clicking on flutter packages get

Project Structure

├── lib
│   ├── app.dart
│   ├── counter
│   │   ├── counter.dart
│   │   ├── cubit
│   │   │   └── counter_cubit.dart
│   │   └── view
│   │       ├── counter_page.dart
│   │       └── counter_view.dart
│   ├── counter_observer.dart
│   └── main.dart
├── pubspec.lock
├── pubspec.yaml

The application uses a feature driven directory structure. This type of project structures enables us to scale the project by having self contained feature. This application is only have single feature but another application may have multiple complex features.

BlockObserver

To observe all state changes in the application we have to create BlocObserver ab mentioned below. Create lib/counter_observer.dart.

import 'package:bloc/bloc.dart';


class CounterObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    print('${bloc.runtimeType} $change');
  }
}

In this case, we are only overriding onChange to see all states changes occur.

main.dart

Now, replace the content on main.dart file.

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';

import 'app.dart';
import 'counter_observer.dart';

void main() {
  Bloc.observer = CounterObserver();
  runApp(const CounterApp());
}

We are simply creating the object of CounterObserver class, and calling runApp with the CounterApp widgets which we will look at the next.

Counter App

Counter App will be the MaterialApp and is specifying the home as CounterPage.

import 'package:flutter/material.dart';

import 'counter/counter.dart';


class CounterApp extends MaterialApp {

  const CounterApp({Key? key}) : super(key: key, home: const CounterPage());
}

Counter Page

The CounterPage widget is responsible for creating a CounterCubic and providing it to the CounterView.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../counter.dart';
import 'counter_view.dart';

class CounterPage extends StatelessWidget {

  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: CounterView(),
    );
  }
}

Counter Cubic

The CounterCubic class will expose two methods.

  • Increment : adds1 to the current state.
  • Decrement : subtract 1 from the current state.

This type of state the CounterCubic is managing is just an int and the initial state is 0.

import 'package:bloc/bloc.dart';


class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  /// Add 1 to the current state.
  void increment() => emit(state + 1);

  /// Subtract 1 from the current state.
  void decrement() => emit(state - 1);
}

Next, let’s see the CounterView which is responsible for consuming state and interacting with the CounterCubit.

Counter View

The CounterView is responsible for rendering the current count and rendering two FloatingButtons for increment/decrement the counter.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../counter.dart';


class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterCubit, int>(
          builder: (context, state) {
            return Text('$state', style: textTheme.headline2);
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            key: const Key('counterView_increment_floatingActionButton'),
            child: const Icon(Icons.add),
            onPressed: () => context.read<CounterCubit>().increment(),
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            key: const Key('counterView_decrement_floatingActionButton'),
            child: const Icon(Icons.remove),
            onPressed: () => context.read<CounterCubit>().decrement(),
          ),
        ],
      ),
    );
  }
}

A BlockBuilder is used to wrap the text widgets in order to update the text ant time the CounterCubit state changes.

That’s it. The Counter app is ready.

Happy Learning.

Leave a Reply

Your email address will not be published.