State Management in Flutter 2: Provider vs BLoC vs GetX

Introduction to State Management in Flutter

State management is a core architectural concept in Flutter that controls how data flows and updates the UI.

In Flutter 2, state management ensures:

  • Efficient UI updates
  • Clean separation of concerns
  • Scalable application architecture

Types of State

  1. Ephemeral State (Local State)
    • UI-related (e.g., button toggle)
  2. App State (Global State)
    • Shared across screens (e.g., user authentication)

Why State Management is Important

Without proper state management:

  • UI becomes inconsistent
  • Code becomes unmaintainable
  • Performance degrades

With proper state management:

  • UI updates are predictable
  • Code is modular and testable
  • Performance is optimized

1. Provider in Flutter

Overview

Provider is a wrapper around InheritedWidget, widely used for simple and medium-scale apps.

Features

  • Easy to learn
  • Lightweight
  • Officially recommended by Flutter team

⚡ Advantages

  • Simple syntax
  • Minimal boilerplate
  • Good for small to medium apps

❌ Limitations

  • Not ideal for complex business logic
  • Can cause unnecessary rebuilds

Want to know in detail, visit below:

Lesson 13: State Management in Flutter Using Provider | Complete Guide with Examples

2. BLoC (Business Logic Component)

Overview

BLoC uses Streams and Reactive Programming to separate UI from business logic.

Concept

  • Input → Event
  • Process → Bloc
  • Output → State

Example

Flutter BLoC Complete Example (Counter App)

Step 1: Add Dependency (yaml file)

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  flutter_bloc: ^8.1.3

Project Structure

Step 2: Define Events:

bloc/counter_event.dart

abstract class CounterEvent {}

// Increment Event
class IncrementEvent extends CounterEvent {}

// Decrement Event
class DecrementEvent extends CounterEvent {}

Step 3: Define States

bloc/counter_state.dart

class CounterState {
  final int counter;

  CounterState(this.counter);
}

Step 4: Create BLoC

bloc/counter_bloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {

  CounterBloc() : super(CounterState(0)) {

    // Increment Handler
    on<IncrementEvent>((event, emit) {
      emit(CounterState(state.counter + 1));
    });

    // Decrement Handler
    on<DecrementEvent>((event, emit) {
      emit(CounterState(state.counter - 1));
    });
  }
}

Step 5: UI Implementation

screens/home_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/counter_bloc.dart';
import '../bloc/counter_event.dart';
import '../bloc/counter_state.dart';

class HomeScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("BLoC Counter Example"),
      ),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text(
              "Counter: ${state.counter}",
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [

          FloatingActionButton(
            heroTag: "inc",
            onPressed: () {
              context.read<CounterBloc>().add(IncrementEvent());
            },
            child: Icon(Icons.add),
          ),

          SizedBox(height: 10),

          FloatingActionButton(
            heroTag: "dec",
            onPressed: () {
              context.read<CounterBloc>().add(DecrementEvent());
            },
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Step 6: Main Entry Point

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter_bloc.dart';
import 'screens/home_screen.dart';

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomeScreen(),
      ),
    );
  }
}

How It Works (Execution Flow)

  1. User taps button
  2. Event is dispatched → IncrementEvent / DecrementEvent
  3. BLoC processes event using on<Event>
  4. New state is emitted
  5. BlocBuilder rebuilds UI

Best Practices (Important)

  • Use separate files for Event, State, Bloc
  • Keep business logic inside Bloc only
  • Use BlocBuilder for UI updates
  • Use BlocListener for side effects (snackbar, navigation)
  • Avoid placing logic in UI layer

⚡ Advantages

  • Scalable architecture
  • Clear separation of concerns
  • Testable business logic

❌ Limitations

  • Verbose code
  • Steeper learning curve

3. GetX State Management

Overview

GetX is a lightweight, high-performance solution combining:

  • State management
  • Dependency injection
  • Navigation

Example

Flutter GetX Complete Example (Counter App)

Step 1: Add Dependency

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  get: ^4.6.6

Project Structure

Step 2: Create Controller

controllers/counter_controller.dart

import 'package:get/get.dart';

class CounterController extends GetxController {

  // Reactive variable
  var count = 0.obs;

  // Increment method
  void increment() {
    count++;
  }

  // Decrement method
  void decrement() {
    count--;
  }
}

Step 3: UI Implementation

screens/home_screen.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/counter_controller.dart';

class HomeScreen extends StatelessWidget {

  // Initialize controller
  final CounterController controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Counter Example"),
      ),
      body: Center(
        child: Obx(() => Text(
          "Counter: ${controller.count}",
          style: TextStyle(fontSize: 24),
        )),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [

          FloatingActionButton(
            heroTag: "inc",
            onPressed: controller.increment,
            child: Icon(Icons.add),
          ),

          SizedBox(height: 10),

          FloatingActionButton(
            heroTag: "dec",
            onPressed: controller.decrement,
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Step 4: Main Entry Point

main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'screens/home_screen.dart';

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    );
  }
}

How It Works (Execution Flow)

  1. Controller is initialized using Get.put()
  2. Reactive variable (count) is created using .obs
  3. UI listens using Obx()
  4. When count changes → UI auto updates (no manual rebuild)
Key GetX Concepts Used
Concept Description
.obs Makes variable reactive
Obx() Auto UI update on state change
Get.put() Dependency injection
GetMaterialApp Enables GetX features globally

 

Best Practices

  • Keep logic inside Controller only
  • Avoid overusing global controllers
  • Use Bindings for large apps (recommended)
  • Use GetBuilder for non-reactive performance optimization

⚡ Advantages

  • Minimal boilerplate
  • High performance
  • Reactive programming without streams

❌ Limitations

  • Less structured than BLoC
  • Overuse can lead to poor architecture

Provider vs BLoC vs GetX:

Feature Provider BLoC GetX
Complexity Low High Very Low
Boilerplate Minimal High Minimal
Performance Moderate High Very High
Scalability Medium High High
Learning Curve Easy Difficult Easy
Best Use Case Small apps Large apps Rapid development

 

Optimization Considerations

Provider Optimization

  • Use Consumer instead of rebuilding full widget tree
  • Split models to reduce rebuild scope

BLoC Optimization

  • Use flutter_bloc package for cleaner architecture
  • Dispose streams properly to avoid memory leaks

GetX Optimization

  • Use .obs only where necessary
  • Avoid global controllers when not needed

When to Use What?

✅ Use Provider when:

  • App is small or medium
  • You want simplicity

✅ Use BLoC when:

  • App is large and complex
  • Requires strict architecture

✅ Use GetX when:

  • You need fast development
  • You want minimal boilerplate

Real-Time Scenario

Imagine a Food Delivery App:

  • Provider: Manage cart state (simple)
  • BLoC: Handle order processing & API logic
  • GetX: Manage UI updates and navigation quickly

Conclusion

State management is the backbone of any Flutter app. Choosing the right approach depends on:

  • Application size
  • Complexity
  • Team expertise

👉 Final Recommendation:

  • Beginners → Provider
  • Enterprise Apps → BLoC
  • Rapid Development → GetX

 

Write A Comment