๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Flutter

BLoC ํŒจํ„ด: ์ดˆ๊ฐ„๋‹จ ์ดํ•ดํ•˜๊ธฐ

by alaxhenry 2025. 11. 5.
๋ฐ˜์‘ํ˜•
SMALL

๐ŸŽฌ ์˜ํ™”๊ด€ ๋น„์œ ๋กœ ์ดํ•ดํ•˜๊ธฐ

BLoC์„ ์˜ํ™”๊ด€์— ๋น„์œ ํ•ด๋ณผ๊ฒŒ์š”:

  • Event (์ด๋ฒคํŠธ): ๊ด€๊ฐ์ด ๊ทน์žฅ ์ง์›์—๊ฒŒ ํ•˜๋Š” ์š”์ฒญ
    • "ํŒ์ฝ˜ ์ฃผ์„ธ์š”", "์˜ํ™” ์‹œ์ž‘ํ•ด์ฃผ์„ธ์š”", "ํ™”์žฅ์‹ค ์–ด๋””์˜ˆ์š”?"
  • BLoC (๋ธ”๋ก): ๊ทน์žฅ ์ง์›
    • ๊ด€๊ฐ์˜ ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•จ
  • State (์ƒํƒœ): ๊ทน์žฅ์˜ ํ˜„์žฌ ์ƒํ™ฉ
    • "์˜ํ™” ์ƒ์˜ ์ค‘", "ํŒ์ฝ˜ ์ค€๋น„๋จ", "์˜ํ™” ์ข…๋ฃŒ"

๐Ÿ“ฑ ์ฝ”๋“œ๋กœ ๋ณด๋Š” ์‹ค์ œ ์˜ˆ์‹œ (ํ…Œ๋งˆ ๋ณ€๊ฒฝ)

๋‹น์‹ ์˜ ํ”„๋กœ์ ํŠธ์—์„œ ํ…Œ๋งˆ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ์„ ์˜ˆ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ์š”:

1๏ธโƒฃ Event (์ด๋ฒคํŠธ) - "๋ฌด์—‡์„ ์š”์ฒญํ• ๊นŒ?"

// theme_event.dart

// ์ด๋ฒคํŠธ = ์‚ฌ์šฉ์ž๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰๋™๋“ค
sealed class ThemeEvent {
  // 1. ์•ฑ ์‹œ์ž‘ํ•  ๋•Œ ํ…Œ๋งˆ ์ดˆ๊ธฐํ™”
  const factory ThemeEvent.init() = _InitialEvent;
  
  // 2. ํŠน์ • ํ…Œ๋งˆ๋กœ ๋ณ€๊ฒฝ
  const factory ThemeEvent.change({ThemeType type}) = _ChangeThemeEvent;
  
  // 3. ํ…Œ๋งˆ ํ† ๊ธ€ (๋ผ์ดํŠธ ↔ ๋‹คํฌ)
  const factory ThemeEvent.toggle() = _ToggleThemeEvnet;
}

 

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด:

  • init: "์•ฑ ์ผฐ์–ด, ์ €์žฅ๋œ ํ…Œ๋งˆ ๋ถˆ๋Ÿฌ์™€์ค˜!"
  • change: "๋‹คํฌ ๋ชจ๋“œ๋กœ ๋ฐ”๊ฟ”์ค˜!"
  • toggle: "์ง€๊ธˆ ํ…Œ๋งˆ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ”์ค˜!"

2๏ธโƒฃ State (์ƒํƒœ) - "ํ˜„์žฌ ์ƒํ™ฉ์€?"

// theme_state.dart

class ThemeState {
  final ThemeType type; // ํ˜„์žฌ ํ…Œ๋งˆ๊ฐ€ ๋ญ”์ง€
  
  const ThemeState({
    this.type = ThemeType.light, // ๊ธฐ๋ณธ์€ ๋ผ์ดํŠธ ๋ชจ๋“œ
  });
}

 

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด:

  • "์ง€๊ธˆ ์•ฑ์ด ๋‹คํฌ ๋ชจ๋“œ์ธ์ง€ ๋ผ์ดํŠธ ๋ชจ๋“œ์ธ์ง€" ์ €์žฅํ•˜๋Š” ๊ณณ
  • UI๋Š” ์ด ์ƒํƒœ๋ฅผ ๋ณด๊ณ  ํ™”๋ฉด์„ ๊ทธ๋ฆผ

3๏ธโƒฃ BLoC (๋ธ”๋ก) - "์ด๋ฒคํŠธ ์ฒ˜๋ฆฌํ•˜๋Š” ์ง์›"

// theme_bloc.dart

