[基礎知識]Blocによる状態管理の手順

Blocを使ったタスク管理アプリケーションのサンプルコード

1.ファイル構成と役割

pubspec.yaml

  • 役割: アプリケーションで使用するパッケージを管理するファイル。
  • 自動生成: flutter create <project_name>コマンドを実行すると生成されます。

lib/main.dart

  • 役割: アプリケーションのエントリーポイント。Blocの設定を行います。

lib/models/task.dart

  • 役割: タスクモデルを定義するファイル。

lib/bloc/task_bloc.dart

  • 役割: タスクのビジネスロジックを管理するBlocファイル。

lib/bloc/task_event.dart

  • 役割: Blocで使用するイベントを定義するファイル。

lib/bloc/task_state.dart

  • 役割: Blocで使用する状態を定義するファイル。

lib/screens/home_screen.dart

  • 役割: タスクのリストと追加ボタンを表示するホーム画面。

lib/screens/add_task_screen.dart

  • 役割: 新しいタスクを追加する画面。

lib/screens/edit_task_screen.dart

  • 役割: 既存のタスクを編集する画面。

lib/widgets/task_list.dart

  • 役割: タスクリストを表示するウィジェット。

2.サンプルコード

(1)pubspec.yaml

必要なパッケージ(flutter_bloc、equatable)を追加します。

Dart
name: task_management_app
description: A new Flutter project using Bloc for state management.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^7.0.0
  equatable: ^2.0.0
  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

(2)lib/main.dart

アプリケーションのエントリーポイントを設定します。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Task Management App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
        create: (context) => TaskBloc(),
        child: HomeScreen(),
      ),
    );
  }
}

(3)lib/models/task.dart

タスクモデルを定義します。

Dart
class Task {
  String title;
  String description;
  bool isCompleted;

  Task({
    required this.title,
    required this.description,
    this.isCompleted = false,
  });
}

(4)lib/bloc/task_event.dart

Blocで使用するイベントを定義します。

Dart
import 'package:equatable/equatable.dart';
import '../models/task.dart';

// タスクイベントの基底クラス
abstract class TaskEvent extends Equatable {
  const TaskEvent();

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

// タスク追加イベント
class AddTask extends TaskEvent {
  final Task task;

  const AddTask(this.task);

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

// タスク編集イベント
class EditTask extends TaskEvent {
  final int index;
  final Task task;

  const EditTask(this.index, this.task);

  @override
  List<Object> get props => [index, task];
}

// タスク削除イベント
class RemoveTask extends TaskEvent {
  final int index;

  const RemoveTask(this.index);

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

// タスク完了状態トグルイベント
class ToggleTaskCompletion extends TaskEvent {
  final int index;

  const ToggleTaskCompletion(this.index);

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

(5)lib/bloc/task_state.dart

Blocで使用する状態を定義します。

Dart
import 'package:equatable/equatable.dart';
import '../models/task.dart';

// タスク状態の基底クラス
abstract class TaskState extends Equatable {
  const TaskState();

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

// 初期状態
class TaskInitial extends TaskState {}

// タスクリスト状態
class TaskLoaded extends TaskState {
  final List<Task> tasks;

