Flutter 웹 흰 화면을 광고·인앱결제 분기로 해결
목차
web에서 앱을 열었더니 그냥 흰 화면. 아무것도 안 나오는 상태였다.
Flutter는 멀티 플랫폼 프레임워크라는 타이틀답게 단일 코드베이스로 mobile과 web을 동시에 지원한다. 근데 이게 "그냥 된다"는 보장이 아니라는 걸 한 번씩은 경험하게 된다. 이번 빈 화면 이슈도 바로 그 지점에서 터진 케이스였다.
왜 빈 화면이 나왔나
원인은 단순했다. mobile-only 패키지가 web 빌드에 그대로 import되면서 초기화 단계에서 조용히 죽고 있었다.
Flutter web은 dart2js로 컴파일되기 때문에 dart:io 기반 패키지나 native channel을 직접 쓰는 패키지들은 web에서 런타임 에러를 뱉거나 아예 초기화 자체를 실패시킨다. 무서운 건 이게 에러 메시지 없이 그냥 빈 화면으로 끝나는 경우가 꽤 있다는 점이다. 콘솔을 열지 않으면 뭔가 잘못됐는지조차 모를 수 있다.
이번 케이스에서 문제가 된 구조는 대략 이랬다.
// 기존 main.dart — mobile/web 구분 없이 단일 진입점
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await IAPRepository.init(); // mobile-only native 패키지
await AdConfig.init(); // mobile-only 광고 SDK
runApp(const MyApp());
}
IAPRepository나 AdConfig 쪽에서 mobile native SDK를 import하고 있으니 web 빌드가 저 시점에서 멈추거나 undefined reference로 조용히 실패한다.
작업 내용: entry 분리 + conditional import
해결 방향은 두 가지가 같이 적용됐다.
1. web entry 분리
main.dart와 main_web.dart를 분리해서 빌드 타겟 자체를 나눴다. web 전용 진입점에서는 mobile-only 초기화 코드를 아예 안 부른다.
// main_web.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// IAP, Ad 초기화 없음
runApp(const MyAppWeb());
}
app_web.dart와 app_router_web.dart도 분리됐는데, 이건 단순 entry 분리를 넘어서 web 전용 앱 셸과 라우터 구성을 별도로 가져가겠다는 의도다. mobile과 web의 네비게이션 구조나 레이아웃이 갈릴 수 있으니 라우터 레벨에서 나눠두면 나중에 web 전용 기능을 추가하거나 빼기가 훨씬 편하다.
2. conditional import
단순히 entry만 나눈다고 끝이 아니다. 공유 코드 어딘가에서 mobile-only 패키지를 여전히 import하고 있으면 web 빌드 자체가 실패한다. 이때 쓰는 패턴이 conditional import다.
// ad_config.dart 또는 iap_repository.dart 상단
export 'ad_config_stub.dart'
if (dart.library.io) 'ad_config_mobile.dart';
stub 파일은 인터페이스만 맞춰두고 실제 구현은 비워두는 방식이다.
// ad_config_stub.dart
class AdConfig {
static Future<void> init() async {}
}
web에서는 stub이 들어가고 mobile에서는 실제 구현이 들어가니 빌드 에러도 없고 런타임 에러도 없다.
변경된 파일들이 하는 일
| 파일 | 역할 | 이번 변경 의미 |
|---|---|---|
main.dart |
mobile 진입점 | web 관련 코드 제거, mobile 전용으로 정리 |
main_web.dart |
web 진입점 신규 | mobile-only 초기화 없는 web 전용 부트스트랩 |
app_web.dart |
web 앱 셸 | web 전용 앱 루트 위젯 분리 |
app_router_web.dart |
web 라우터 | web 네비게이션 구조 분리 |
ad_config.dart |
광고 SDK 설정 | conditional import로 web stub 분기 |
iap_repository.dart |
인앱결제 저장소 | conditional import로 web stub 분기 |
이런 구조 분리를 미리 못 한 이유
솔직히 말하면 초기에 "web도 지원해야 하나?" 가 명확하지 않은 상태로 mobile 코드가 먼저 쌓였다. 이런 상황은 팀에서 흔히 생긴다. mobile 먼저 치고 나가다가 나중에 "web도 돼야 해"가 추가되는 패턴. 그때 이미 진입점, 라우터, 각종 init 코드가 mobile 가정으로 굳어져 있는 상태라서 수습이 좀 더 번거로워진다.
코드리뷰 때 늘 하는 얘기 중 하나가 "플랫폼 의존성은 최대한 경계로 밀어내라"는 건데, 막상 초기 구현에서 그걸 지키기가 어렵다. 속도 앞에서 타협이 일어나는 지점이기도 하다.
이번 작업으로 web entry가 깔끔하게 분리됐으니 앞으로 web 전용 기능을 붙이거나 mobile 전용 SDK를 추가할 때 서로 건드릴 일이 줄었다. conditional import 패턴도 한 번 잡아두면 이후에 같은 문제가 생겨도 틀이 있으니 빠르게 적용할 수 있다.
빈 화면 하나가 꽤 의미 있는 구조 정리로 이어진 케이스였다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.