Conversation
- Add connectivity_plus dependency for network monitoring - Add ACCESS_NETWORK_STATE permission for Android - Create ConnectivityService for real-time network status - Create OfflineScreen widget for offline error display - Integrate lifecycle management in WebView (5min+ background auto-refresh) - Integrate network status check (offline screen, auto-refresh on reconnect)
There was a problem hiding this comment.
Pull request overview
앱 생명주기 관리와 네트워크 상태 체크 기능을 추가하는 PR입니다. 5분 이상 백그라운드 후 포그라운드 복귀 시 WebView 자동 새로고침, 오프라인 시 에러 화면 표시 및 온라인 복구 시 자동 새로고침 기능을 구현했습니다.
변경사항:
ConnectivityService추가로 네트워크 상태 모니터링OfflineScreen위젯으로 오프라인 에러 UI 제공BottleNoteWebView에 lifecycle 및 네트워크 로직 통합
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/utils/network/connectivity_service.dart | 네트워크 상태 관리 서비스 신규 추가 |
| lib/ui/offline_screen.dart | 오프라인 상태 표시 UI 위젯 신규 추가 |
| lib/web_view/web_view.dart | Lifecycle 관리 및 네트워크 상태 체크 로직 통합 |
| pubspec.yaml | connectivity_plus 패키지 의존성 추가 |
| android/app/src/main/AndroidManifest.xml | ACCESS_NETWORK_STATE 권한 추가 |
| pubspec.lock | 패키지 lock 파일 업데이트 |
| ios/Podfile.lock | iOS 의존성 lock 파일 업데이트 |
Comments suppressed due to low confidence (1)
lib/web_view/web_view.dart:140
- [P0]
_setupPullToRefresh()의onRefresh콜백에서_webviewController를 접근하고 있습니다. PullToRefresh가 WebView 생성 전에 트리거될 수 있으며, 이 경우LateInitializationError로 크래시됩니다.
void _setupPullToRefresh() {
_pullToRefreshController = PullToRefreshController(
settings: PullToRefreshSettings(
color: const Color(0xffe58257),
backgroundColor: Colors.white,
),
onRefresh: () async {
if (Platform.isAndroid) {
_webviewController.reload();
} else if (Platform.isIOS) {
_webviewController.loadUrl(
urlRequest: URLRequest(url: await _webviewController.getUrl()),
);
}
},
);
| void _updateStatus(List<ConnectivityResult> results) { | ||
| final isConnected = | ||
| results.any((result) => result != ConnectivityResult.none); | ||
|
|
||
| final newStatus = isConnected ? NetworkStatus.online : NetworkStatus.offline; | ||
|
|
||
| if (newStatus != _currentStatus) { | ||
| _currentStatus = newStatus; | ||
| _networkStatusController.add(newStatus); | ||
| _logger.d('Network status changed: $newStatus'); | ||
| } | ||
| } |
There was a problem hiding this comment.
[P2] _networkStatusController.add() 호출 시 stream이 이미 닫혔는지 확인하지 않습니다. dispose() 후에도 _connectivity.onConnectivityChanged 이벤트가 발생하면 StateError가 발생할 수 있습니다.
| body: _isOffline && !_initialLoadCompleted | ||
| ? OfflineScreen(onRetry: _retryConnection) | ||
| : _buildBody(colors), |
There was a problem hiding this comment.
[P2] 오프라인 화면 표시 조건이 _isOffline && !_initialLoadCompleted로 제한되어 있습니다. 이미 로드가 완료된 후(_initialLoadCompleted == true) 오프라인 상태가 되면 사용자에게 네트워크 끊김을 알릴 방법이 없습니다. 온라인 복구 시 자동 새로고침만 되고 오프라인 상태에 대한 피드백이 없습니다.
| Future<void> _initializeConnectivity() async { | ||
| await _connectivityService.initialize(); | ||
|
|
||
| final isConnected = await _connectivityService.checkConnectivity(); | ||
| if (!isConnected) { | ||
| setState(() { | ||
| _isOffline = true; | ||
| }); | ||
| } |
There was a problem hiding this comment.
[P1] _initializeConnectivity()에서 비동기 작업 완료 후 setState()를 호출하고 있지만 mounted 체크가 없습니다. 앱 초기화 중 위젯이 dispose되면 크래시가 발생할 수 있습니다.
|
|
||
| // Offline -> Online: auto reload | ||
| if (wasOffline && status == NetworkStatus.online && _initialLoadCompleted) { | ||
| _webviewController.reload(); |
There was a problem hiding this comment.
[P0] _webviewController가 late로 선언되어 있지만 WebView 생성 전(onWebViewCreated 콜백 실행 전)에 네트워크 상태 변경 또는 앱 복귀 시 reload()가 호출될 수 있습니다. 이 경우 LateInitializationError로 앱이 크래시됩니다.
| _webviewController.reload(); | |
| try { | |
| _webviewController.reload(); | |
| } on LateInitializationError catch (e, stackTrace) { | |
| logger.w('WebViewController not initialized yet on connectivity change', e, stackTrace); | |
| } |
| void _handleAppResumed() { | ||
| if (_backgroundTime != null && _initialLoadCompleted) { | ||
| final duration = DateTime.now().difference(_backgroundTime!); | ||
| if (duration > _refreshThreshold) { | ||
| logger.d('Long background duration: ${duration.inSeconds}s. Reloading WebView.'); | ||
| _webviewController.reload(); | ||
| } | ||
| _backgroundTime = null; | ||
| } | ||
| } |
There was a problem hiding this comment.
[P0] _handleAppResumed()에서 _webviewController.reload() 호출 시 컨트롤러 초기화 여부를 확인하지 않습니다. 앱 시작 직후(WebView 생성 전) 백그라운드→포그라운드 전환이 발생하면 LateInitializationError로 크래시됩니다.
| _pullToRefreshController.dispose(); | ||
| _webviewController.dispose(); | ||
| _networkSubscription?.cancel(); | ||
| _connectivityService.dispose(); |
There was a problem hiding this comment.
[P1] dispose()에서 _webviewController.dispose() 전에 _networkSubscription?.cancel()과 _connectivityService.dispose()를 먼저 호출해야 합니다. 현재 순서에서는 네트워크 이벤트가 도착하여 이미 dispose된 _webviewController에 접근할 수 있습니다.
| _pullToRefreshController.dispose(); | |
| _webviewController.dispose(); | |
| _networkSubscription?.cancel(); | |
| _connectivityService.dispose(); | |
| _networkSubscription?.cancel(); | |
| _connectivityService.dispose(); | |
| _pullToRefreshController.dispose(); | |
| _webviewController.dispose(); |
| _networkSubscription = | ||
| _connectivityService.networkStatusStream.listen((status) { | ||
| final wasOffline = _isOffline; | ||
| setState(() { | ||
| _isOffline = status == NetworkStatus.offline; | ||
| }); | ||
|
|
||
| // Offline -> Online: auto reload | ||
| if (wasOffline && status == NetworkStatus.online && _initialLoadCompleted) { | ||
| _webviewController.reload(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
[P0] Stream listener에서 setState()와 _webviewController.reload() 호출 전 mounted 체크가 없습니다. 위젯이 dispose된 후 네트워크 상태가 변경되면 크래시가 발생합니다.
| Future<void> _retryConnection() async { | ||
| final isConnected = await _connectivityService.checkConnectivity(); | ||
| if (isConnected) { | ||
| setState(() { | ||
| _isOffline = false; | ||
| }); | ||
| if (_initialLoadCompleted) { | ||
| _webviewController.reload(); | ||
| } | ||
| } else { | ||
| if (mounted) { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| const SnackBar( | ||
| content: Text('인터넷 연결을 확인해 주세요.'), | ||
| duration: Duration(seconds: 2), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
[P1] _retryConnection()에서 비동기 작업 완료 후 setState()와 _webviewController.reload() 호출 전 mounted 체크가 없습니다. 재시도 중 위젯이 dispose되면 크래시가 발생합니다.
- Add _isWebViewCreated flag to prevent LateInitializationError - Add mounted checks before setState() in async callbacks - Fix dispose() order: cancel network subscription before webview dispose - Add _isDisposed flag in ConnectivityService to prevent StateError
리뷰 코멘트 수정 완료 (f5f42aa)모든 Copilot 리뷰 이슈를 수정했습니다: P0 (크리티컬)
P1 (중요)
P2 (권장)
|
변경 사항
connectivity_plus패키지 추가 (네트워크 모니터링)ACCESS_NETWORK_STATE권한 추가주요 변경 파일
lib/utils/network/connectivity_service.dartlib/ui/offline_screen.dartlib/web_view/web_view.dart테스트
테스트 시나리오
스크린샷 (UI 변경 시)
오프라인 화면 (WiFi 아이콘 + "인터넷 연결 없음" + 다시 시도 버튼)
관련 이슈
N/A
🤖 Generated with Claude Code