왜 나는 앱 개발을 멈추고 Flutter로 엔진을 만들기 시작했는가
I’m happy to translate the article for you, but I need the actual text of the post. Could you please paste the content you’d like translated (excluding any code blocks or URLs you want to keep unchanged)? Once I have the text, I’ll provide a Korean translation while preserving the original formatting and the source link at the top.
소개
인디 개발자로서 가장 큰 병목 현상은 실력이 아니라 시간입니다. 여러 앱을 유지보수하면 생산성이 급격히 떨어질 수 있습니다.
Source:
문제
몇 달 전, 나는 PyMaster라는 게임화된 앱을 만들기 시작했습니다. 이 앱은 사람들에게 휴대폰으로 파이썬을 배우도록 돕는 것이 목표였습니다. 첫 번째 버전을 출시한 직후, 나는 바로 SQL, JavaScript, 심지어 Rust까지 추가하고 싶었습니다. 가장 명백한 방법은 코드베이스를 복제하고, 로고를 바꾸고, 콘텐츠를 수정한 뒤 새로운 앱을 배포하는 것이었습니다.
시도해 보았습니다: 레포지토리를 복제하고, 몇 가지 이름을 바꾸었으며, 10분도 안 되어 내가 보고 있는 것이 싫어졌습니다.
- 세 개의 별도 코드베이스
- 세 개의 별도 버그 집합
- 스트릭 로직을 고치기 위해 세 번이나 푸시해야 함
솔로 창업자로서는 이것이 로드맵이 아니라, 느린 죽음입니다.
Source:
솔루션: 화이트‑라벨 엔진
다른 앱을 하나 더 만드는 대신, 나는 엔진을 만들었다—무한히 많은 앱으로 컴파일할 수 있는 하나의 코드베이스. Flutter + Riverpod 로 구동되는 화이트‑라벨 교육 플랫폼이다.
UI와 콘텐츠 분리
일반 앱에서는 UI가 너무 많은 것을 알고 있다:
Text("Welcome to Python")또한 특정 언어만 이해하는 강하게 결합된 파서가 포함되어 있다. 이를 화이트‑라벨화하려면 UI가 완전히 무디(무지)해져야 한다—주어진 것을 그대로 렌더링하기만 하면 된다.
핵심 계약: CourseBlueprint
// 모든 앱 플레버가 충족해야 하는 마스터 계약
abstract class CourseBlueprint {
String get appTitle;
String get virtualCurrencyName;
// Theme Engine
Color get brandPrimaryColor;
Color get brandSecondaryColor;
// Logic Injection
CodeParserStrategy get codeParser;
// Per‑app 3rd Party Config
String get analyticsId;
String get aiSystemPrompt;
}각 플레버(Python, SQL, JavaScript, …)는 이 인터페이스를 구현한다. UI는 구체적인 파서를 직접 import 하지 않고, 설정을 통해 제공된 파서를 요청하기만 한다.
예시: Python 플레버
// Python‑전용 구현
class PythonCourseConfig implements CourseBlueprint {
@override
String get appTitle => "PyMaster";
@override
String get virtualCurrencyName => "Tokens";
@override
Color get brandPrimaryColor => const Color(0xFFFFD43B); // Python Yellow
@override
Color get brandSecondaryColor => const Color(0xFF306998); // Python Blue
@override
CodeParserStrategy get codeParser => PythonCodeParser(); // Handles indentation
@override
String get analyticsId => "UA-XXXXX-PYTHON";
@override
String get aiSystemPrompt =>
"You are an expert Python tutor. Explain the error in simple terms.";
}새로운 플레버(예: SQL)를 출시하려면 SqlCourseConfig 를 만들고, 테마 색상을 바꾸고, SqlKeywordParser 를 주입하고, AI 프롬프트만 업데이트하면 된다.
올바른 설정 선택
컴파일 시 Flutter의 --dart-define 플래그를 사용한다. 작은 팩토리가 플레버를 읽어 적절한 설정을 반환한다:
class BlueprintFactory {
static CourseBlueprint getForFlavor(String flavor) {
switch (flavor.toLowerCase()) {
case 'python':
return PythonCourseConfig();
case 'sql':
return SqlCourseConfig();
default:
return PythonCourseConfig(); // Safe fallback
}
}
}흔히 겪는 실수: 빌드 플래그(--dart-define=APP_FLAVOR=sql)를 전달하지 않아 앱이 기본 Python 테마로 돌아가는 경우.
Riverpod 로 전역 접근
Riverpod 은 prop‑drilling 을 없애준다:
// Global provider — overridden at startup
final blueprintProvider = Provider((ref) {
throw UnimplementedError("Must be overridden in main.dart");
});
void main() {
const flavor = String.fromEnvironment('APP_FLAVOR', defaultValue: 'python');
final selectedConfig = BlueprintFactory.getForFlavor(flavor);
runApp(
ProviderScope(
overrides: [
blueprintProvider.overrideWithValue(selectedConfig),
],
child: const MyEduApp(),
),
);
}이제 어떤 위젯도 설정을 읽을 수 있다:
Widget build(BuildContext context, WidgetRef ref) {
final config = ref.watch(blueprintProvider);
return Text(
"Welcome to ${config.appTitle}!",
style: TextStyle(color: config.brandPrimaryColor),
);
}UI는 도메인에 전혀 알지 못한다; 단지 전달받은 것을 그대로 렌더링한다.
혜택
- 단일 진실 원천: 핵심 게이미피케이션 엔진(연속성, XP, 오프라인 진행)에서 버그를 한 번 수정하면, 그 수정이 모든 변형에 전파됩니다.
- 빠른 반복: 새로운 앱을 시작하는 데 몇 주가 아니라 몇 시간이 걸립니다.
- 집중된 노력: 이제 내 시간 대부분을 인프라가 아니라 콘텐츠에 할애합니다.
결과
첫 번째 플러버인 PyMaster를 출시했으며, 현재 Google Play 스토어에 라이브 중입니다. 주요 기능은 다음과 같습니다:
- 게임화된 오프라인 진행
- 동적 테마 엔진
- 내장 AI 튜터
토론
비슷한 구조를 설계해 본 적이 있나요? 다음 중 어떤 것을 선택했나요:
- 앱 플레버
- 모노레포
- 플러그인 아키텍처
당신이 겪은 트레이드‑오프가 궁금합니다. 아래 댓글에 의견을 남겨 주세요.