Skip to main content

Flutter Bloc

Flutter Bloc Examples.

Here is a detailed step-by-step tutorial on Flutter Bloc.

Introduction

Flutter Bloc is a state management library that helps developers manage the state of their Flutter applications in a predictable and efficient way. It follows the principles of the BLoC (Business Logic Component) pattern, which separates the presentation layer from the business logic and state management.

With Flutter Bloc, you can easily handle complex state transitions, make your code more testable, and improve the overall architecture of your Flutter applications.

History

Flutter Bloc was first introduced by Felix Angelov in 2018 as a state management solution for Flutter applications. It quickly gained popularity among Flutter developers due to its simplicity and powerful features. Since then, it has been widely adopted in the Flutter community and has become one of the most popular state management libraries.

Features

Here are some of the key features of Flutter Bloc:

  1. Separation of Concerns: Flutter Bloc encourages separation of concerns by separating the presentation layer from the business logic and state management. This makes it easier to understand and maintain your codebase.

  2. Predictable State Management: Flutter Bloc provides a predictable way to manage the state of your application. It ensures that state changes are handled consistently and that the UI is updated accordingly.

  3. Reactive Programming: Flutter Bloc leverages the power of reactive programming to handle state changes. It uses streams to emit and listen to state changes, making it easy to react to changes in the application's state.

  4. Event-Driven Architecture: Flutter Bloc follows an event-driven architecture, where events are dispatched to trigger state changes. This allows you to explicitly define the actions that can change the state of your application.

  5. Testability: Flutter Bloc makes it easy to write unit tests for your application's business logic. Since the business logic is separated from the UI, it can be tested independently, ensuring that your application behaves as expected.

Now, let's look at some code examples to understand how Flutter Bloc works.

Example 1: Counter App

Let's start with a simple counter app to demonstrate the basic usage of Flutter Bloc.

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

// Define the events
enum CounterEvent { increment, decrement }

// Define the state
class CounterState {
final int count;
CounterState(this.count);
}

// Define the bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0));


Stream<CounterState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.increment:
yield CounterState(state.count + 1);
break;
case CounterEvent.decrement:
yield CounterState(state.count - 1);
break;
}
}
}

// Define the UI
class CounterApp extends StatelessWidget {

Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => CounterBloc(),
child: CounterScreen(),
),
);
}
}

class CounterScreen extends StatelessWidget {

Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context);

return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'Count: ${state.count}',
style: TextStyle(fontSize: 24),
);
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
onPressed: () =>
counterBloc.add(CounterEvent.increment),
child: Icon(Icons.add),
),
SizedBox(width: 20),
FloatingActionButton(
onPressed: () =>
counterBloc.add(CounterEvent.decrement),
child: Icon(Icons.remove),
),
],
),
],
),
),
);
}
}

void main() {
runApp(CounterApp());
}

In this example, we define a CounterEvent enum to represent the possible events that can occur in our app (increment and decrement). We also define a CounterState class to hold the current count value.

Then, we define a CounterBloc class that extends Bloc and handles the state changes based on the events received. The mapEventToState method maps the events to the corresponding state changes.

In the UI, we use the BlocProvider widget to provide an instance of the CounterBloc to the CounterScreen. We use the BlocBuilder widget to listen to state changes and update the UI accordingly.

When the user taps on the increment or decrement buttons, we dispatch the corresponding events to the CounterBloc using the add method.

Example 2: Todo App

Now, let's take a look at a more complex example of a todo app using Flutter Bloc.

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

// Define the events
abstract class TodoEvent {}

class AddTodo extends TodoEvent {
final String text;
AddTodo(this.text);
}

class ToggleTodo extends TodoEvent {
final int index;
ToggleTodo(this.index);
}

class ClearCompleted extends TodoEvent {}

// Define the state
class TodoState {
final List<String> todos;
final List<bool> completed;
TodoState(this.todos, this.completed);
}

// Define the bloc
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc() : super(TodoState([], []));


Stream<TodoState> mapEventToState(TodoEvent event) async* {
if (event is AddTodo) {
yield TodoState(
[...state.todos, event.text],
[...state.completed, false],
);
} else if (event is ToggleTodo) {
final completed = List<bool>.from(state.completed);
completed[event.index] = !completed[event.index];
yield TodoState(state.todos, completed);
} else if (event is ClearCompleted) {
final todos = state.todos.whereIndexed((index, _) => !state.completed[index]).toList();
final completed = state.completed.where((completed) => !completed).toList();
yield TodoState(todos, completed);
}
}
}

extension IterableExtension<E> on Iterable<E> {
Iterable<T> whereIndexed<T>(bool Function(int index, E element) test) {
var index = 0;
return where((element) => test(index++, element));
}
}

// Define the UI
class TodoApp extends StatelessWidget {

Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Todo App')),
body: BlocProvider(
create: (_) => TodoBloc(),
child: TodoScreen(),
),
),
);
}
}

class TodoScreen extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();


Widget build(BuildContext context) {
final todoBloc = BlocProvider.of<TodoBloc>(context);

return Column(
children: [
Row(
children: [
Expanded(
child: TextField(
controller: _textEditingController,
decoration: InputDecoration(
hintText: 'Enter a todo',
),
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
final text = _textEditingController.text;
if (text.isNotEmpty) {
todoBloc.add(AddTodo(text));
_textEditingController.clear();
}
},
),
],
),
BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) {
return Expanded(
child: ListView.builder(
itemCount: state.todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(state.todos[index]),
leading: Checkbox(
value: state.completed[index],
onChanged: (_) => todoBloc.add(ToggleTodo(index)),
),
);
},
),
);
},
),
TextButton(
onPressed: () => todoBloc.add(ClearCompleted()),
child: Text('Clear Completed'),
),
],
);
}
}

void main() {
runApp(TodoApp());
}

In this example, we define three different events: AddTodo, ToggleTodo, and ClearCompleted. The AddTodo event is dispatched when a new todo is added, the ToggleTodo event is dispatched when a todo is toggled (completed or not), and the ClearCompleted event is dispatched to clear completed todos.

We also define a TodoState class to hold the list of todos and their completed status.

The TodoBloc handles the state changes based on the events received. For example, when an AddTodo event is dispatched, a new TodoState is emitted with the updated list of todos.

In the UI, we use the BlocProvider widget to provide an instance of the TodoBloc to the TodoScreen. We use the BlocBuilder widget to listen to state changes and update the UI accordingly.

When the user enters a new todo and taps on the add button, we dispatch the AddTodo event to the TodoBloc with the entered text. Similarly, when the user toggles a todo or taps on the clear completed button, we dispatch the corresponding events to the TodoBloc.

Conclusion

This tutorial provided an introduction to Flutter Bloc, its history, features, and examples. We covered the basic usage of Flutter Bloc with a counter app and a more complex todo app. Flutter Bloc is a powerful state management library that can greatly simplify the process of managing the state of Flutter applications. It is highly recommended for developers looking for an efficient and scalable state management solution for their Flutter projects.

For more information, you can refer to the official Flutter Bloc documentation here.