Flutter Web 고려사항 모음

고랭지참치·2025년 4월 9일
1

Flutter

목록 보기
14/24

Flutter web 빌드 런

  • 아래 명령어로 크롬 브라우저 빌드 가능
  • 웹 빌드는 그때그때마다 잉여 포트를 랜덤으로 가져가서 사용하니, 필요한 포트넘버가 있다면 —web-port=3000 ****와 같이 지정해서 빌드하도록 하자
flutter run -d chrome --web-port=3000

Flutter web ‘#’ ur제거

  • main.dart 위치에서 아래 함수로 Url에서 기본으로 달려있는 # 제거 가능
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

usePathUrlStrategy();

Web API

  • 무려 15일전 (2024년 7월 31일 기준)에 web 라이브러리가 배포됐다.
    https://pub.dev/packages/web/versions
  • 기존에는 dart.html 라이브러리를 통해서 제한적이고 안전하지 않은 방법으로 web환경의 js함수를 사용해야 했으나, web라이브러리를 통해 인가된 방법으로 브라우저 기능에 접근할 수 있게 되었다.

웹 스토리지 접근 및 저장 예시 코드

import 'package:web/web.dart';

// 세션 스토리지 저장
_sessionStore({required String sessionStorage}) {
  window.sessionStorage['session_value'] = sessionStorage;
}

// 쿠키 저장
_cookieStorage({required String cookies}) {
  window.document.cookie =
      'username=$cookies; expires=Thu, 18 Dec 2024 12:00:00 UTC';
}

// 로컬 스토리지 저장
_localStorage({required String localStorage}) {
  window.localStorage['local_value'] = localStorage;
}
// 로컬 스토리지에서 특정 아이템 값을 확인해 라우트
routeByLocalStorage({required BuildContext context}) {
  final String? isStorageExiest = window.localStorage.getItem('local_value');
  logger.e(isStorageExiest);

  if (isStorageExiest == null) {
    context.goNamed(LoginScreen.route);
  } else {
    context.goNamed(HomeScreen.route);
  }
}
  • 위 코드와 같이 스토리지 접근 가능!
window.location.reload();
  • 위 함수로는 새로고침도 가능

❓ Flutter SecureStorage를 웹에서 사용 가능?

- Flutter Secure Storage는 웹을 지원한다고는 되어있으나, 현재 실험적인 단계라고 한다.

await SecureStorageService.setString(key: 'new', value: '마이 네임 이스 민우');

새로고침 시 AppState초기화

  • 기존 앱 서비스 개발때처럼 AuthState를 유지하는 provider를 생성하여 사용자 인증 상태를 감시하고, redirect와 연결시키려 했다.
  • 이슈 발견 : 웹 새로고침을 할 때마다 provider의 state가 모두 초기화되어, 인증완료 상태였음에도 redirect가 계속 발생한다.

해결 접근 방법 →

웹이 새로고침 되는 것을 감시하는 listener를 만들고, 감지될 때 마다 token을 확인하여 문제가 없으면 authState 값을 할당하자.

