Macam-Macam Metode Penulisan State Management Bloc pada Flutter

kholil 12 August 2025 Flutter 14 views 0 comment 4 minutes
Gambar Artikel

Bloc adalah library populer untuk state management di Flutter yang memisahkan logika bisnis (business logic) dari antarmuka pengguna (UI). Library ini menyediakan dua metode utama untuk mengelola state: Bloc (yang menggunakan events untuk memicu perubahan state) dan Cubit (versi yang lebih sederhana tanpa events, langsung memanggil fungsi). Kedua metode ini berbasis stream, di mana state diekspos sebagai output yang bisa diamati oleh UI.

Dalam artikel ini, kita akan fokus pada penulisan kelas Bloc/Cubit dan penggunaannya di view (UI Flutter). Sebagai contoh kasus, kita gunakan aplikasi Todo List sederhana, di mana state melibatkan daftar todo item (dengan properti seperti id, deskripsi, dan status selesai).

Catatan: Asumsikan package flutter_bloc sudah ditambahkan ke pubspec.yaml dan diimpor di file terkait.

Metode 1: Menggunakan Bloc dengan Events

Metode ini cocok untuk aplikasi kompleks di mana perubahan state perlu dilacak melalui events (seperti aksi pengguna). Kita definisikan: - Events: Kelas-kelas yang merepresentasikan aksi (misalnya, tambah todo, toggle selesai). - States: Kelas yang menyimpan data state (misalnya, list todo, status loading). - Bloc: Kelas yang menghandle events dan emit state baru.

Penulisan Bloc

Definisikan events, states, dan bloc di folder terpisah (misalnya, todos_bloc.dart).

Events

sealed class TodoEvent {}
final class AddTodo extends TodoEvent {
  final String description;
  AddTodo(this.description);
}
final class ToggleTodo extends TodoEvent {
  final String id;
  ToggleTodo(this.id);
}
final class DeleteTodo extends TodoEvent {
  final String id;
  DeleteTodo(this.id);
}

States

class TodoState {
  final List<Todo> todos;
  final bool isLoading;
  TodoState({this.todos = const [], this.isLoading = false});

  TodoState copyWith({List<Todo>? todos, bool? isLoading}) {
    return TodoState(
      todos: todos ?? this.todos,
      isLoading: isLoading ?? this.isLoading,
    );
  }
}

class Todo {
  final String id;
  final String description;
  final bool isCompleted;
  Todo({required this.id, required this.description, this.isCompleted = false});
}

Bloc

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  TodoBloc() : super(TodoState()) {
    on<AddTodo>(_onAddTodo);
    on<ToggleTodo>(_onToggleTodo);
    on<DeleteTodo>(_onDeleteTodo);
  }

  void _onAddTodo(AddTodo event, Emitter<TodoState> emit) {
    final newTodo = Todo(id: DateTime.now().toString(), description: event.description);
    final updatedTodos = List<Todo>.from(state.todos)..add(newTodo);
    emit(state.copyWith(todos: updatedTodos));
  }

  void _onToggleTodo(ToggleTodo event, Emitter<TodoState> emit) {
    final updatedTodos = state.todos.map((todo) {
      if (todo.id == event.id) {
        return Todo(id: todo.id, description: todo.description, isCompleted: !todo.isCompleted);
      }
      return todo;
    }).toList();
    emit(state.copyWith(todos: updatedTodos));
  }

  void _onDeleteTodo(DeleteTodo event, Emitter<TodoState> emit) {
    final updatedTodos = state.todos.where((todo) => todo.id != event.id).toList();
    emit(state.copyWith(todos: updatedTodos));
  }
}

Penggunaan di View

Gunakan widget dari flutter_bloc seperti BlocProvider (untuk provide bloc ke subtree), BlocBuilder (untuk rebuild UI berdasarkan state), dan context.read<Bloc>() untuk add event.

Provide Bloc di Level App

Di main.dart:

void main() {
  runApp(
    BlocProvider(
      create: (context) => TodoBloc(),
      child: MyApp(),
    ),
  );
}

Gunakan di View

Di TodoListScreen.dart:

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Todo List')),
      body: BlocBuilder<TodoBloc, TodoState>(
        builder: (context, state) {
          if (state.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          return ListView.builder(
            itemCount: state.todos.length,
            itemBuilder: (context, index) {
              final todo = state.todos[index];
              return ListTile(
                title: Text(todo.description),
                leading: Checkbox(
                  value: todo.isCompleted,
                  onChanged: (_) => context.read<TodoBloc>().add(ToggleTodo(todo.id)),
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => context.read<TodoBloc>().add(DeleteTodo(todo.id)),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Misalnya, tampilkan dialog untuk input description, lalu add event
          context.read<TodoBloc>().add(AddTodo('New Todo'));
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Metode 2: Menggunakan Cubit (Tanpa Events)

Metode ini lebih sederhana untuk kasus di mana tidak perlu tracking events secara detail. Kita hanya definisikan state dan fungsi langsung di Cubit untuk emit state baru.

Penulisan Cubit

Definisikan state dan cubit di file terpisah (misalnya, todos_cubit.dart).

States

// Sama seperti di atas: TodoState dan Todo class

Cubit

class TodoCubit extends Cubit<TodoState> {
  TodoCubit() : super(TodoState());

  void addTodo(String description) {
    final newTodo = Todo(id: DateTime.now().toString(), description: description);
    final updatedTodos = List<Todo>.from(state.todos)..add(newTodo);
    emit(state.copyWith(todos: updatedTodos));
  }

  void toggleTodo(String id) {
    final updatedTodos = state.todos.map((todo) {
      if (todo.id == id) {
        return Todo(id: todo.id, description: todo.description, isCompleted: !todo.isCompleted);
      }
      return todo;
    }).toList();
    emit(state.copyWith(todos: updatedTodos));
  }

  void deleteTodo(String id) {
    final updatedTodos = state.todos.where((todo) => todo.id != id).toList();
    emit(state.copyWith(todos: updatedTodos));
  }
}

Penggunaan di View

Sama seperti Bloc, tapi panggil fungsi langsung alih-alih add event.

Provide Cubit di Level App

Di main.dart:

void main() {
  runApp(
    BlocProvider(
      create: (context) => TodoCubit(),
      child: MyApp(),
    ),
  );
}

Gunakan di View

Di TodoListScreen.dart:

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Todo List')),
      body: BlocBuilder<TodoCubit, TodoState>(
        builder: (context, state) {
          if (state.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          return ListView.builder(
            itemCount: state.todos.length,
            itemBuilder: (context, index) {
              final todo = state.todos[index];
              return ListTile(
                title: Text(todo.description),
                leading: Checkbox(
                  value: todo.isCompleted,
                  onChanged: (_) => context.read<TodoCubit>().toggleTodo(todo.id),
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => context.read<TodoCubit>().deleteTodo(todo.id),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Misalnya, tampilkan dialog untuk input, lalu panggil fungsi
          context.read<TodoCubit>().addTodo('New Todo');
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Kesimpulan

Bloc dengan events lebih cocok untuk traceability dan kompleksitas tinggi, sementara Cubit ideal untuk simplicity. Kedua metode menggunakan widget seperti BlocProvider dan BlocBuilder untuk integrasi dengan UI. Untuk fitur lanjutan seperti persistensi, gunakan HydratedBloc, tapi itu di luar fokus artikel ini.

Komentar (0)

Tinggalkan Komentar