  const TaskLoaded(this.tasks);

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

(6)lib/bloc/task_bloc.dart

タスクのビジネスロジックを管理するBlocファイルを作成します。

Dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '../models/task.dart';
import 'task_event.dart';
import 'task_state.dart';

// タスクBlocクラス
class TaskBloc extends Bloc<TaskEvent, TaskState> {
  TaskBloc() : super(TaskInitial()) {
    on<AddTask>((event, emit) {
      if (state is TaskLoaded) {
        final updatedTasks = List<Task>.from((state as TaskLoaded).tasks)
          ..add(event.task);
        emit(TaskLoaded(updatedTasks));
      }
    });

    on<EditTask>((event, emit) {
      if (state is TaskLoaded) {
        final updatedTasks = List<Task>.from((state as TaskLoaded).tasks);
        updatedTasks[event.index] = event.task;
        emit(TaskLoaded(updatedTasks));
      }
    });

    on<RemoveTask>((event, emit) {
      if (state is TaskLoaded) {
        final updatedTasks = List<Task>.from((state as TaskLoaded).tasks)
          ..removeAt(event.index);
        emit(TaskLoaded(updatedTasks));
      }
    });

    on<ToggleTaskCompletion>((event, emit) {
      if (state is TaskLoaded) {
        final updatedTasks = List<Task>.from((state as TaskLoaded).tasks);
        final task = updatedTasks[event.index];
        updatedTasks[event.index] = Task(
          title: task.title,
          description: task.description,
          isCompleted: !task.isCompleted,
        );
        emit(TaskLoaded(updatedTasks));
      }
    });
  }
}

(7)lib/screens/home_screen.dart

ホーム画面を作成します。

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/task_bloc.dart';
import '../bloc/task_state.dart';
import '../widgets/task_list.dart';
import 'add_task_screen.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Task Management'),
      ),
      body: BlocBuilder<TaskBloc, TaskState>(
        builder: (context, state) {
          if (state is TaskInitial) {
            context.read<TaskBloc>().add(TaskEvent());
            return Center(child: CircularProgressIndicator());
          } else if (state is TaskLoaded) {
            return TaskList(tasks: state.tasks);
          }
          return Container();
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => AddTaskScreen()),
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

(8)lib/screens/add_task_screen.dart

タスク追加画面を作成します。

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/task_bloc.dart';
import '../bloc/task_event.dart';
import '../models/task.dart';

class AddTaskScreen extends StatelessWidget {
  final _titleController = TextEditingController();
  final _descriptionController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add Task'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            TextField(
              controller: _descriptionController,
              decoration: InputDecoration(labelText: 'Description'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                final title = _titleController.text;
                final description = _descriptionController.text;
                if (title.isNotEmpty && description.isNotEmpty) {
                  BlocProvider.of<TaskBloc>(context).add(
                    AddTask(Task(title: title, description: description)),
                  );
                  Navigator.pop(context);
                }
              },
              child: Text('Add Task'),
            ),
          ],
        ),
      ),
    );
  }
}

(9)lib/screens/edit_task_screen.dar

タスク編集画面を作成します。

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/task_bloc.dart';
import '../bloc/task_event.dart';
import '../models/task.dart';

class EditTaskScreen extends StatelessWidget {
  final int index;
  final Task task;

  EditTaskScreen({required this.index, required this.task});

  final _titleController = TextEditingController();
  final _descriptionController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    _titleController.text = task.title;
    _descriptionController.text = task.description;

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Task'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            TextField(
              controller: _descriptionController,
              decoration: InputDecoration(labelText: 'Description'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                final title = _titleController.text;
                final description = _descriptionController.text;
                if (title.isNotEmpty && description.isNotEmpty) {
                  BlocProvider.of<TaskBloc>(context).add(
                    EditTask(index, Task(title: title, description: description)),
                  );
                  Navigator.pop(context);
                }
              },
              child: Text('Save Changes'),
            ),
          ],
        ),
      ),
    );
  }
}

(10)lib/widgets/task_list.dart

タスクリストを表示するウィジェットを作成します。

Dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/task_bloc.dart';
import '../bloc/task_event.dart';
import '../models/task.dart';
import '../screens/edit_task_screen.dart';

class TaskList extends StatelessWidget {
  final List<Task> tasks;

  TaskList({required this.tasks});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: tasks.length,
      itemBuilder: (context, index) {
        final task = tasks[index];
        return ListTile(
          title: Text(task.title),
          subtitle: Text(task.description),
          trailing: Checkbox(
            value: task.isCompleted,
            onChanged: (value) {
              context.read<TaskBloc>().add(ToggleTaskCompletion(index));
            },
          ),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => EditTaskScreen(index: index, task: task),
              ),
            );
          },
        );
      },
    );
  }
}

コメントを残す