Flutter WebView 연동 가이드
Inventory 고객센터 웹을 Flutter 앱의 WebView에서 표시하고 사용자 정보를 전달하는 방법입니다.
📦 필수 패키지
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
firebase_auth: ^4.0.0 # 이미 사용 중
webview_flutter: ^4.4.0
🔧 구현 코드
1. WebView 페이지 생성
// lib/screens/inventory_webview_screen.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:webview_flutter/webview_flutter.dart';
class InventoryWebViewScreen extends StatefulWidget {
final String? initialUrl;
const InventoryWebViewScreen({
Key? key,
this.initialUrl,
}) : super(key: key);
@override
State<InventoryWebViewScreen> createState() => _InventoryWebViewScreenState();
}
class _InventoryWebViewScreenState extends State<InventoryWebViewScreen> {
late final WebViewController _controller;
bool _isLoading = true;
bool _authSent = false;
@override
void initState() {
super.initState();
_initializeWebView();
}
void _initializeWebView() {
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() => _isLoading = true);
},
onPageFinished: (String url) {
setState(() => _isLoading = false);
// 페이지 로드 완료 후 인증 정보 전달
_sendAuthToWeb();
},
onWebResourceError: (WebResourceError error) {
debugPrint('WebView 에러: ${error.description}');
},
),
)
..loadRequest(Uri.parse(
widget.initialUrl ?? 'https://goolzy.github.io/inventory/',
));
}
/// 🔑 앱의 인증 정보를 웹으로 전달
Future<void> _sendAuthToWeb() async {
if (_authSent) return; // 중복 전송 방지
try {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
debugPrint('[Inventory] 로그인되지 않은 사용자');
return;
}
// JavaScript로 인증 정보 전달
final jsCode = '''
if (window.setAppAuth) {
window.setAppAuth({
email: '${_escapeJs(user.email ?? '')}',
uid: '${_escapeJs(user.uid)}',
displayName: '${_escapeJs(user.displayName ?? '')}'
});
console.log('[Flutter] 인증 정보 전달 완료');
} else {
console.warn('[Flutter] setAppAuth 함수를 찾을 수 없습니다');
}
''';
await _controller.runJavaScript(jsCode);
_authSent = true;
debugPrint('[Inventory] 인증 정보 전달 완료: ${user.email}');
} catch (e) {
debugPrint('[Inventory] 인증 정보 전달 실패: $e');
}
}
/// JavaScript 문자열 이스케이프 (XSS 방지)
String _escapeJs(String str) {
return str
.replaceAll('\\', '\\\\')
.replaceAll("'", "\\'")
.replaceAll('"', '\\"')
.replaceAll('\n', '\\n')
.replaceAll('\r', '\\r');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('고객센터'),
actions: [
// 새로고침 버튼
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
_authSent = false; // 재전송 허용
_controller.reload();
},
),
],
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
);
}
}
2. 사용 예시
// 앱의 다른 화면에서 호출
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const InventoryWebViewScreen(
initialUrl: 'https://goolzy.github.io/inventory/',
),
),
);
// 특정 페이지로 바로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const InventoryWebViewScreen(
initialUrl: 'https://goolzy.github.io/inventory/bug-report/',
),
),
);
3. 메뉴/설정에서 링크 추가
// lib/screens/settings_screen.dart
ListTile(
leading: const Icon(Icons.help_outline),
title: const Text('FAQ & 고객센터'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const InventoryWebViewScreen(
initialUrl: 'https://goolzy.github.io/inventory/',
),
),
);
},
),
ListTile(
leading: const Icon(Icons.bug_report),
title: const Text('버그 신고'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const InventoryWebViewScreen(
initialUrl: 'https://goolzy.github.io/inventory/bug-report/',
),
),
);
},
),
🧪 테스트 방법
1. 웹 브라우저에서 테스트
# GitHub Pages에 배포 후
https://goolzy.github.io/inventory/
- 로그인 없이 모든 페이지 접근 가능 확인
- 폼 제출 테스트
2. Flutter 앱에서 테스트
// 디버그 콘솔에서 확인할 로그:
// [Inventory] 앱으로부터 인증 정보 수신: {email: user@example.com, ...}
// [Inventory] 사용자 정보 저장 완료
// [Bug Report] 이메일 자동완성: user@example.com
3. 확인 사항
- ✅ 페이지 로드 시 이메일 자동 입력
- ✅ 네비게이션에 이메일 표시
- ✅ 폼 제출 성공
- ✅ 새로고침해도 이메일 유지 (24시간)
🔒 보안 고려사항
✅ 구현된 보안 조치:
- JavaScript 이스케이프 - XSS 방지
- 이메일 검증 - 웹에서 형식 체크
- 24시간 만료 - sessionStorage 타임스탬프
- HTTPS 전용 - GitHub Pages 기본 제공
⚠️ 주의사항:
setAppAuth()는 누구나 호출 가능 (WebView 환경 한정)- 민감한 데이터는 전달하지 말 것
- 결제/금융 정보는 앱에서만 처리
🚀 향후 확장 (옵션)
Firestore 사용 시 (v1.1.0+):
// Custom Token 방식으로 전환 필요
// Firebase Functions 설정 후:
Future<String> _getCustomToken() async {
final idToken = await FirebaseAuth.instance.currentUser?.getIdToken();
final response = await http.post(
Uri.parse('https://YOUR_REGION-YOUR_PROJECT.cloudfunctions.net/createCustomToken'),
headers: {'Authorization': 'Bearer $idToken'},
);
return jsonDecode(response.body)['token'];
}
// WebView URL에 토큰 추가
initialUrl: 'https://goolzy.github.io/inventory/?token=$customToken'
📚 참고 자료
❓ 문제 해결
Q: 이메일이 자동 입력되지 않아요
A:
- 콘솔에서
[Inventory] 앱으로부터 인증 정보 수신로그 확인 setJavaScriptMode(JavaScriptMode.unrestricted)설정 확인- 페이지 로드 완료 후 충분한 시간 대기 (onPageFinished)
Q: sessionStorage가 작동하지 않아요
A:
- iOS:
WKWebView는 기본적으로 sessionStorage 지원 - Android:
WebView는 기본적으로 sessionStorage 지원 - 혹시 시크릿/프라이빗 모드가 아닌지 확인
Q: CORS 에러가 발생해요
A:
- GitHub Pages는 CORS 제한 없음
- JavaScript 주입은 CORS 영향 받지 않음
- 폼 제출 시
mode: 'no-cors'사용 중
✅ 체크리스트
배포 전 확인사항:
- Firebase Auth 로그인 가능
- WebView에서 페이지 로드 가능
- 콘솔에서 인증 정보 전달 로그 확인
- 폼에서 이메일 자동 입력 확인
- 폼 제출 성공 확인
- 앱 종료 후 재시작 시 정상 작동 확인
작성일: 2025-01-06 최종 수정: 2025-01-06