Get in touch
or send us a question?
CONTACT

Flutter Counter Tutorial With BLoC Pattern part 2 – END

Countinue with part 1

Let’s take a look at CounterPage next!

Counter Page

The CounterPage widget is responsible for creating a CounterCubit (which we will look at next) 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';

/// {@template counter_page}
/// A [StatelessWidget] which is responsible for providing a
/// [CounterCubit] instance to the [CounterView].
/// {@endtemplate}
class CounterPage extends StatelessWidget {
  /// {@macro counter_page}
  const CounterPage({Key key}) : super(key: key);

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

Note: It’s important to separate or decouple the creation of a Cubit from the consumption of a Cubit in order to have code that is much more testable and reusable.

Counter Cubit

The CounterCubit class will expose two methods:

  • increment: adds 1 to the current state
  • decrement: subtracts 1 from the current state

The type of state the CounterCubit is managing is just an int and the initial state is 0.

import 'package:bloc/bloc.dart';

/// {@template counter_cubit}
/// A [Cubit] which manages an [int] as its state.
/// {@endtemplate}
class CounterCubit extends Cubit<int> {
  /// {@macro counter_cubit}
  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);
}
Copy to clipboardErrorCopied

Tip: Use the VSCode Extension or IntelliJ Plugin to create new cubits automatically.

Next, let’s take a look at the CounterView which will be responsible for consuming the state and interacting with the CounterCubit.

Counter View

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

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

import '../counter.dart';

/// {@template counter_view}
/// A [StatelessWidget] which reacts to the provided
/// [CounterCubit] state and notifies it in response to user input.
/// {@endtemplate}
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(),
          ),
        ],
      ),
    );
  }
}
Copy to clipboardErrorCopied

BlocBuilder is used to wrap the Text widget in order to update the text any time the CounterCubit state changes. In addition, context.read<CounterCubit>() is used to look-up the closest CounterCubit instance.

Note: Only the Text widget is wrapped in a BlocBuilder because that is the only widget that needs to be rebuilt in response to state changes in the CounterCubit. Avoid unnecessarily wrapping widgets that don’t need to be rebuilt when a state changes.

Barrel

Add counter.dart to export all the public facing parts of the counter feature.

export 'cubit/counter_cubit.dart';
export 'view/counter_page.dart';
Copy to clipboardErrorCopied

That’s it! We’ve separated the presentation layer from the business logic layer. The CounterView has no idea what happens when a user presses a button; it just notifies the CounterCubit. Furthermore, the CounterCubit has no idea what is happening with the state (counter value); it’s simply emitting new states in response to the methods being called.

We can run our app with flutter run and can view it on our device or simulator/emulator.

The full source (including unit and widget tests) for this example can be found here.

Thank for reading!

References form here