class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
  ThemeBloc() : super(const ThemeState()) {
    
    // ์ด๋ฒคํŠธ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด switch๋กœ ์ฒ˜๋ฆฌ
    on<ThemeEvent>((event, emit) async {
      switch (event) {
        case _InitialEvent():
          await _initTheme(event, emit);
        case _ChangeThemeEvent():
          await _changeTheme(event, emit);
        case _ToggleThemeEvnet():
          await _toggleTheme(event, emit);
      }
    });
  }

  // 1. ํ…Œ๋งˆ ์ดˆ๊ธฐํ™” ์ฒ˜๋ฆฌ
  Future<void> _initTheme(event, emit) async {
    // ์ €์žฅ๋œ ํ…Œ๋งˆ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    var result = await getAppThemeUseCase();
    
    // ์ƒˆ๋กœ์šด ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธ
    emit(ThemeState(type: result));
  }

  // 2. ํ…Œ๋งˆ ๋ณ€๊ฒฝ ์ฒ˜๋ฆฌ
  Future<void> _changeTheme(event, emit) async {
    // ์ƒˆ ํ…Œ๋งˆ ์ €์žฅ
    await saveAppThemeUseCase(event.type);
    
    // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    emit(state.copyWith(type: event.type));
  }

  // 3. ํ…Œ๋งˆ ํ† ๊ธ€ ์ฒ˜๋ฆฌ
  Future<void> _toggleTheme(event, emit) async {
    // ๋ฐ˜๋Œ€ ํ…Œ๋งˆ ๊ณ„์‚ฐ
    var nextTheme = state.type == ThemeType.light 
        ? ThemeType.dark 
        : ThemeType.light;
    
    // ์ €์žฅํ•˜๊ณ  ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    await saveAppThemeUseCase(nextTheme);
    emit(state.copyWith(type: nextTheme));
  }
}

 

๐Ÿ”„ ์ „์ฒด ํ๋ฆ„ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

 

์‚ฌ์šฉ์ž ๋ฒ„ํŠผ ํด๋ฆญ
    ↓
[์ด๋ฒคํŠธ ๋ฐœ์ƒ] ThemeEvent.toggle()
    ↓
[BLoC์ด ๋ฐ›์Œ] "์˜ค์ผ€์ด, ํ† ๊ธ€ ์ฒ˜๋ฆฌํ• ๊ฒŒ!"
    ↓
[์ฒ˜๋ฆฌ ๋กœ์ง ์‹คํ–‰]
  1. ํ˜„์žฌ ํ…Œ๋งˆ ํ™•์ธ (state.type)
  2. ๋ฐ˜๋Œ€ ํ…Œ๋งˆ ๊ณ„์‚ฐ
  3. ์ €์žฅ์†Œ์— ์ €์žฅ
    ↓
[์ƒˆ ์ƒํƒœ ๋ฐœํ–‰] emit(์ƒˆ๋กœ์šด ThemeState)
    ↓
[UI ์ž๋™ ์—…๋ฐ์ดํŠธ] ํ™”๋ฉด์ด ๋‹คํฌ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝ๋จ!

๐Ÿ’ก ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ

UI์—์„œ BLoC ์‚ฌ์šฉํ•˜๊ธฐ

// 1. BLoC ๊ฐ€์ ธ์˜ค๊ธฐ
final themeBloc = context.read<ThemeBloc>();

// 2. ์ด๋ฒคํŠธ ๋ณด๋‚ด๊ธฐ (๋ฒ„ํŠผ ํด๋ฆญ ์‹œ)
ElevatedButton(
  onPressed: () {
    // "ํ…Œ๋งˆ ํ† ๊ธ€ํ•ด์ค˜!" ์ด๋ฒคํŠธ ์ „์†ก
    themeBloc.add(const ThemeEvent.toggle());
  },
  child: Text('ํ…Œ๋งˆ ๋ณ€๊ฒฝ'),
)

// 3. ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€ํ•˜๊ธฐ
BlocBuilder<ThemeBloc, ThemeState>(
  builder: (context, state) {
    // state๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์ด ๋ถ€๋ถ„ ๋‹ค์‹œ ๊ทธ๋ ค์ง
    return Text(
      'ํ˜„์žฌ ํ…Œ๋งˆ: ${state.type}' // "ํ˜„์žฌ ํ…Œ๋งˆ: dark"
    );
  },
)

๐ŸŽฏ ํ•ต์‹ฌ ์ •๋ฆฌ

๊ตฌ์„ฑ ์š”์†Œ์—ญํ• ๋น„์œ 

