Flutter 앱 개발기 (1/5) — Flutter 3개 프로젝트의 공유 패턴: UI·상태관리 재사용

콰이어트, 마인스위퍼, 성장 일지 — 세 프로젝트가 공유하는 Flutter 패턴 구조


핵심 요약

  • 콰이어트(Riverpod + Hive), 마인스위퍼(BLoC + Hive), 성장 일지(Riverpod + SQLite) 3개 Flutter 프로젝트의 공유 패턴과 재사용 전략을 정리했다
  • 테마 시스템, 설정 패널, 커스텀 위젯 등 UI 패턴을 프로젝트 간 공유하여 개발 중복을 줄였다
  • 프로젝트 간 [tech] 관계를 온톨로지에 명시하면, 한 프로젝트의 버그 수정이 다른 프로젝트의 동일 버그를 예방하는 구조가 된다

배경

Flutter 프로젝트를 하나만 운영하면 패턴 재사용을 고민할 이유가 없습니다. 세 개가 되면 이야기가 달라집니다. 비슷한 UI를 세 번 만들고, 비슷한 버그를 세 번 고치고, 비슷한 설정 화면을 세 번 설계하게 됩니다.

콰이어트(텍스트 북리더), 마인스위퍼(육각 지뢰찾기), 성장 일지(GrowNote) — 세 앱은 목적이 다르지만 Flutter 스택을 공유합니다. 이 공유 지점을 체계화하면 개발 속도와 품질 모두 개선됩니다.


본문

1. 공유 UI 패턴 — 테마, 설정, 위젯

테마 시스템: 세 앱 모두 다크/라이트 모드를 지원합니다. 콰이어트에서 처음 설계한 테마 전환 구조(ThemeData 생성 함수 + 사용자 설정 저장)를 마인스위퍼와 성장 일지에 그대로 적용했습니다. 앱별 색상 팔레트만 다르고 구조는 동일합니다.

// 공통 테마 팩토리 패턴 (앱별 색상만 주입)
ThemeData buildAppTheme({
  required ColorScheme colorScheme,
  required TextTheme textTheme,
}) {
  return ThemeData(
    colorScheme: colorScheme,
    textTheme: textTheme,
    useMaterial3: true,
  );
}

설정 패널: 설정 화면의 레이아웃 패턴이 수렴합니다. 섹션 헤더, 토글 스위치, 슬라이더, 선택지 리스트 — 이 조합이 반복됩니다. 콰이어트에서 만든 SettingsTile, SettingsSection 위젯을 일반화하여 다른 프로젝트에서 재사용합니다.

커스텀 위젯: 로딩 인디케이터, 빈 상태 화면, 에러 화면 같은 공통 위젯도 패턴이 같습니다. 앱별 일러스트나 문구만 다를 뿐 구조는 동일합니다.


2. 상태 관리 비교 — Riverpod vs BLoC, 언제 무엇을

세 프로젝트는 두 가지 상태 관리 도구를 사용합니다.

프로젝트 도구 상태 특성
콰이어트 Riverpod 반응형, 설정 중심
마인스위퍼 BLoC 이벤트 기반, 트랜잭션 중심
성장 일지 Riverpod 데이터 기반, CRUD 중심

선택 기준은 명확합니다. 데이터가 흐르는 앱(설정 변경 → UI 반영, DB 변경 → 목록 갱신)에는 Riverpod의 선언적 반응성이 적합합니다. 이벤트가 상태를 바꾸는 앱(사용자 입력 → 복잡한 로직 → 상태 전이)에는 BLoC의 명시적 흐름이 적합합니다.

// Riverpod — 반응형 데이터 흐름 예시 (성장 일지)
final growthEntriesProvider = StreamProvider<List<GrowthEntry>>((ref) {
  final db = ref.watch(databaseProvider);
  return db.watchAllEntries();
});

// BLoC — 이벤트 → 상태 전이 예시 (마인스위퍼)
class GameBloc extends Bloc<GameEvent, GameState> {
  GameBloc() : super(GameInitial()) {
    on<RevealCell>(_onRevealCell);
    on<FlagCell>(_onFlagCell);
    on<ResetGame>(_onResetGame);
  }
}

두 도구를 병행하면 학습 비용이 드는 것은 사실입니다. 그러나 문제에 맞는 도구를 선택하는 것이 장기적으로 유지보수 비용을 낮춥니다.


3. 온톨로지의 [tech] 관계 — 왜 명시하는가

프로젝트 온톨로지에 이런 관계가 정의되어 있습니다: - 콰이어트 ↔ 마인스위퍼: Flutter 스택 공유 — UI 패턴, 상태 관리 [tech] - 콰이어트 ↔ 성장 일지: Flutter 스택 공유 — UI 패턴, 상태 관리 [tech] - 마인스위퍼 ↔ 성장 일지: Flutter 스택 공유 — 위젯 패턴 재사용 [tech]

