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.