비행기 안에서 앱을 켜보신 QA 천재 개발자님의 피드백으로부터 시작된 이 이야기. 기내 Wi-Fi가 없는 환경에서 앱이 정상 작동하지 않는다는 아주 소중한 리포트를 받고, 고민을 시작했다.
"우리 앱은 Vue3 + Flutter인데, 네트워크 감지를 웹에서 할까, 아니면 네이티브(Futter)에서 할까?"
웹에서는 아주 간단하게 네트워크 감지를 할 수 있는 내장 API가 존재한다.
브라우저가 네트워크에 연결되어 있는지 확인하는 내장 API navigator.onLine
사용하기
브라우저에 네트워크 상태가 바뀔 때 연결 상태 변화 감지할수 있는 addEventListener
사용하기
초기값을 설정하고 네트워크 변경을 확인하여 UI를 변경하거나 네트워크 재시도 요청 시도 하기
이것저것 사부작거린 코드 내용들
<script setup>
onMounted(() => {
console.log('현재 상태:', navigator.onLine ? '온라인' : '오프라인');
window.addEventListener('online', () => {
console.log('📡 온라인으로 복구됨');
});
window.addEventListener('offline', () => {
console.log('📴 오프라인으로 전환됨');
});
}
const isOnline = ref(navigator.onLine);
const updateNetworkStatus = () => {
isOnline.value = navigator.onLine;
console.log('####', navigator.onLine);
/* if (isOnline.value) {
retryCallbacks.forEach((cb) => cb());
}*/
};
</script>
이렇게 순조롭게 일이 풀리면 내가 아니지?
생각해보니 앱 진입 시점에서 네트워크가 없다면 웹을 가져오지도 못하기 때문에 원하는 화면을 보여줄수도 없잖아!!!!
또한 인터넷 연결 여부를 완전히 보장하지도 않는다. 예로 wifi에 연결되어 있더라도 인터넷이 안될수있다.
아주 만족스럽게도 flutter에서 제공하는 패키지가 있다.
connectivity_plus
https://pub.dev/packages/connectivity_plus
패키지에서 제공하는 플랫폼인데 얼마나 아름다운가!
그리고 요구되는 버전도 잘 파악하길 바란다.
해당 패키지를 통해 Wi-Fi, Mobile, No Connection 등의 구분이 명확하게 가능하고, javascript보다 세밀하게 제어가 가능하다.
그래서 내가 생각한 구성
1. 실시간 감지하는 감시자 생성
2. 네트워크 상태의 따라 화면 분기처리
-> 이론은 항상 완벽하다.
자 그럼 시작해볼까
network_connectivity_observer.dart
import 'package:connectivity_plus/connectivity_plus.dart';
enum Status {
available,
unavailable,
}
class NetworkConnectivityObserver {
//connectivity_plus 패키지를 이용해서 Wi-Fi, LTE 등 모든 연결 상태를 감지
final Connectivity _connectivity = Connectivity();
Stream<Status> observe() async* {
// 처음 연결 상태 체크
final initial = await _connectivity.checkConnectivity();
yield _convertToStatus(initial); // 리스트로 감싸서 전달
// 이후 상태 변경 감지
yield* _connectivity.onConnectivityChanged.map((List<ConnectivityResult> results) {
return _convertToStatus(results); // 리스트로 처리
});
}
// 리스트 형태로 연결 상태 확인
Status _convertToStatus(List<ConnectivityResult> results) {
// 리스트에서 하나라도 연결되어 있으면 available로 처리
for (var result in results) {
if (result != ConnectivityResult.none) {
return Status.available;
}
}
return Status.unavailable;
}
}
webview_container.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:safeit_mobile_native/app/safeit_app/webview/views/screen/offline_screen.dart';
import 'package:safeit_mobile_native/app/safeit_app/webview/views/screen/webview.dart';
import 'package:safeit_mobile_native/app/utils/service/network_connectivity_observer.dart';
class WebviewContainer extends StatefulWidget{
const WebviewContainer({super.key});
State<WebviewContainer> createState() => _WebviewContainerState();
}
class _WebviewContainerState extends State<WebviewContainer> {
final observer = NetworkConnectivityObserver();
late StreamSubscription<Status> _subscription;
Status? _status;
void initState() {
super.initState();
_subscription = observer.observe().listen((status) {
setState(() {
_status = status;
});
});
}
void dispose() {
_subscription.cancel();
super.dispose();
}
Widget build(BuildContext context) {
if(_status == Status.unavailable){
return const OfflineScreen();
}
return Webview();
}
}
offline_screen.dart
class OfflineScreen extends StatelessWidget {
final VoidCallback? onRetry;
const OfflineScreen({super.key, this.onRetry});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: AppBar(elevation: 0),
),
body: SafeArea(
bottom: Platform.isAndroid ? true : false,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/images/icons/no_signal.svg",
width: MediaQuery.sizeOf(context).width / 5,
),
const SizedBox(height: 20),
Text("인터넷 연결이 끊어졌습니다.", style: TextStyle(fontSize: 22, height: 2)),
Text("원활한 이용을 위해 네트워크 상태를"),
Text("확인한 후 다시 시도해 주세요."),
const SizedBox(height: 50),
SizedBox(
width: 120,
child: Button(
text: '재시도',
func: () {
// 추후 재시도 기능 구현 가능
},
borderColor: const Color(0xFF2e81ff),
backgroundColor: Colors.white,
fontColor: const Color(0xFF2e81ff),
),
),
],
),
),
),
);
}
}
Flutter로 구현하면서 웹보다 훨씬 정교하고 강력한 제어가 가능하다는 걸 느꼈고 마음이 매우 편안했다.
인터넷이 안 되더라도 우아하게 안내 가넝!
이걸로 며칠을 고민하다니!!!.. 따흐흑 그래도 인터넷 제발 끊기지는 말아죠..