이 명시가 왜 필요한가? 콰이어트에서 테마 관련 버그를 수정하면, "마인스위퍼와 성장 일지에도 같은 패턴이 있으므로 확인이 필요하다"는 맥락이 자동으로 활성화됩니다. 온톨로지의 관계 정의는 단순한 문서가 아니라 작업 범위를 결정하는 실행 가능한 메타데이터입니다.


4. 크로스 프로젝트 학습 — 버그 전파 방지

실제 사례: 콰이어트에서 다크 모드 전환 시 특정 위젯의 색상이 즉시 반영되지 않는 버그가 있었습니다. 원인은 const로 선언된 위젯이 테마 변경을 감지하지 못하는 것이었습니다.

// 문제 — const 위젯은 테마 변경을 감지하지 못함
const Icon(Icons.settings, color: Colors.grey)  // ❌

// 수정 — const 제거하여 테마 반영 허용
Icon(Icons.settings, color: Theme.of(context).iconTheme.color)  // ✓

이 버그를 수정한 뒤, 같은 패턴을 사용하는 마인스위퍼와 성장 일지를 점검했습니다. 마인스위퍼에서 동일한 문제가 잠복해 있었고, 사전에 수정할 수 있었습니다. 버그가 사용자에게 도달하기 전에 차단한 셈입니다.

프로젝트 간 관계를 인지하지 않으면 이런 크로스 프로젝트 학습은 불가능합니다. 각 프로젝트를 완전히 독립적으로 관리했다면 같은 버그를 세 번 경험했을 것입니다.


5. 공유 테스트 패턴

세 프로젝트에서 반복되는 테스트 패턴:

위젯 테스트: 설정 화면이 올바르게 렌더링되는지, 토글 전환이 상태를 변경하는지. 테스트 구조가 거의 동일하여 한 프로젝트에서 작성한 테스트 템플릿을 다른 프로젝트에 복사 후 수정합니다.

상태 관리 테스트: Riverpod의 경우 ProviderContainer를 생성하고 상태 변화를 검증하는 패턴, BLoC의 경우 blocTest를 사용하는 패턴이 각각 정형화되어 있습니다.

// Riverpod 테스트 패턴 (공통 구조)
test('setting change reflects in state', () async {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  // ...
});

// BLoC 테스트 패턴 (공통 구조)
blocTest<GameBloc, GameState>(
  'emits [GamePlaying] when RevealCell added',
  build: () => GameBloc(),
  act: (bloc) => bloc.add(RevealCell(row: 0, col: 0)),
  expect: () => [isA<GamePlaying>()],
);

골든 테스트: UI 회귀를 잡기 위한 스크린샷 비교 테스트. 테마 변경, 다크/라이트 모드 전환, 다양한 화면 크기에서의 렌더링을 검증합니다. 이 패턴도 세 프로젝트에서 동일하게 적용합니다.

테스트 패턴을 공유하면 새 프로젝트의 테스트 셋업 시간이 크게 줄어듭니다.


시행착오

과도한 추상화 시도: 세 프로젝트에서 공유하는 코드를 별도 패키지로 추출하려 했습니다. 하지만 앱별 커스터마이징이 많아서 추상화 비용이 재사용 이득을 초과했습니다. 코드 복사 후 수정이 현실적으로 더 효율적이었습니다.

상태 관리 통일 시도: 하나의 도구로 통일하면 학습 비용이 줄 것이라는 가정으로 마인스위퍼도 Riverpod으로 시작했다가 이벤트 처리가 어색해져서 BLoC으로 전환했습니다. 도구 통일보다 문제에 맞는 도구 선택이 더 중요합니다.


마무리

Flutter 멀티 프로젝트 운영에서 핵심 원칙은 "패턴은 공유하되 도구는 문제에 맞게 선택한다"입니다. 테마 시스템, 설정 패널, 테스트 구조 같은 패턴은 프로젝트 간에 이전할 수 있습니다. 그러나 상태 관리 도구나 저장소 선택은 각 앱의 데이터 특성에 따라 달라져야 합니다.

온톨로지에 프로젝트 간 [tech] 관계를 명시하는 것은 단순한 문서화가 아닙니다. 한 프로젝트의 경험이 다른 프로젝트로 자동 전파되는 구조를 만드는 것입니다. 프로젝트가 늘어날수록 이 관계 정의의 가치가 커집니다.

시리즈 전체 안내: 시리즈 목차

댓글

이 블로그의 인기 게시물

"LLM 핵심 학습 (1/6) — 기본: 토큰화·임베딩·어텐션·위치 인코딩"

"LLM 핵심 학습 (2/6) — 파인튜닝: LoRA·QLoRA·증류·Adapter"

"ML 기초 학습 (1/9) — 머신러닝과 sklearn: 학습의 좌표계"