Last active
July 9, 2023 15:34
-
-
Save DaisukeNagata/f3ec8cfd96f7d9c33ed36c67d452dc34 to your computer and use it in GitHub Desktop.
Business Logic Component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
/* | |
BLoC(Business Logic Component)パターンは、状態管理パターンの一種であり、 | |
アプリケーション内の状態管理とビジネスロジックを分離することを目的としています。 | |
BLoCパターンのメリットやデメリットについて説明します。 | |
メリット: | |
分離された責務: BLoCパターンは、ビジネスロジックと状態管理を分離するため、アプリケーションのコードをより構造化し、保守性を向上させます。 | |
再利用性: BLoCは単一の責務を持ち、ビジネスロジックをカプセル化しています。そのため、他の部分で再利用することが容易です。 | |
テスト容易性: BLoCはテストしやすく、ユニットテストや結合テストを行いやすくなります。状態とイベントのフローを切り離し、ビジネスロジックの振る舞いをテストすることができます。 | |
反応性: BLoCはストリームを使用して状態の変更を通知します。これにより、状態の変更に応じてウィジェットが自動的に更新されるため、リアクティブなUIを構築するのに適しています。 | |
デメリット: | |
学習曲線: BLoCパターンは初めての開発者にとって学習が必要な場合があります。ストリーム、イベント、状態の概念を理解する必要があります。 | |
ボイラープレートコード: BLoCパターンでは、BLoCやイベント、状態に関連するコードが増える傾向があります。これにより、冗長なコードやボイラープレートコードが発生する可能性があります。 | |
オーバーエンジニアリング: 小規模なアプリケーションや単純な状態管理に対してBLoCパターンを使用すると、過度な抽象化や複雑性が発生する可能性があります。適切な場所と適切な規模で使用することが重要です。 | |
BLoCパターンは、大規模で複雑なアプリケーションや状態管理が重要な場合に特に有用です。ただし、アプリケーションの要件や規模に応じて、他の状態管理パターンも検討する価値があります。 | |
*/ | |
/* | |
CounterEventとIncrementEventは、状態管理パターンであるBLoC(Business Logic Component)パターンにおいて、 | |
アプリケーション内で発生するイベントを表現するためのクラスです。 | |
CounterEventは、カウンターに関連するイベントを一般的に表すために使用される抽象クラスです。 | |
イベントはアプリケーション内で何かが起こったことを示すものであり、カウンターの例では増加や減少などのイベントが考えられます。 | |
IncrementEventは、具体的なカウンターの増加イベントを表すクラスです。 | |
カウンターが増加するというアクションが発生した際にこのイベントを使用します。 | |
これらのイベントクラスを使用することで、BLoC内でイベントを受け取り、それに基づいて状態を変更するための処理を実行することができます。 | |
具体的には、CounterBloc内のmapEventToStateメソッドでイベントを受け取り、対応する状態への変更を行います。 | |
例えば、IncrementEventが発生した場合、mapEventToStateメソッド内でカウンターの値を増加させるなどの処理を行うことができます。 | |
状態管理パターンを使用することで、イベントと状態を明確に分離し、アプリケーションの状態変化を一元管理することができます。これにより、複雑な状態の変更をシンプルかつ予測可能な方法で処理できます。 | |
*/ | |
abstract class CounterEvent {} | |
class IncrementEvent extends CounterEvent {} | |
// 状態 | |
class CounterState { | |
int counterValue; | |
CounterState({required this.counterValue}); | |
} | |
void main() => runApp(const CounterApp()); | |
class CounterApp extends StatelessWidget { | |
const CounterApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: BlocProvider<CounterBloc>( | |
bloc: CounterBloc(), | |
child: const CounterPage(), | |
), | |
); | |
} | |
} | |
/* | |
CounterPageはStatelessWidgetを継承しており、ビルドメソッド内でウィジェットのビルドを行っています。 | |
CounterBlocのインスタンスを取得するために、BlocProvider.of<CounterBloc>(context)を使用しています。 | |
これにより、ウィジェットツリー内の最も近いBlocProviderウィジェットからCounterBlocのインスタンスを取得できます。 | |
bodyの部分では、StreamBuilderを使用してカウンターの状態を監視しています。 | |
streamにはcounterBloc.stateStreamを指定し、状態の変化を受け取ります。initialDataには初期の状態を指定しています。 | |
builderコールバック関数内では、状態のスナップショットを取得し、表示を行っています。snapshot.hasDataを使用して、 | |
データが存在するかどうかを確認し、それに応じて表示内容を変更しています。カウンターの値は、snapshot.data!.counterValueで取得し、テキストとして表示しています。 | |
floatingActionButtonでは、加算ボタンを提供しており、押下時にcounterBloc.eventSink.add(IncrementEvent())を呼び出して、カウンターの値を増やすイベントを発行しています。 | |
以上のコードを使用することで、CounterPageウィジェットはCounterBlocを使用して状態管理を行い、カウンターの値を表示・変更することができます。 | |
*/ | |
class CounterPage extends StatelessWidget { | |
const CounterPage({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context); | |
return Scaffold( | |
appBar: AppBar(title: const Text('Counter')), | |
body: StreamBuilder<CounterState>( | |
stream: counterBloc.stateStream, | |
initialData: CounterState(counterValue: 0), | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return Center( | |
child: Text('You hit me: ${snapshot.data!.counterValue} times'), | |
); | |
} else { | |
return const Center(child: Text('Loading...')); | |
} | |
}, | |
), | |
floatingActionButton: FloatingActionButton( | |
child: const Icon(Icons.add), | |
onPressed: () { | |
counterBloc.eventSink.add(IncrementEvent()); | |
}, | |
), | |
); | |
} | |
} | |
/* | |
提供されたコードは、BlocProviderという状態管理用のウィジェットで、 | |
InheritedWidgetを継承しています。BlocProviderはジェネリック型のTを受け取り、CounterBlocのサブクラスを提供します。 | |
BlocProviderの役割は、ウィジェットツリー内でCounterBlocのインスタンスを提供し、 | |
そのインスタンスを他のウィジェットで利用できるようにすることです。ofメソッドを使用することで、 | |
ウィジェットツリー内の特定の型のCounterBlocインスタンスを取得できます。 | |
修正されたコードでは、以下のような構造になっています。 | |
*/ | |
/* | |
InheritedWidgetは、Flutterフレームワークで状態を共有するための機能です。InheritedWidgetは、 | |
ウィジェットツリー内で共有されるデータを持つウィジェットです。 | |
これにより、子ウィジェットが親ウィジェットの状態にアクセスできるようになります。 | |
InheritedWidgetは、以下のような特徴を持っています: | |
状態の共有:InheritedWidgetは、状態を持つウィジェットです。その状態は、ウィジェットツリー内で共有され、子ウィジェットに継承されます。 | |
これにより、状態を共有して、アプリケーション内の異なる部分で利用することができます。 | |
自動的な更新:InheritedWidget内の状態が変更されると、自動的に関連する子ウィジェットが再ビルドされます。 | |
つまり、状態が変更された場合、その変更は自動的に子ウィジェットに反映されます。 | |
コンテキストを介したアクセス:子ウィジェットは、BuildContextを介してInheritedWidget内の状態にアクセスできます。 | |
これにより、子ウィジェットは状態に応じて異なる動作や表示を行うことができます。 | |
InheritedWidgetの使用例としては、テーマデータやロケール情報の共有、ログイン状態の管理などがあります。 | |
これらの情報をInheritedWidgetで共有することで、アプリケーション内のさまざまな部分で利用できるようになります。 | |
ただし、InheritedWidgetを使用する場合、パフォーマンスに配慮する必要があります。 | |
ウィジェットツリー内の変更は再ビルドを引き起こすため、必要以上に大きなウィジェットツリーにInheritedWidgetを配置するとパフォーマンスの問題が発生する可能性があります。 | |
適切な粒度で状態を共有することが重要です。 | |
最近のFlutterでは、ProviderパッケージやRiverpodなどの状態管理ライブラリがInheritedWidgetをラップしてより便利に使用できるようになっています。 | |
これらのライブラリを使用することで、状態管理の柔軟性と効率性を向上させることができます。 | |
*/ | |
class BlocProvider<T extends CounterBloc> extends InheritedWidget { | |
final T bloc; | |
const BlocProvider({Key? key, required this.bloc, required Widget child}) | |
: super(key: key, child: child); | |
static T of<T extends CounterBloc>(BuildContext context) { | |
final BlocProvider<T>? provider = | |
context.dependOnInheritedWidgetOfExactType<BlocProvider<T>>(); | |
return provider!.bloc; | |
} | |
@override | |
bool updateShouldNotify(InheritedWidget oldWidget) => true; | |
} | |
class CounterBloc { | |
CounterState _currentState = CounterState(counterValue: 0); | |
final _eventController = StreamController<CounterEvent>(); | |
Sink<CounterEvent> get eventSink => _eventController.sink; | |
final _stateController = StreamController<CounterState>(); | |
Stream<CounterState> get stateStream => _stateController.stream; | |
CounterBloc() { | |
_eventController.stream.listen(_mapEventToState); | |
} | |
void _mapEventToState(CounterEvent event) { | |
if (event is IncrementEvent) { | |
_currentState = | |
CounterState(counterValue: _currentState.counterValue + 1); | |
} | |
_stateController.sink.add(_currentState); | |
} | |
void dispose() { | |
_eventController.close(); | |
_stateController.close(); | |
} | |
} |
Author
DaisukeNagata
commented
Jul 9, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment