๐Ÿ“ฆ ํ†ตํ•ฉ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ๊ตฌํ˜„ ๋ฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ - devlink-community/gaesubang-app GitHub Wiki

1. ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ ๊ฐœ์š”

Firebase ์—ฐ๋™ ์•ฑ์—์„œ ํ™”๋ฉด ๊ฐ„ ์ƒํƒœ ์ผ๊ด€์„ฑ ์œ ์ง€๋ฅผ ์œ„ํ•ด ํ†ตํ•ฉ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์‹œ์Šคํ…œ์€ ์•ฑ ๋‚ด ์ค‘์š” ์ด๋ฒคํŠธ(ํ”„๋กœํ•„ ๋ณ€๊ฒฝ, ๊ฒŒ์‹œ๊ธ€ ์—…๋ฐ์ดํŠธ, ๋Œ“๊ธ€ ์ถ”๊ฐ€ ๋“ฑ)๋ฅผ ๋ฐœํ–‰(emit) ํ•˜๊ณ  ๊ตฌ๋…(listen) ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”ง ์ฃผ์š” ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ์ฑ…

๋ฌธ์ œ ์›์ธ ํ•ด๊ฒฐ์ฑ…
๋Œ“๊ธ€/์ข‹์•„์š” ๋ฐ˜์˜ ์ง€์—ฐ ํ™”๋ฉด ๊ฐ„ ๋…๋ฆฝ์  ์ƒํƒœ ๊ด€๋ฆฌ๋กœ ์ธํ•œ ๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ž๋™ ๊ฐฑ์‹  ์‹œ์Šคํ…œ ๋„์ž…
ํ”„๋กœํ•„ ๋ณ€๊ฒฝ ๋ฏธ๋ฐ˜์˜ ์•ฑ ์ „์—ญ์— ๊ฑธ์นœ ์‚ฌ์šฉ์ž ์ •๋ณด ๋™๊ธฐํ™” ๋ถ€์žฌ ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ๋ฅผ ์ „์—ญ ๊ตฌ๋…

2. ์ฃผ์š” ๊ตฌํ˜„ ์ฝ”๋“œ

๐Ÿงฉ ์ด๋ฒคํŠธ ์ •์˜ ์‹œ์Šคํ…œ (AppEvent)

์•ฑ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ค‘์š” ์ด๋ฒคํŠธ๋ฅผ sealed class๋กœ ์ •์˜ํ•˜์—ฌ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค

@freezed
sealed class AppEvent with _$AppEvent {
  // ํ”„๋กœํ•„ ๊ด€๋ จ ์ด๋ฒคํŠธ
  const factory AppEvent.profileUpdated() = ProfileUpdated;
  
  // ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ด€๋ จ ์ด๋ฒคํŠธ
  const factory AppEvent.postCreated(String postId) = PostCreated;
  const factory AppEvent.postUpdated(String postId) = PostUpdated;
  const factory AppEvent.postLiked(String postId) = PostLiked;
  const factory AppEvent.postBookmarked(String postId) = PostBookmarked;
  const factory AppEvent.commentAdded(String postId, String commentId) = CommentAdded;
  const factory AppEvent.commentLiked(String postId, String commentId) = CommentLiked;
}

๐Ÿ”” ์ด๋ฒคํŠธ Notifier (AppEventNotifier)

์ด๋ฒคํŠธ๋ฅผ ์ „์—ญ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” Riverpod Provider

@Riverpod(keepAlive: true)
class AppEventNotifier extends _$AppEventNotifier {
  final List<AppEvent> _events = [];
  
  @override
  List<AppEvent> build() => [];
  
  void emit(AppEvent event) {
    _events.add(event);
    if (_events.length > 20) {
      _events.removeAt(0);
    }
    state = List.from(_events);
    print('๐Ÿ”” AppEventNotifier: ์ด๋ฒคํŠธ ๋ฐœํ–‰ - $event');
  }
  
  bool hasEventOfType<T extends AppEvent>() {
    return _events.any((event) => event is T);
  }
  
  bool hasPostEvent(String postId) {
    return _events.any((event) => 
      event is PostUpdated && event.postId == postId ||
      event is PostLiked && event.postId == postId ||
      // ...๋‹ค๋ฅธ postId ๊ด€๋ จ ์ด๋ฒคํŠธ ์ฒดํฌ
    );
  }
}

3. ์ด๋ฒคํŠธ ๋ฐœํ–‰ ์œ„์น˜(Producer)

ํ”„๋กœํ•„ ํŽธ์ง‘ (ProfileEditNotifier)

