Simplifying State Management in Flutter with the BLoC Pattern
In the ever-evolving world of mobile application development, Flutter has emerged as a powerful cross-platform framework. As apps become more complex, handling state management efficiently becomes crucial to maintain code readability and scalability. One of the popular state management solutions in Flutter is the Business Logic Component (BLoC) pattern. In this article, we will explore what the BLoC pattern is, how it works, and its benefits in simplifying state management within Flutter apps.
The BLoC pattern, short for Business Logic Component, is a state management architecture designed to separate user interfaces from business logic in Flutter applications. By following the principles of reactive programming, the BLoC pattern enables a clear separation of concerns, making code easier to comprehend, maintain, and test.
Core Components of the BLoC Pattern
- Streams and StreamBuilders: The BLoC pattern utilizes streams, which are asynchronous event sequences, to facilitate data flow. Flutter provides StreamBuilder widgets that enable widgets to listen to streams and update the user interface accordingly when new data arrives.
- BLoC: The BLoC acts as the core component of the pattern, serving as the intermediary between data and the UI. It processes user input, applies business logic, and emits data as events through streams. The BLoC receives data from various sources, such as API calls, databases, or user interactions.
- Events: Events represent user actions or any other data sources triggering changes in the application’s state. When an event occurs, the BLoC processes it and sends updated data back to the UI.
- States: States depict the different application states at any given time. When an event is processed by the BLoC, it triggers a state change, which is then propagated to the UI.
- The Repository Provider serves as an essential component in the BLoC pattern, facilitating the management and retrieval of data from various sources. It acts as an intermediary between the BLoC and the data layer, abstracting the details of data access and providing a unified interface to the BLoC.
Here is how data flow happenes in BLoC pattern
How the Repository Provider Works in the BLoC Pattern
- Data Retrieval: When the BLoC requires data, it communicates with the Repository Provider, making a request for the desired data.
- Data Source Handling: The Repository Provider determines the appropriate data source based on the request and retrieves the data. This can involve making API calls, querying a database, or accessing local storage.
- Data Transformation: The Repository Provider processes and transforms the retrieved data if necessary, ensuring it is in the format required by the BLoC or the UI.
- Caching: The Repository Provider may cache the retrieved data to optimize future access. This can help reduce unnecessary network requests and improve app performance.
- Data Delivery: The Repository Provider delivers the processed data to the BLoC, which can then update the UI or perform additional business logic operations.
How the BLoC Pattern Works
- Event Trigger: User interactions or external data changes, such as network responses, initiate events. These events are then dispatched to the corresponding BLoC.
- BLoC Processing: Upon receiving an event, the BLoC processes it by applying the appropriate business logic and updating its internal state.
- State Change: After processing an event, the BLoC emits a new state through the stream.
- Stream Listening: UI components, typically implemented using StreamBuilder widgets, listen to the BLoC’s stream for state updates.
- UI Update: When a new state is received through the stream, the StreamBuilder automatically updates the UI based on the latest data.
Here we will see code for changing email ID on the text componenet as you type it in Email Field by streams with BLoC Pattern.
There will be 4 classes :
- BLoC Class
- Event Class
- State Class
- UI
BLoC Class
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'auth_event.dart';
import 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc() : super(AuthState()) {
on<AuthEvent>((event, emit) {
emit(AuthState(emailUsername: state.emailUsername));
});
}
}
Event Class
import 'package:flutter/cupertino.dart';
@immutable
abstract class AuthEvent {
const AuthEvent();
}
class EmailChanged extends AuthEvent {
final String emailUsername;
EmailChanged(this.emailUsername);
}
State Class
class AuthState {
late String emailUsername;
AuthState({this.emailUsername = ''});
AuthState copyWith({String? emailUserName}) {
return AuthState(emailUsername: emailUsername ?? this.emailUsername);
}
}
UI of Authentication
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth_bloc.dart';
import 'auth_event.dart';
import 'auth_state.dart';
void main() => runApp(LoginSignupApp());
class LoginSignupApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login/Signup',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => AuthBloc(),
child: LoginSignupScreen(),
),
);
}
}
class LoginSignupScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login/Signup'),
),
body: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
return Container(
padding: EdgeInsets.all(16.0),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) {
state.emailUsername = value;
BlocProvider.of<AuthBloc>(context)
.add(EmailChanged(state.emailUsername));
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (value) => ()),
SizedBox(height: 16.0),
ElevatedButton(
child: Text('Login'),
onPressed: () => (),
),
SizedBox(height: 16.0),
Text(state.emailUsername)
],
),
),
);
},
),
);
}
}
Conclusion
The BLoC pattern offers an effective solution for state management in Flutter app development. By separating business logic from UI components, this pattern enhances code clarity, maintainability, and testability. With its reactive nature, the BLoC pattern ensures smooth state transitions, improving the overall user experience.