Event ์š”์ฒญ/๋ช…๋ น ์ฃผ๋ฌธ์„œ
State ํ˜„์žฌ ์ƒํ™ฉ ์ฃผ๋ฌธ ์ƒํƒœ
BLoC ์ฒ˜๋ฆฌํ•˜๋Š” ์‚ฌ๋žŒ ์ง์›
emit ์ƒํƒœ ๋ณ€๊ฒฝ ์•Œ๋ฆผ ์ง„๋™๋ฒจ ์šธ๋ฆผ
add ์ด๋ฒคํŠธ ๋ณด๋‚ด๊ธฐ ์ฃผ๋ฌธํ•˜๊ธฐ

๐Ÿค” ์ž์ฃผ ํ•˜๋Š” ์งˆ๋ฌธ

Q: ์™œ ์ด๋ ‡๊ฒŒ ๋ณต์žกํ•˜๊ฒŒ ํ•˜๋‚˜์š”? A:

  • UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ
  • ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›€
  • ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๋ช…ํ™•ํ•จ
  • ์—ฌ๋Ÿฌ ํ™”๋ฉด์—์„œ ๊ฐ™์€ ์ƒํƒœ ๊ณต์œ  ๊ฐ€๋Šฅ

Q: Event๋ฅผ ๊ผญ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋‚˜์š”? A: ๋„ค! Event๊ฐ€ ์žˆ์–ด์•ผ:

  • ์–ด๋–ค ๋™์ž‘๋“ค์ด ๊ฐ€๋Šฅํ•œ์ง€ ๋ช…ํ™•ํ•จ
  • ๋‚˜์ค‘์— ์ฝ”๋“œ ์ฐพ๊ธฐ ์‰ฌ์›€
  • ๋””๋ฒ„๊น…ํ•  ๋•Œ ์–ด๋–ค ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์ถ”์  ๊ฐ€๋Šฅ

Q: State๋Š” ์–ธ์ œ ๋ฐ”๋€Œ๋‚˜์š”? A: BLoC์—์„œ emit()์„ ํ˜ธ์ถœํ•  ๋•Œ๋งŒ!


๐Ÿ“ ๊ฐ„๋‹จํ•œ ์—ฐ์Šต ์˜ˆ์‹œ

์นด์šดํ„ฐ ์•ฑ์œผ๋กœ ์—ฐ์Šตํ•ด๋ณผ๊นŒ์š”?

// 1. ์ด๋ฒคํŠธ ์ •์˜
sealed class CounterEvent {
  const factory CounterEvent.increment() = _Increment;
  const factory CounterEvent.decrement() = _Decrement;
}

// 2. ์ƒํƒœ ์ •์˜
class CounterState {
  final int count;
  CounterState({this.count = 0});
}

// 3. BLoC ์ •์˜
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState()) {
    on<CounterEvent>((event, emit) {
      switch (event) {
        case _Increment():
          emit(CounterState(count: state.count + 1));
        case _Decrement():
          emit(CounterState(count: state.count - 1));
      }
    });
  }
}

// 4. UI์—์„œ ์‚ฌ์šฉ
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Column(
      children: [
        Text('${state.count}'), // ์ˆซ์ž ํ‘œ์‹œ
        ElevatedButton(
          onPressed: () {
            context.read<CounterBloc>()
              .add(const CounterEvent.increment());
          },
          child: Text('+'),
        ),
      ],
    );
  },
)

๐ŸŽจ ์ •๋ฆฌํ•˜๋ฉด

  1. Event: "~ํ•ด์ค˜!" (๋ช…๋ น)
  2. BLoC: "์•Œ์•˜์–ด, ์ฒ˜๋ฆฌํ• ๊ฒŒ!" (์ฒ˜๋ฆฌ์ž)
  3. State: "์ง€๊ธˆ ์ƒํ™ฉ์€ ์ด๋ž˜!" (๊ฒฐ๊ณผ)
  4. UI: State๋ฅผ ๋ณด๊ณ  ํ™”๋ฉด์„ ๊ทธ๋ฆผ

ํ๋ฆ„: ๋ฒ„ํŠผ ํด๋ฆญ → Event ๋ฐœ์ƒ → BLoC์ด ์ฒ˜๋ฆฌ → State ๋ณ€๊ฒฝ → UI ์ž๋™ ์—…๋ฐ์ดํŠธ

์ดํ•ด๋˜์…จ๋‚˜์š”? ๋” ๊ถ๊ธˆํ•œ ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด ๋ง์”€ํ•ด์ฃผ์„ธ์š”! ๐Ÿ˜Š

๋ฐ˜์‘ํ˜•
LIST