// ํ”„๋กœํ•„ ์ €์žฅ ์„ฑ๊ณต ์‹œ
if (result case AsyncData(:final value)) {
  // ...๊ธฐ์กด ์ฝ”๋“œ...
  
  // ์•ฑ ์ด๋ฒคํŠธ ๋ฐœํ–‰: ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ๋จ
  ref.read(appEventNotifierProvider.notifier).emit(
    const AppEvent.profileUpdated(),
  );
  
  debugPrint('โœ… ProfileEditNotifier: ํ”„๋กœํ•„ ์ €์žฅ ์„ฑ๊ณต ๋ฐ ์ด๋ฒคํŠธ ๋ฐœํ–‰');
}

๊ฒŒ์‹œ๊ธ€ ์ข‹์•„์š”/๋ถ๋งˆํฌ (CommunityDetailNotifier)

// ์ข‹์•„์š” ์ฒ˜๋ฆฌ ๋ฐ ์ด๋ฒคํŠธ ๋ฐœํ–‰
Future<void> _handleLike() async {
  // ...๊ธฐ์กด ์ฝ”๋“œ...
  
  // ์ด๋ฒคํŠธ ๋ฐœํ–‰: ์ข‹์•„์š” ์ƒํƒœ ๋ณ€๊ฒฝ๋จ
  ref.read(appEventNotifierProvider.notifier).emit(
    AppEvent.postLiked(_postId),
  );
  
  debugPrint('โœ… CommunityDetailNotifier: ์ข‹์•„์š” ํ† ๊ธ€ ์™„๋ฃŒ ๋ฐ ์ด๋ฒคํŠธ ๋ฐœํ–‰');
}

๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ (CommunityWriteNotifier)

// ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ์„ฑ๊ณต ์‹œ
final createdPostId = await usecase.execute(/*...*/);

// ์ด๋ฒคํŠธ ๋ฐœํ–‰: ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ๋จ
ref.read(appEventNotifierProvider.notifier).emit(
  AppEvent.postCreated(createdPostId),
);

4. ์ด๋ฒคํŠธ ๊ตฌ๋… ์œ„์น˜ (Consumer)

๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก (CommunityListNotifier)

@override
CommunityListState build() {
  // ...๊ธฐ์กด ์ฝ”๋“œ...

  // ์•ฑ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€
  ref.listen(appEventNotifierProvider, (previous, current) {
    if (previous != current) {
      final eventNotifier = ref.read(appEventNotifierProvider.notifier);
      
      // ํ”„๋กœํ•„ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด - ์ž‘์„ฑ์ž ์ •๋ณด ๊ด€๋ จ์ด๋ฏ€๋กœ ๋ชฉ๋ก ๊ฐฑ์‹ 
      if (eventNotifier.hasEventOfType<ProfileUpdated>()) {
        debugPrint('๐Ÿ”„ CommunityListNotifier: ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ๊ฐ์ง€, ๋ชฉ๋ก ๊ฐฑ์‹ ');
        Future.microtask(() => _fetch());
        return;
      }
      
      // ๊ฒŒ์‹œ๊ธ€ ๊ด€๋ จ ์ด๋ฒคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ๋ชฉ๋ก ๊ฐฑ์‹ 
      final hasPostEvents = current.any((event) => 
        event is PostLiked || 
        event is PostBookmarked || 
        event is CommentAdded ||
        event is PostUpdated
      );
      
      if (hasPostEvents) {
        debugPrint('๐Ÿ”„ CommunityListNotifier: ๊ฒŒ์‹œ๊ธ€ ์•ก์…˜ ์ด๋ฒคํŠธ ๊ฐ์ง€, ๋ชฉ๋ก ๊ฐฑ์‹ ');
        Future.microtask(() => _fetch());
      }
    }
  });

  // ...๋‚˜๋จธ์ง€ ์ฝ”๋“œ...
}

๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ (CommunityDetailNotifier)

@override
CommunityDetailState build(String postId) {
  // ...๊ธฐ์กด ์ฝ”๋“œ...

  // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋กœ ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ํ™”๋ฉด ์ƒˆ๋กœ๊ณ ์นจ
  ref.listen(appEventNotifierProvider, (previous, current) {
    if (previous != current) {
      final eventNotifier = ref.read(appEventNotifierProvider.notifier);
      
      // ํ”„๋กœํ•„ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ํ™”๋ฉด ์ƒˆ๋กœ๊ณ ์นจ
      if (eventNotifier.hasEventOfType<ProfileUpdated>()) {
        debugPrint('๐Ÿ”„ CommunityDetailNotifier: ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ๊ฐ์ง€, ๊ฒŒ์‹œ๊ธ€ ์ƒˆ๋กœ๊ณ ์นจ');
        _loadAll();
      }
    }
  });

  // ...๋‚˜๋จธ์ง€ ์ฝ”๋“œ...
}

