Flutter ์ํ ๊ด๋ฆฌ: Provider vs BLoC ๐

๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ ์ธ๊ณ์์ Flutter๊ฐ ๊ธ๋ถ์ํ๋ฉด์, ๊ฐ๋ฐ์๋ค์ ํจ์จ์ ์ธ ์ํ ๊ด๋ฆฌ ๋ฐฉ๋ฒ์ ์ฐพ๋ ๋ฐ ๋ง์ ๊ด์ฌ์ ๊ธฐ์ธ์ด๊ณ ์์ต๋๋ค. ํนํ Provider์ BLoC(Business Logic Component)๋ Flutter ์ํ๊ณ์์ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ ๊ฐ์ง ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ผ๋ก ์๋ฆฌ ์ก์์ฃ . ์ด ๊ธ์์๋ ์ด ๋ ๊ฐ์ง ์ ๊ทผ ๋ฐฉ์์ ๊น์ด ์๊ฒ ๋น๊ตํ๊ณ , ๊ฐ๊ฐ์ ์ฅ๋จ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๐ค
Flutter ๊ฐ๋ฐ์๋ก์, ์ฐ๋ฆฌ๋ ํญ์ ๋ ๋์ ๋ฐฉ๋ฒ์ ์ฐพ์ ๋์์ผ ํฉ๋๋ค. ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋ฏ์ด, ์ฐ๋ฆฌ๋ ๋ค์ํ ๊ธฐ์ ๊ณผ ํจํด์ ์ตํ๊ณ ์ ์ฉํด์ผ ํ์ฃ . ๊ทธ๋ผ ์ง๊ธ๋ถํฐ Provider์ BLoC์ ๋ํด ์์ธํ ์์๋ณด๊ฒ ์ต๋๋ค!
Provider: ๊ฐ๋จํ๊ณ ์ง๊ด์ ์ธ ์ํ ๊ด๋ฆฌ ๐ฏ
Provider๋ Flutter ํ์์ ๊ณต์์ ์ผ๋ก ์ถ์ฒํ๋ ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ ๋๋ค. ๊ฐ๋จํ ๊ตฌ์กฐ์ ์ง๊ด์ ์ธ API๋ก ์ธํด ๋ง์ ๊ฐ๋ฐ์๋ค์ ์ฌ๋์ ๋ฐ๊ณ ์์ฃ . ๐
Provider์ ์ฃผ์ ํน์ง:
- ์์กด์ฑ ์ฃผ์ (Dependency Injection): Provider๋ ์์ ฏ ํธ๋ฆฌ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ์ ๋ฌํ ์ ์๊ฒ ํด์ค๋๋ค.
- ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ: ChangeNotifier๋ฅผ ํตํด ์ํ ๋ณํ๋ฅผ ๊ฐ์งํ๊ณ UI๋ฅผ ์๋์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค.
- ์ฝ๋์ ๊ฐ๊ฒฐ์ฑ: ๋ณต์กํ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋ ์์ด๋ ์ํ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ์ฑ๋ฅ ์ต์ ํ: ํ์ํ ์์ ฏ๋ง ๋ฆฌ๋น๋ํ์ฌ ์ฑ์ ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค.
ย
Provider๋ฅผ ์ฌ์ฉํ๋ฉด, ์ํ ๊ด๋ฆฌ ๋ก์ง์ UI ์ฝ๋์์ ๋ถ๋ฆฌํ์ฌ ๋ ๊น๋ํ๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ์ด๋ ๋ง์น ์ฌ๋ฅ๋ท์์ ๊ฐ ๋ถ์ผ์ ์ ๋ฌธ๊ฐ๋ค์ด ์์ ์ ์ฌ๋ฅ์ ํนํํ์ฌ ์ ๊ณตํ๋ ๊ฒ๊ณผ ๋น์ทํ๋ค๊ณ ํ ์ ์๊ฒ ๋ค์. ๐
Provider ์ฌ์ฉ ์์:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Provider Example')),
body: Center(
child: Consumer<counter>(
builder: (context, counter, child) => Text(
'${counter.count}',
style: TextStyle(fontSize: 24),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<counter>(context, listen: false).increment(),
child: Icon(Icons.add),
),
),
);
}
}
</counter></counter>
์ด ์์์์ ๋ณผ ์ ์๋ฏ์ด, Provider๋ฅผ ์ฌ์ฉํ๋ฉด ์ํ(Counter)๋ฅผ ์ฝ๊ฒ ์ ์ํ๊ณ , ์์ ฏ ํธ๋ฆฌ ์ ์ฒด์ ์ ๊ณตํ ์ ์์ต๋๋ค. Consumer ์์ ฏ์ ํตํด ์ํ ๋ณํ๋ฅผ ๊ฐ์งํ๊ณ , UI๋ฅผ ์๋์ผ๋ก ์ ๋ฐ์ดํธํ ์ ์์ฃ . ๐
๐ก Pro Tip:
Provider๋ฅผ ์ฌ์ฉํ ๋๋ ์ํ ๋ณํ๊ฐ ํ์ํ ๋ถ๋ถ๋ง Consumer๋ก ๊ฐ์ธ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ถํ์ํ ๋ฆฌ๋น๋๋ฅผ ๋ฐฉ์งํ๊ณ ์ฑ์ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์ต๋๋ค.
BLoC: ๊ฐ๋ ฅํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ์ํ ๊ด๋ฆฌ ๐๏ธ
BLoC(Business Logic Component)๋ Google์์ ์ ์ํ ์ํคํ ์ฒ ํจํด์ผ๋ก, ๋ณต์กํ ์ฑ์์ ์ํ ๊ด๋ฆฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ ์ ์๊ฒ ํด์ค๋๋ค. BLoC์ ๋น์ฆ๋์ค ๋ก์ง์ UI์์ ์์ ํ ๋ถ๋ฆฌํ์ฌ, ํ ์คํธ์ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๐งฉ
BLoC์ ์ฃผ์ ํน์ง:
- ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ: Stream์ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ๋น๋๊ธฐ ๋ฐ์ดํฐ ํ๋ฆ์ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
- ๋ช ํํ ์ํคํ ์ฒ: ์ ๋ ฅ(Event)๊ณผ ์ถ๋ ฅ(State)์ด ๋ช ํํ ๊ตฌ๋ถ๋์ด ์์ด ์ฝ๋์ ๊ตฌ์กฐ๊ฐ ๋ช ํํฉ๋๋ค.
- ํ ์คํธ ์ฉ์ด์ฑ: ๋น์ฆ๋์ค ๋ก์ง์ด UI์ ๋ถ๋ฆฌ๋์ด ์์ด ๋จ์ ํ ์คํธ๊ฐ ์ฝ์ต๋๋ค.
- ํ์ฅ์ฑ: ๋ณต์กํ ์ฑ์์๋ ํจ๊ณผ์ ์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
ย
BLoC ํจํด์ ์ฌ์ฉํ๋ฉด, ์ฑ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ช ํํ๊ฒ ๊ตฌ์กฐํํ ์ ์์ต๋๋ค. ์ด๋ ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ณต์กํ ํ๋ก์ ํธ๋ฅผ ์ฌ๋ฌ ์ ๋ฌธ๊ฐ๊ฐ ํ์ ํ์ฌ ํจ์จ์ ์ผ๋ก ์งํํ๋ ๊ฒ๊ณผ ์ ์ฌํ๋ค๊ณ ๋ณผ ์ ์๊ฒ ๋ค์. ๐ค
BLoC ์ฌ์ฉ ์์:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
// States
class CounterState {
final int count;
CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<counterevent counterstate=""> {
CounterBloc() : super(CounterState(0)) {
on<incrementevent>((event, emit) {
emit(CounterState(state.count + 1));
});
}
}
void main() {
runApp(
BlocProvider(
create: (context) => CounterBloc(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('BLoC Example')),
body: Center(
child: BlocBuilder<counterbloc counterstate="">(
builder: (context, state) {
return Text(
'${state.count}',
style: TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<counterbloc>().add(IncrementEvent()),
child: Icon(Icons.add),
),
),
);
}
}
</counterbloc></counterbloc></incrementevent></counterevent>
์ด ์์์์ ๋ณผ ์ ์๋ฏ์ด, BLoC ํจํด์ Event์ State๋ฅผ ๋ช ํํ ๊ตฌ๋ถํ๊ณ ์์ต๋๋ค. BlocBuilder๋ฅผ ํตํด ์ํ ๋ณํ๋ฅผ ๊ฐ์งํ๊ณ UI๋ฅผ ์ ๋ฐ์ดํธํ๋ฉฐ, ๋น์ฆ๋์ค ๋ก์ง์ CounterBloc ํด๋์ค์ ์์ ํ ๋ถ๋ฆฌ๋์ด ์์ต๋๋ค. ๐จโ๐ป
๐ก Pro Tip:
BLoC ํจํด์ ์ฌ์ฉํ ๋๋ ๊ฐ ๊ธฐ๋ฅ๋ณ๋ก ๋ณ๋์ BLoC์ ๋ง๋๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฝ๋์ ๋ชจ๋์ฑ์ด ๋์์ง๊ณ , ๋๊ท๋ชจ ์ฑ์์๋ ํจ๊ณผ์ ์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
Provider vs BLoC: ์ด๋ค ๊ฒ์ ์ ํํด์ผ ํ ๊น? ๐ค
Provider์ BLoC์ ๊ฐ๊ฐ ์ฅ๋จ์ ์ด ์์ด, ํ๋ก์ ํธ์ ํน์ฑ์ ๋ฐ๋ผ ์ ํํด์ผ ํฉ๋๋ค. ๋ ์ ๊ทผ ๋ฐฉ์์ ๋น๊ตํด๋ณด๋ฉด์, ์ด๋ค ์ํฉ์์ ์ด๋ค ๋ฐฉ์์ด ๋ ์ ํฉํ์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๐ง
1. ํ์ต ๊ณก์ ๐
- Provider: ๋น๊ต์ ๊ฐ๋จํ API๋ก ์ธํด ํ์ต ๊ณก์ ์ด ๋ฎ์ต๋๋ค. Flutter ์ด๋ณด์๋ ์ฝ๊ฒ ์ ๊ทผํ ์ ์์ต๋๋ค.
- BLoC: ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ๊ณผ Stream์ ๋ํ ์ดํด๊ฐ ํ์ํ์ฌ ํ์ต ๊ณก์ ์ด ๋์ ํธ์ ๋๋ค.
ย
๊ฒฐ๋ก : ๋น ๋ฅด๊ฒ ๊ฐ๋ฐ์ ์์ํ๊ณ ์ถ๊ฑฐ๋, ํ ์ ์ฒด๊ฐ ์๋ก์ด ํจํด์ ํ์ตํ ์๊ฐ์ด ๋ถ์กฑํ๋ค๋ฉด Provider๊ฐ ๋ ์ ํฉํ ์ ์์ต๋๋ค.
2. ํ๋ก์ ํธ ๋ณต์ก๋ ๐๏ธ
- Provider: ์์ ๊ท๋ชจ์ ํ๋ก์ ํธ๋ ์ค๊ฐ ๊ท๋ชจ์ ์ฑ์ ์ ํฉํฉ๋๋ค. ๊ฐ๋จํ ์ํ ๊ด๋ฆฌ์ ํจ๊ณผ์ ์ ๋๋ค.
- BLoC: ๋๊ท๋ชจ ํ๋ก์ ํธ๋ ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ์ง ์ฑ์ ์ ํฉํฉ๋๋ค. ํ์ฅ์ฑ์ด ๋ฐ์ด๋๊ณ ์ฝ๋ ๊ตฌ์กฐํ๊ฐ ์ฉ์ดํฉ๋๋ค.
ย
๊ฒฐ๋ก : ํ๋ก์ ํธ์ ๊ท๋ชจ์ ๋ณต์ก๋๋ฅผ ๊ณ ๋ คํ์ฌ ์ ํํด์ผ ํฉ๋๋ค. ๋ณต์กํ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ BLoC์ด ๋ ์ ํฉํ ์ ์์ต๋๋ค.
3. ์ฑ๋ฅ ๐
- Provider: ๊ฐ๋จํ ๊ตฌ์กฐ๋ก ์ธํด ์ผ๋ฐ์ ์ผ๋ก ์ฑ๋ฅ์ด ์ข์ต๋๋ค. ํ์ง๋ง ๋ณต์กํ ์ํ ๊ด๋ฆฌ์์๋ ์ฑ๋ฅ ์ต์ ํ์ ์ถ๊ฐ ์์ ์ด ํ์ํ ์ ์์ต๋๋ค.
- BLoC: Stream ๊ธฐ๋ฐ์ผ๋ก ๋์ํ์ฌ ๋ณต์กํ ์ํ ๊ด๋ฆฌ์์๋ ํจ์จ์ ์ธ ์ฑ๋ฅ์ ๋ณด์ ๋๋ค. ํ์ง๋ง ๊ฐ๋จํ ์ํฉ์์๋ ์ค๋ฒํค๋๊ฐ ์์ ์ ์์ต๋๋ค.
ย
๊ฒฐ๋ก : ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ ๋ฐฉ์ ๋ชจ๋ ์ถฉ๋ถํ ์ฑ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ํ์ง๋ง ๋งค์ฐ ๋ณต์กํ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ BLoC์ด ๋ ๋์ ์ฑ๋ฅ์ ๋ณด์ผ ์ ์์ต๋๋ค.
4. ํ ์คํธ ์ฉ์ด์ฑ ๐งช
- Provider: ๋จ์ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ์ง๋ง, UI์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ถ๋ฆฌ๊ฐ BLoC๋งํผ ๋ช ํํ์ง ์์ ์ ์์ต๋๋ค.
- BLoC: ๋น์ฆ๋์ค ๋ก์ง์ด ์์ ํ ๋ถ๋ฆฌ๋์ด ์์ด ๋จ์ ํ ์คํธ๊ฐ ๋งค์ฐ ์ฉ์ดํฉ๋๋ค. ๋ํ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ํคํ ์ฒ๋ก ์ธํด ํตํฉ ํ ์คํธ๋ ์ฝ๊ฒ ์ํํ ์ ์์ต๋๋ค.
ย
๊ฒฐ๋ก : ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)์ ์ค์ํ๊ฒ ์๊ฐํ๊ฑฐ๋, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ ๋ํ ์ฒ ์ ํ ํ ์คํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ BLoC์ด ๋ ์ ํฉํ ์ ์์ต๋๋ค.
5. ์ฝ๋ ๊ตฌ์กฐํ ๋ฐ ์ ์ง๋ณด์์ฑ ๐งฉ
- Provider: ๊ฐ๋จํ ๊ตฌ์กฐ๋ก ์ธํด ์์ ํ๋ก์ ํธ์์๋ ์ฝ๋ ๊ตฌ์กฐํ๊ฐ ์ฝ์ต๋๋ค. ํ์ง๋ง ํ๋ก์ ํธ๊ฐ ์ปค์ง์๋ก ์ํ ๊ด๋ฆฌ ๋ก์ง์ด ๋ณต์กํด์ง ์ ์์ต๋๋ค.
- BLoC: ๋ช ํํ ์ํคํ ์ฒ๋ก ์ธํด ๋๊ท๋ชจ ํ๋ก์ ํธ์์๋ ์ฝ๋ ๊ตฌ์กฐํ๊ฐ ์ฉ์ดํฉ๋๋ค. ๋น์ฆ๋์ค ๋ก์ง์ ๋ถ๋ฆฌ๊ฐ ๋ช ํํ์ฌ ์ ์ง๋ณด์์ฑ์ด ๋์ต๋๋ค.
ย
๊ฒฐ๋ก : ์ฅ๊ธฐ์ ์ธ ๊ด์ ์์ ํ๋ก์ ํธ์ ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ์ค์ํ๊ฒ ์๊ฐํ๋ค๋ฉด BLoC์ด ๋ ๋์ ์ ํ์ผ ์ ์์ต๋๋ค.
๐ก ๊ฐ๋ฐ์์ ์ธ์ฌ์ดํธ:
์ค์ ํ๋ก์ ํธ์์๋ Provider์ BLoC์ ํจ๊ป ์ฌ์ฉํ๋ ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ ๋ฐฉ์๋ ๊ณ ๋ คํด๋ณผ ๋งํฉ๋๋ค. ๊ฐ๋จํ ์ํ ๊ด๋ฆฌ๋ Provider๋ก, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ BLoC์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ ํจํด์ ์ฅ์ ์ ์ต๋ํ ํ์ฉํ ์ ์์ต๋๋ค.
์ค์ ์ฌ์ฉ ์ฌ๋ก ๋ถ์ ๐
์ด๋ก ์ ์ธ ๋น๊ต๋ ์ค์ํ์ง๋ง, ์ค์ ํ๋ก์ ํธ์์ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง ์ดํด๋ณด๋ ๊ฒ๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ฌ๊ธฐ์๋ Provider์ BLoC์ ์ฌ์ฉํ ์ค์ ์ฌ๋ก๋ฅผ ๋ถ์ํด๋ณด๊ฒ ์ต๋๋ค. ๐ต๏ธโโ๏ธ
1. ์์ ๋ฏธ๋์ด ์ฑ - Provider ์ฌ์ฉ ์ฌ๋ก
๊ฐ๋จํ ์์ ๋ฏธ๋์ด ์ฑ์ ๊ฐ๋ฐํ๋ ๊ฒฝ์ฐ, Provider๋ฅผ ์ฌ์ฉํ์ฌ ํจ๊ณผ์ ์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
// ์ฌ์ฉ์ ๋ชจ๋ธ
class User {
final String name;
final String email;
User(this.name, this.email);
}
// ์ฌ์ฉ์ ์ํ ๊ด๋ฆฌ
class UserProvider with ChangeNotifier {
User? _user;
User? get user => _user;
void setUser(User user) {
_user = user;
notifyListeners();
}
void logout() {
_user = null;
notifyListeners();
}
}
// ํฌ์คํธ ๋ชจ๋ธ
class Post {
final String title;
final String content;
Post(this.title, this.content);
}
// ํฌ์คํธ ์ํ ๊ด๋ฆฌ
class PostProvider with ChangeNotifier {
List<post> _posts = [];
List<post> get posts => _posts;
void addPost(Post post) {
_posts.add(post);
notifyListeners();
}
}
// ๋ฉ์ธ ์ฑ
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProvider(create: (_) => PostProvider()),
],
child: MyApp(),
),
);
}
// ํ ํ๋ฉด
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final user = context.watch<userprovider>().user;
final posts = context.watch<postprovider>().posts;
return Scaffold(
appBar: AppBar(title: Text('Social Media App')),
body: user == null
? LoginScreen()
: ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(posts[index].title),
subtitle: Text(posts[index].content),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// ์ ํฌ์คํธ ์ถ๊ฐ ๋ก์ง
},
child: Icon(Icons.add),
),
);
}
}
</postprovider></userprovider></post></post>
์ด ์์์์ Provider๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ์ ๋ณด์ ํฌ์คํธ ๋ชฉ๋ก์ ๊ด๋ฆฌํ๊ณ ์์ต๋๋ค. ๊ฐ๋จํ ๊ตฌ์กฐ๋ก ์ธํด ์ฝ๋๊ฐ ์ง๊ด์ ์ด๊ณ ์ดํดํ๊ธฐ ์ฝ์ต๋๋ค. ๐
2. ์ ์์๊ฑฐ๋ ์ฑ - BLoC ์ฌ์ฉ ์ฌ๋ก
๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ด ํ์ํ ์ ์์๊ฑฐ๋ ์ฑ์ ๊ฒฝ์ฐ, BLoC ํจํด์ ์ฌ์ฉํ์ฌ ํจ๊ณผ์ ์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
// ์ํ ๋ชจ๋ธ
class Product {
final String id;
final String name;
final double price;
Product(this.id, this.name, this.price);
}
// ์ฅ๋ฐ๊ตฌ๋ ์ด๋ฒคํธ
abstract class CartEvent {}
class AddToCartEvent extends CartEvent {
final Product product;
AddToCartEvent(this.product);
}
class RemoveFromCartEvent extends CartEvent {
final Product product;
RemoveFromCartEvent(this.product);
}
// ์ฅ๋ฐ๊ตฌ๋ ์ํ
class CartState {
final List<product> items;
final double total;
CartState(this.items, this.total);
}
// ์ฅ๋ฐ๊ตฌ๋ BLoC
class CartBloc extends Bloc<cartevent cartstate=""> {
CartBloc() : super(CartState([], 0)) {
on<addtocartevent>((event, emit) {
final updatedItems = List<product>.from(state.items)..add(event.product);
final newTotal = state.total + event.product.price;
emit(CartState(updatedItems, newTotal));
});
on<removefromcartevent>((event, emit) {
final updatedItems = List<product>.from(state.items)..remove(event.product);
final newTotal = state.total - event.product.price;
emit(CartState(updatedItems, newTotal));
});
}
}
// ๋ฉ์ธ ์ฑ
void main() {
runApp(
BlocProvider(
create: (context) => CartBloc(),
child: MyApp(),
),
);
}
// ์ํ ๋ชฉ๋ก ํ๋ฉด
class ProductListScreen extends StatelessWidget {
final List<product> products = [
Product('1', 'Laptop', 999.99),
Product('2', 'Smartphone', 699.99),
Product('3', 'Headphones', 199.99),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product List')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(products[index].name),
subtitle: Text('\$${products[index].price}'),
trailing: IconButton(
icon: Icon(Icons.add_shopping_cart),
onPressed: () {
context.read<cartbloc>().add(AddToCartEvent(products[index]));
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => CartScreen()));
},
child: Icon(Icons.shopping_cart),
),
);
}
}
// ์ฅ๋ฐ๊ตฌ๋ ํ๋ฉด
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Cart')),
body: BlocBuilder<cartbloc cartstate="">(
builder: (context, state) {
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(state.items[index].name),
subtitle: Text('\$${state.items[index].price}'),
trailing: IconButton(
icon: Icon(Icons.remove_shopping_cart),
onPressed: () {
context.read<cartbloc>().add(RemoveFromCartEvent(state.items[index]));
},
),
);
},
),
),
Padding(
padding: EdgeInsets.all(16.0),
child: Text('Total: \$${state.total.toStringAsFixed(2)}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
),
],
);
},
),
);
}
}
</cartbloc></cartbloc></cartbloc></product></product></removefromcartevent></product></addtocartevent></cartevent></product>
์ด ์์์์ BLoC ํจํด์ ์ฌ์ฉํ์ฌ ์ฅ๋ฐ๊ตฌ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ์์ต๋๋ค. ๋น์ฆ๋์ค ๋ก์ง์ด UI์ ์์ ํ ๋ถ๋ฆฌ๋์ด ์์ด, ๋ณต์กํ ์ํ ๊ด๋ฆฌ์ ํ ์คํธ๊ฐ ์ฉ์ดํฉ๋๋ค. ๐
๐ก ์ค๋ฌด ํ:
์ค์ ํ๋ก์ ํธ์์๋ ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ ์ ํํ ๋ ํ์ ๊ฒฝํ๊ณผ ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ์ข ํฉ์ ์ผ๋ก ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ๋๋ก๋ Provider์ BLoC์ ํจ๊ป ์ฌ์ฉํ๋ ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ ๋ฐฉ์์ด ๊ฐ์ฅ ํจ๊ณผ์ ์ผ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๊ฐ๋จํ UI ์ํ๋ Provider๋ก, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ BLoC์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค.
์ฑ๋ฅ ์ต์ ํ ์ ๋ต ๐
Provider์ BLoC ๋ชจ๋ ํจ์จ์ ์ธ ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ด์ง๋ง, ๋๊ท๋ชจ ์ฑ์์๋ ์ถ๊ฐ์ ์ธ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๊ฐ ํจํด๋ณ๋ก ์ฑ๋ฅ์ ์ต์ ํํ๋ ์ ๋ต์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๐ช
Provider ์ฑ๋ฅ ์ต์ ํ
- ์ ํ์ ๋ฆฌ๋น๋: Consumer ์์ ฏ์ ์ฌ์ฉํ์ฌ ํ์ํ ๋ถ๋ถ๋ง ๋ฆฌ๋น๋ํ๋๋ก ํฉ๋๋ค.
- Selector ์ฌ์ฉ: Selector๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ์ํ ๋ณํ์๋ง ๋ฐ์ํ๋๋ก ํฉ๋๋ค.
- ์ํ ๋ถ๋ฆฌ: ๊ด๋ จ ์๋ ์ํ๋ฅผ ๋ณ๋์ Provider๋ก ๋ถ๋ฆฌํฉ๋๋ค.
ย
์์ ์ฝ๋:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<mymodel string="">(
selector: (_, model) => model.specificData,
builder: (_, specificData, __) {
return Text(specificData);
},
);
}
}
</mymodel>
BLoC ์ฑ๋ฅ ์ต์ ํ
- ์ด๋ฒคํธ ๋๋ฐ์ด์ฑ: ์ฐ์์ ์ธ ์ด๋ฒคํธ ๋ฐ์ ์ ๋ง์ง๋ง ์ด๋ฒคํธ๋ง ์ฒ๋ฆฌํฉ๋๋ค.
- ์ํ ๋๋ฑ์ฑ ๊ฒ์ฌ: ๋ถํ์ํ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
- ๋น๋๊ธฐ ์ฐ์ฐ ์ต์ ํ: switchMap ๋ฑ์ ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์์ ์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค.
ย
์์ ์ฝ๋:
class MyBloc extends Bloc<myevent mystate=""> {
MyBloc() : super(MyInitialState()) {
on<myevent>((event, emit) async {
// ์ด๋ฒคํธ ๋๋ฐ์ด์ฑ
await Future.delayed(Duration(milliseconds: 300));
// ๋น๋๊ธฐ ์ฐ์ฐ ์ต์ ํ
final result = await _fetchData();
// ์ํ ๋๋ฑ์ฑ ๊ฒ์ฌ
if (result != state.data) {
emit(MyState(result));
}
}, transformer: debounce(const Duration(milliseconds: 300)));
}
}
EventTransformer<t> debounce<t>(Duration duration) {
return (events, mapper) => events.debounce (Duration duration) {
return (events, mapper) => events.debounceTime(duration).switchMap(mapper);
}
</t></t></myevent></myevent>
๐ก ์ฑ๋ฅ ์ต์ ํ ํ:
ํญ์ ์ฑ๋ฅ ํ๋กํ์ผ๋ง์ ํตํด ์ค์ ๋ณ๋ชฉ ์ง์ ์ ํ์ ํ๊ณ , ํ์ํ ๋ถ๋ถ์๋ง ์ต์ ํ๋ฅผ ์ ์ฉํ์ธ์. ๊ณผ๋ํ ์ต์ ํ๋ ์คํ๋ ค ์ฝ๋์ ๋ณต์ก์ฑ์ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.
๊ฒฐ๋ก : ๋น์ ์ ํ๋ก์ ํธ์ ๋ง๋ ์ ํ ๐ฏ
Provider์ BLoC์ ๊ฐ๊ฐ ๊ณ ์ ํ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ํ๋ก์ ํธ์ ํน์ฑ์ ๋ฐ๋ผ ์ ํฉํ ์ ํ์ด ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ์ต์ข ์ ์ธ ์ ํ์ ์ํด ๋ค์ ์ฌํญ๋ค์ ๊ณ ๋ คํด๋ณด์ธ์:
- ํ๋ก์ ํธ ๊ท๋ชจ์ ๋ณต์ก๋: ์์ ๊ท๋ชจ์ ํ๋ก์ ํธ๋ผ๋ฉด Provider๊ฐ, ๋๊ท๋ชจ ๋ณต์กํ ํ๋ก์ ํธ๋ผ๋ฉด BLoC์ด ๋ ์ ํฉํ ์ ์์ต๋๋ค.
- ํ์ ๊ฒฝํ๊ณผ ํ์ต ๊ณก์ : Provider๋ ํ์ต์ด ์ฝ๊ณ , BLoC์ ๋ ๊น์ ์ดํด๊ฐ ํ์ํฉ๋๋ค.
- ์ ์ง๋ณด์์ฑ๊ณผ ํ ์คํธ ์ฉ์ด์ฑ: BLoC์ ์ฝ๋ ๊ตฌ์กฐํ์ ํ ์คํธ์ ๊ฐ์ ์ด ์์ต๋๋ค.
- ์ฑ๋ฅ ์๊ตฌ์ฌํญ: ๋ ํจํด ๋ชจ๋ ์ต์ ํ๊ฐ ๊ฐ๋ฅํ์ง๋ง, ๋ณต์กํ ์ํ ๊ด๋ฆฌ์์๋ BLoC์ด ๋ ํจ๊ณผ์ ์ผ ์ ์์ต๋๋ค.
- ๋ฏธ๋์ ํ์ฅ์ฑ: ํ๋ก์ ํธ๊ฐ ์ฑ์ฅํ ๊ฐ๋ฅ์ฑ์ ๊ณ ๋ คํ์ฌ ์ ํํ์ธ์.
ย
๋ง์ง๋ง์ผ๋ก, ์ด ๋ ํจํด์ ์ํธ ๋ฐฐํ์ ์ผ๋ก ์๊ฐํ ํ์๋ ์์ต๋๋ค. ํ๋ก์ ํธ์ ๋ค๋ฅธ ๋ถ๋ถ์ ๋ค๋ฅธ ํจํด์ ์ ์ฉํ๋ ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ ๋ฐฉ์๋ ๊ณ ๋ คํด๋ณผ ๋งํฉ๋๋ค. ์ค์ํ ๊ฒ์ ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑ์ํค๊ณ ๊ฐ๋ฐ ํ์ด ํจ์จ์ ์ผ๋ก ์์ ํ ์ ์๋ ์๋ฃจ์ ์ ์ ํํ๋ ๊ฒ์ ๋๋ค.
Flutter ๊ฐ๋ฐ์์ ์ํ ๊ด๋ฆฌ๋ ํต์ฌ์ ์ธ ๋ถ๋ถ์ ๋๋ค. Provider์ BLoC์ ๊ฐ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์์ผ๋ฉฐ, ๋ ๋ค ํ๋ฅญํ ์ ํ์ด ๋ ์ ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์ ๊ฐ์ฅ ์ ํฉํ ์๋ฃจ์ ์ ์ ํํ์ฌ ํจ์จ์ ์ด๊ณ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ ์ฑ์ ๊ฐ๋ฐํ์๊ธฐ ๋ฐ๋๋๋ค! ๐
๐ก ๋ง์ง๋ง ์กฐ์ธ:
์ํ ๊ด๋ฆฌ ํจํด์ ์ ํํ ๋๋ ํ์ฌ์ ์๊ตฌ์ฌํญ๋ฟ๋ง ์๋๋ผ ๋ฏธ๋์ ํ์ฅ ๊ฐ๋ฅ์ฑ๋ ๊ณ ๋ คํ์ธ์. ํ๋ก์ ํธ๊ฐ ์ฑ์ฅํจ์ ๋ฐ๋ผ ์ํ ๊ด๋ฆฌ์ ๋ณต์ก์ฑ๋ ์ฆ๊ฐํ ์ ์์ต๋๋ค. ์ฒ์๋ถํฐ ํ์ฅ ๊ฐ๋ฅํ ์๋ฃจ์ ์ ์ ํํ๋ฉด ๋์ค์ ํฐ ๋ฆฌํฉํ ๋ง์ ํผํ ์ ์์ต๋๋ค.
Flutter ๊ฐ๋ฐ์์ ์ํ ๊ด๋ฆฌ๋ ๋งค์ฐ ์ค์ํ ์ฃผ์ ์ ๋๋ค. Provider์ BLoC์ ๊ฐ๊ฐ ๊ณ ์ ํ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ์ ํ ์ ํํ๊ฑฐ๋ ์กฐํฉํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๊ธ์ด ์ฌ๋ฌ๋ถ์ Flutter ๊ฐ๋ฐ ์ฌ์ ์ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ํญ์ ์ต์ ํธ๋ ๋๋ฅผ ์ฃผ์ํ๊ณ , ์ง์์ ์ผ๋ก ํ์ตํ๋ฉฐ ๊ฐ๋ฐ ์คํฌ์ ํฅ์์์ผ ๋๊ฐ์ธ์. ํ์ดํ ! ๐จโ๐ป๐ฉโ๐ป
- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