BLoC(Business Logic Component) 패턴은 Flutter 애플리케이션에서 비즈니스 로직을 UI 코드와 분리하는 데 사용되는 설계 패턴입니다.
이 패턴은 Stream과 Sink를 활용하여 데이터의 흐름을 관리하고, 상태 관리를 효율적으로 수행할 수 있게 합니다.
BLoC 패턴은 Flutter의 공식 상태 관리 솔루션 중 하나로, 재사용성과 테스트 가능성을 높이는 데 기여합니다.
주요 개념
1. BLoC (Business Logic Component):
• 비즈니스 로직을 담당하는 컴포넌트입니다. Stream을 통해 UI에 데이터를 전달하고, Sink를 통해 UI로부터 이벤트를 받습니다.
2. Stream:
• 비동기 데이터의 흐름을 나타내는 Dart의 클래스입니다. BLoC 패턴에서는 상태 변경을 UI로 전달하는 데 사용됩니다.
3. Sink:
• 데이터를 Stream에 전달하는 입력 통로입니다. BLoC 패턴에서는 UI에서 발생한 이벤트를 BLoC로 전달하는 데 사용됩니다.
4. Event:
• UI에서 발생하는 사용자 상호작용을 나타냅니다. 버튼 클릭, 폼 입력 등 다양한 이벤트가 포함됩니다.
5. State:
• UI에서 표시할 데이터를 나타냅니다. BLoC는 상태를 관리하고 Stream을 통해 UI로 전달합니다.
BLoC 패턴의 구성 요소
1. Event 클래스:
• 사용자가 수행하는 동작을 정의합니다.
2. State 클래스:
• 애플리케이션의 상태를 정의합니다.
3. BLoC 클래스:
• 이벤트를 처리하고 상태를 관리하는 로직을 포함합니다.
예제
간단한 카운터 애플리케이션을 BLoC 패턴으로 구현한 예제를 살펴보겠습니다.
Event 클래스
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
State 클래스
class CounterState {
final int count;
CounterState(this.count);
}
BLoC 클래스
import 'dart:async';
class CounterBloc {
final _stateController = StreamController<CounterState>();
StreamSink<CounterState> get _inCounter => _stateController.sink;
Stream<CounterState> get counter => _stateController.stream;
final _eventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink => _eventController.sink;
CounterBloc() {
_eventController.stream.listen(_mapEventToState);
}
void _mapEventToState(CounterEvent event) {
if (event is Increment) {
_inCounter.add(CounterState(_stateController.stream.value.count + 1));
} else if (event is Decrement) {
_inCounter.add(CounterState(_stateController.stream.value.count - 1));
}
}
void dispose() {
_stateController.close();
_eventController.close();
}
}
UI 코드
import 'package:flutter/material.dart';
import 'counter_bloc.dart'; // BLoC 파일을 가져옴
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
final CounterBloc _bloc = CounterBloc();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BLoC Counter Example'),
),
body: Center(
child: StreamBuilder<CounterState>(
stream: _bloc.counter,
initialData: CounterState(0),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Text(
'Counter: ${snapshot.data.count}',
style: TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
_bloc.counterEventSink.add(Increment());
},
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
_bloc.counterEventSink.add(Decrement());
},
child: Icon(Icons.remove),
),
],
),
);
}
}
예시(실습)
패키지 추가
rxdart:
http:
model 부터 생성해줍니다.
class Album {
int? userId;
int? id;
String? title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
import './album.dart';
class Albums {
late List<Album> albums;
Albums({required this.albums});
Albums.fromJSON(List<dynamic> json) {
albums = List<Album>.empty(growable: true);
for (dynamic val in json) {
albums.add(Album.fromJson(val));
}
}
}
provider 를 생성해줍니다.
import 'dart:convert';
import 'package:http/http.dart' show Client;
import 'package:flutter_lecture/model/albums.dart';
class AlbumApiProvider {
Client client = Client();
Future<Albums> fetchAlbumList() async {
final response = await client
.get(Uri.parse("https://jsonplaceholder.typicode.com/albums"));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return Albums.fromJSON(data);
} else {
throw Exception("Failed");
}
}
}
repository 를 생성합니다.
import 'package:flutter_lecture/data_provider/api_provider.dart';
import 'package:flutter_lecture/model/albums.dart';
class AlbumRepository {
final AlbumApiProvider _albumApiProvider = AlbumApiProvider();
Future<Albums> fetchAllAlbums() async => _albumApiProvider.fetchAlbumList();
}
bloc 파일을 만들어줍니다.
import 'package:flutter_lecture/repository/ablum_repository.dart';
import 'package:rxdart/rxdart.dart';
import '../model/albums.dart';
class AlbumBloc {
final AlbumRepository _albumRepository = AlbumRepository();
final PublishSubject<Albums> _albumFetcher = PublishSubject<Albums>();
Stream<Albums> get allAlbums => _albumFetcher.stream;
Future<void> fetchAllAlbums() async {
Albums albums = await _albumRepository.fetchAllAlbums();
_albumFetcher.sink.add(albums);
}
dispose() {
_albumFetcher.close();
}
}
bloc 파일을 적용할 view 파일을 마지막으로 만들어줍니다.
import 'package:flutter/material.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final AlbumBloc _albumBloc = AlbumBloc();
@override
void initState() {
_albumBloc.fetchAllAlbums();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("test title"),
),
body: StreamBuilder<Albums>(
stream: _albumBloc.allAlbums,
builder: (context, snapshot) {
if(snapshot.hasData) {
Albums? albumList = snapshot.data;
return ListView.builder(
itemCount: albumList?.albums.length,
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("ID: ${albumList?.albums[index].id.toString()}"),
Text("Title: ${albumList?.albums[index].title}" )
],
),
);
}
);
} else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString()),
);
} else {
return const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
);
}
},
),
);
}
}
BLoC 패턴의 장점
1. UI와 비즈니스 로직의 분리:
• UI 코드와 비즈니스 로직을 명확히 분리하여 코드의 가독성과 유지보수성을 높입니다.
2. 재사용성과 테스트 용이성:
• BLoC 컴포넌트는 독립적으로 동작하므로 다른 프로젝트나 화면에서 재사용할 수 있습니다. 또한, 비즈니스 로직이 UI와 분리되어 있어 단위 테스트가 용이합니다.
3. 비동기 데이터 처리:
• Stream과 Sink를 사용하여 비동기 데이터 처리를 효과적으로 관리할 수 있습니다.
참고 문서
이 정보를 통해 Flutter 애플리케이션에서 BLoC 패턴을 효과적으로 사용하여 상태 관리를 수행할 수 있습니다. BLoC 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.
'프론트엔드 > Flutter' 카테고리의 다른 글
flutter get_it 패턴 (1) | 2024.07.22 |
---|---|
flutter provider 패턴 (1) | 2024.07.22 |
flutter 리프레쉬 인디케이터 (0) | 2024.07.22 |
dart 반복문 정리 (0) | 2024.07.21 |
dart 조건문 정리 (0) | 2024.07.21 |