5. ๊ตฌํ˜„ ํ›„ ์žฅ์ 

์ด ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ๊ตฌํ˜„์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ํ™”๋ฉด ๊ฐ„ ์ƒํƒœ ๋™๊ธฐํ™”: ํ•œ ํ™”๋ฉด์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๊ด€๋ จ๋œ ๋ชจ๋“  ํ™”๋ฉด์ด ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.
  • ๋ถ„๋ฆฌ๋œ ์ฑ…์ž„: ๊ฐ ํ™”๋ฉด์€ ์ž์‹ ์˜ ์ฑ…์ž„๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ๋„ ๋‹ค๋ฅธ ํ™”๋ฉด๊ณผ ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ: ๊ฒŒ์‹œ๊ธ€์— ์ข‹์•„์š”/๋Œ“๊ธ€ ํ›„ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๋ฉด ์ฆ‰์‹œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • ํ”„๋กœํ•„ ๋ณ€๊ฒฝ ์ผ๊ด€์„ฑ: ํ”„๋กœํ•„ ์ •๋ณด ๋ณ€๊ฒฝ์ด ์ฆ‰์‹œ ์•ฑ ์ „์ฒด์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • ๋””๋ฒ„๊น… ์šฉ์ด์„ฑ: ์ด๋ฒคํŠธ ํ๋ฆ„์„ ๋กœ๊ทธ๋กœ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์–ด ๋ฌธ์ œ ๋ฐœ๊ฒฌ์ด ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

6. ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ์š”์•ฝ

์ƒˆ ์ด๋ฒคํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

  1. AppEvent sealed class์— ์ƒˆ ์ด๋ฒคํŠธ ํƒ€์ž… ์ถ”๊ฐ€:
const factory AppEvent.newFeatureUpdated(String featureId) = NewFeatureUpdated;
  1. ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํ–‰ (build_runner):
flutter pub run build_runner build --delete-conflicting-outputs

์ด๋ฒคํŠธ ๋ฐœํ–‰ํ•˜๊ธฐ

// ํŠน์ • Notifier ๋‚ด๋ถ€์—์„œ
void performAction() async {
  // ... ์•ก์…˜ ์ฒ˜๋ฆฌ ์ฝ”๋“œ ...
  
  // ์ด๋ฒคํŠธ ๋ฐœํ–‰
  ref.read(appEventNotifierProvider.notifier).emit(
    AppEvent.newFeatureUpdated("feature-123"),
  );
}

์ด๋ฒคํŠธ ๊ตฌ๋…ํ•˜๊ธฐ

@override
SomeState build() {
  // ... ๋‹ค๋ฅธ ์ฝ”๋“œ ...
  
  // ์ด๋ฒคํŠธ ๊ตฌ๋…
  ref.listen(appEventNotifierProvider, (previous, current) {
    if (previous != current) {
      for (final event in current) {
        if (event is NewFeatureUpdated) {
          // ์ด๋ฒคํŠธ์— ๋งž๋Š” ์ฒ˜๋ฆฌ
          _refreshFeature(event.featureId);
        }
      }
    }
  });
  
  // ... ๋‚˜๋จธ์ง€ ์ฝ”๋“œ ...
}

7. ์ฃผ์˜์‚ฌํ•ญ ๋ฐ ๊ถŒ์žฅ์‚ฌํ•ญ

๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: ์ด๋ฒคํŠธ ๊ฐœ์ˆ˜๋ฅผ ์ œํ•œํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ๊ด€๋ฆฌํ•˜์„ธ์š”. ๋””๋ฒ„๊น… ๋กœ๊ทธ: ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ๊ตฌ๋… ์‹œ ๋ช…ํ™•ํ•œ ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด๋ฒคํŠธ ์ •์˜: ์ด๋ฒคํŠธ๋Š” ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ์ •๋ณด๋งŒ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณผ๋„ํ•œ ๊ฐฑ์‹  ๋ฐฉ์ง€: ๋ชจ๋“  ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ๊ฐฑ์‹ ํ•˜์ง€ ๋ง๊ณ  ํ•„์š”ํ•œ ์ด๋ฒคํŠธ๋งŒ ๊ตฌ๋…ํ•˜์„ธ์š”. ํƒ€์ž… ์•ˆ์ „์„ฑ: sealed class์™€ ํŒจํ„ด ๋งค์นญ์„ ํ™œ์šฉํ•ด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•˜์„ธ์š”.

์ด ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ Firebase์™€ ์—ฐ๋™๋œ ์ƒํƒœ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ์•ฑ ์ „์ฒด์—์„œ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜์„ธ์š”.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