Back to Rules
Flutter88% popularity

Flutter BLoC Pattern Implementation

Implement BLoC for predictable state management with clear event-state separation.

Felix AngelovUpdated Feb 18, 2024

Overview

The BLoC (Business Logic Component) pattern provides a structured approach to state management that separates business logic from UI completely. Using streams for state emission and events for user actions creates a unidirectional data flow that makes applications predictable and testable. BLoC integrates deeply with Flutter's widget system through BlocWidget and BlocBuilder. BlocBuilder rebuilds widgets in response to state changes, while BlocListener handles side effects like navigation and snackbars. BlocConsumer combines both capabilities, reducing boilerplate when you need both rebuilding and side effect handling. Event classes represent user intentions or system triggers. They should be small, focused, and serializable for debugging. Using freezed or built_value for immutable event and state classes provides excellent equality semantics and reduces bugs from mutable state. The mapEventToState method transforms events into states through async generators, enabling complex async operations with proper loading and error states. For operations that should be cancelable, the transformEvents method can filter or debounce rapid events. This is particularly valuable for search inputs or auto-save functionality.

Code Example

.flutterrules
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart';
import '../models/product.dart';
import '../repositories/product_repository.dart';

part 'product_event.dart';
part 'product_state.dart';

class ProductBloc extends Bloc<ProductEvent, ProductState> {
  final ProductRepository repository;

  ProductBloc({required this.repository}) : super(ProductInitial()) {
    on<LoadProducts>(_onLoadProducts);
    on<LoadProductDetail>(_onLoadProductDetail);
    on<SearchProducts>(_onSearchProducts);
    on<ClearSearch>(_onClearSearch);
  }

  Future<void> _onLoadProducts(
    LoadProducts event,
    Emitter<ProductState> emit,
  ) async {
    emit(ProductLoading());

    try {
      final products = await repository.getProducts(
        page: event.page,
        category: event.category,
      );

      if (state is ProductLoaded && event.page > 1) {
        final currentProducts = (state as ProductLoaded).products;
        emit(ProductLoaded(
          products: [...currentProducts, ...products],
          hasReachedMax: products.isEmpty,
        ));
      } else {
        emit(ProductLoaded(
          products: products,
          hasReachedMax: products.isEmpty,
        ));
      }
    } catch (e) {
      emit(ProductError(message: e.toString()));
    }
  }

  Future<void> _onSearchProducts(
    SearchProducts event,
    Emitter<ProductState> emit,
  ) async {
    if (event.query.isEmpty) {
      add(LoadProducts());
      return;
    }

    emit(ProductLoading());

    try {
      final results = await repository.searchProducts(event.query);
      emit(ProductSearchResults(
        query: event.query,
        results: results,
      ));
    } catch (e) {
      emit(ProductError(message: 'Search failed: ' + e.toString()));
    }
  }
}

// Event definitions
abstract class ProductEvent extends Equatable {
  const ProductEvent();

  @override
  List<Object?> get props => [];
}

class LoadProducts extends ProductEvent {
  final int page;
  final String? category;

  const LoadProducts({this.page = 1, this.category});

  @override
  List<Object?> get props => [page, category];
}

class LoadProductDetail extends ProductEvent {
  final String productId;

  const LoadProductDetail(this.productId);

  @override
  List<Object?> get props => [productId];
}

class SearchProducts extends ProductEvent {
  final String query;

  const SearchProducts(this.query);

  @override
  List<Object?> get props => [query];
}

// State definitions
abstract class ProductState extends Equatable {
  const ProductState();

  @override
  List<Object?> get props => [];
}

class ProductInitial extends ProductState {}

class ProductLoading extends ProductState {}

class ProductLoaded extends ProductState {
  final List<Product> products;
  final bool hasReachedMax;

  const ProductLoaded({
    required this.products,
    this.hasReachedMax = false,
  });

  ProductLoaded copyWith({
    List<Product>? products,
    bool? hasReachedMax,
  }) {
    return ProductLoaded(
      products: products ?? this.products,
      hasReachedMax: hasReachedMax ?? this.hasReachedMax,
    );
  }

  @override
  List<Object?> get props => [products, hasReachedMax];
}

class ProductSearchResults extends ProductState {
  final String query;
  final List<Product> results;

  const ProductSearchResults({
    required this.query,
    required this.results,
  });

  @override
  List<Object?> get props => [query, results];
}

class ProductError extends ProductState {
  final String message;

  const ProductError({required this.message});

  @override
  List<Object?> get props => [message];
}

More Flutter Rules

FLUTTER
92%

Flutter State Management with Riverpod

Use Riverpod for reactive state management with compile-time safety and testable provider architecture.

state-managementriverpodarchitecture
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../models/todo.dart';
im...
Mar 10, 2024by Remi Rousselet
View Rule
FLUTTER
90%

Flutter Clean Architecture Layers

Structure Flutter applications with domain, data, and presentation layers for maintainability.

flutterclean-architecturedomain-driven
// Domain Layer - Entities
class Article {
  final String id;
  final String title;
  final String content;
  final String authorId;
  final DateTime ...
Jan 20, 2024by Reso Coder
View Rule
FLUTTER
87%

Flutter Navigation with GoRouter

Implement declarative routing with GoRouter for deep linking and nested navigation.

fluttergorouternavigation
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '....
Feb 22, 2024by Chris Cox
View Rule