새로고침 감시 로직 생성

  • 새로고침이 되는 것을 html stream함수를 사용해 감시한다.
    abstract class BrowserEvents {
      ///`Browser Events`
      static Stream? get onBeforeUnload => html.window.onBeforeUnload;
      static Stream? get onUnload => html.window.onUnload;
      static Stream? get onReload => html.window.onLoad;
    
      //Browser LocalStorage Values
      static final html.Storage _localStorage = html.window.localStorage;
    
      static bool get isWebReloaded {
        String? isReloaded = _localStorage['isReload'];
        if (isReloaded == null) return false;
        return isReloaded == 'true';
      }
    
      static void setWebPPageReloadValue(bool value) {
        _localStorage['isReload'] = value.toString().toLowerCase();
      }
    
      static void clearWebPageReloadValue() {
        _localStorage.remove('isReload');
      }
    }
    
    abstract class WebReloadDetector {
      WebReloadDetector._();
    
      /// Call `onBrowserReload`  on Top Level Widget only Once
      static void onReload(Future Function() reloadCallback) {
        if (!kIsWeb) return;
        WidgetsFlutterBinding.ensureInitialized();
        WidgetsBinding.instance.addPostFrameCallback((_) async {
          if (BrowserEvents.isWebReloaded) {
            BrowserEvents.setWebPPageReloadValue(false);
            await reloadCallback.call();
          }
          BrowserEvents.onUnload
              ?.listen((_) => BrowserEvents.setWebPPageReloadValue(true));
        });
      }
    }
    

    main.dart 트리에 감시 클래스 로직 / state 최신화 시켜주기

    class _EagerInitialize extends HookConsumerWidget {
      final Widget child;
      const _EagerInitialize({required this.child});
    
      
      Widget build(BuildContext context, WidgetRef ref) {
        useEffect(
          () {
            WidgetsBinding.instance.addPostFrameCallback((_) {
              WebReloadDetector.onReload(() async {
                final bool isTokenExiest = await BrowserAuthEvent.isTokenExiest();
                if (!context.mounted) return;
                if (!isTokenExiest) context.goNamed(LoginScreen.route);
                ref
                    .read(authProvider.notifier)
                    .setAuthState(AuthState.authenticated);
              });
            });
            return null;
          },
          [],
        );
    
        return child;
      }
    }
    

트러블 슈팅

CORS 이슈

💡 참고 블로그 : https://inpa.tistory.com/entry/WEB-📚-CORS-💯-정리-해결-방법-👏[👏](https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F)
  • 도메인 제한이 걸려져 있다면.
    • https://dev.co.kr
    • https://qa.co.kr
    • localHost:3000
  • 위 도메인이 아닌 Origin refer에서 요청, 응답이 있는 경우 브라우저에서 차단해버린다.
    • 앱은 브라우저를 타지 않기 때문에 Origin 도메인을 서버쪽에서 추가로 열어주지 않더라도 접근이 가능하다.
  • Flutter web을 디버깅 하면서 사내 API를 써야 하는 경우 서버팀에 열러진 도메인을 문의하도록 하고, 특정 도메인을 사용하는데 제한이 있는 상황이라면 localHost로 접근할 수 있는지 물어보자.

WebAssembly

  • Last updated May 14, 2024
  • Available on the Flutter stable channel
  • Pre-built Flutter web app using Wasm Wonderous
  • 우리는 stable 버전 사용하고 있어서 지원함.
  • 다만 브라우저가 wsamGC를 지원해야함.

왜 쓰면 좋은가?

  • 웹에서 네이티브 성능 추구
  • 구글 크롬팀과 플러터팀이 협력하여 Dart 같은 고급 언어를 위한 wasm 지원을 정의하여 확정된 표준인 WsamGC proposals이 탄생
  • WasmGC code를 생성하기 위해 Dart 컴파일러 백엔드를 추가하여 Wasm 모듈로 앱 코드와 Flutter 렌더링 엔진을 모두 컴파일하고 실행

효율

  • 결과적으로 모바일 및 데스크톱에서 훨씬 향상된 성능 확인
  • 내부 벤치마크(M1, chrome)에 위 예시 웹사이트의 프레임 렌더링 시간은
    일반적으로 2배, 최악의 경우 3배 향상
  • 렌더링 성능은 다음 프레임을 렌더링 하는 데 할당된 시간(프레임예산)을 초과하면 버벅거림
  • 따라서 애니메이션이나 풍부한 전환 기능을 갖춘 앱에서 매우 중요
  • 실제로 눈으로 봤을 때 차이 GIF img1.daumcdn.net
  • 그 외적으로 조금 더 네이티브에 가까운 기능들을 사용할 수 있으나 크게 의미 없어 보임

참조

Landing Flutter 3.22 and Dart 3.4 at Google I/O 2024

Support for WebAssembly (Wasm)

profile
소프트웨어 엔지니어 / Flutter 개발자

1개의 댓글

comment-user-thumbnail
2025년 7월 8일

아니 또 흘러흘러 플러터웹 검색하다 민우님글에 흘러들어와버렷네요

답글 달기