Flutter


Flutter 가이드 문서는 아래 2가지 라이브러리를 필수로 사용합니다.

  • WebView 패키지 (v6) : flutter_inappwebview

    • v5를 사용하고 계신 클라이언트는 [Migration Guide] 링크를 참고하여 마이그레이션을 권장드립니다.

  • 화면 활성화 여부 판단 패키지 : visibility_detector

    • 런처 화면이 백그라운드 모드로 전환될 때 WebView의 pause , resume 상태 등을 체크하기 위해 필요합니다.

// pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  flutter_inappwebview: ^6.0.0
  visibility_detector: ^0.4.0+2

1단계: 런처 위젯 추가

인앱 게임을 런처에 구동하기 위해서는 WebView를 사용하는 위젯을 생성해야 합니다. 다음 가이드를 따라 진행해주시기 바랍니다.

1. dart 파일 생성

기본적으로 lib 폴더 경로에 런처가 구동될 dart 파일을 생성합니다. 가이드에서는 screens 폴더를 생성 후 bleepy_screen.dart 파일을 생성하였습니다.

2. Navigator 추가

런처 페이지 위젯으로 이동하기 위한 router를 추가합니다.

3. InAppWebViewSettings 옵션 추가

WebView에 사용될 옵션 값을 정의합니다.

4. WebView에 런처 URL 로드

위젯 페이지 내 InAppWebView 를 추가하고 런처 호출 URL을 요청합니다.

// lib/screens/bleepy_screen.dart
class _BleepyLauncherScreenState extends State<BleepyLauncherScreen> {
  InAppWebViewController? webViewController;

  @override
  Widget build(BuildContext context) {
    final GlobalKey webViewKey = GlobalKey();

    // WebView 옵션
    InAppWebViewSettings options = InAppWebViewSettings(
      ...
      javaScriptEnabled: true, // javascript 실행 여부
      useHybridComposition: true, // hybrid 사용을 위한 android 웹뷰 최적화
      clearCache: true,
      ...
    );

    return InAppWebView(
      key: webViewKey,
      initialUrlRequest:
      URLRequest(url: WebUri({런처 URL})),
      initialSettings: options,
      onWebViewCreated: (controller) {
        webViewController = controller;
      }
    )
  }
}

2단계: 위젯 백그라운드 처리 추가

WebView 내에 런처를 실행하면 자바스크립트 로직이 핑 전송 API를 주기적으로 호출합니다. 따라서 위젯이 백그라운드 모드로 이동되면 inactive 상태로 처리되어야 정상입니다. 그러나 런처 위젯에서 다른 위젯으로 Navigator 를 통한 라우팅 시 플랫폼에 따라 예외적인 케이스가 존재합니다.

AOS의 경우 라우팅 시 inactive 상태로 변경되지 않는 이슈가 있어 직접 컨트롤이 필요합니다. 다음 가이드를 따라 코드를 추가해 주세요.

1. LiftCycle 감지 설정 추가

위젯의 LifeCycle 감지를 위해 WidgetsBindingObserver 를 사용합니다.

  • initState 에서 addObserver 를 호출

  • dispose 에서 removeObserver 를 호출

해당 과정은 VisibilityDetector 라이브러리의 정상 동작을 위해 선행되어야 합니다.

// lib/screens/bleepy_screen.dart
class _BleepyLauncherScreenState extends State<BleepyLauncherScreen> 
  with WidgetsBindingObserver{
  ...

  @override
  void initState() {
    super.initState();
    // 최초 위젯 실행 시 호출
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    // 위젯이 메모리에서 제거될 때 호출 
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  ...
}

2. VisibilityDetector 라이브러리 추가

VisibilityDetector 라이브러리를 통해 현재 화면의 활성화 여부 판단이 필요합니다. 화면의 활성화/비활성화 여부에 따라 WebView에 아래와 같은 처리가 필요합니다.

  • 화면 비활성화 시

    • WebView pause

  • 화면 활성화 시

    • WebView resume

VisibilityDetector 라이브러리는 아래 옵션 표를 참고합니다.

Interface function
Type
Description

key

key

필수 입력 값

onVisibilityChanged

void Function(VisibilityInfo)

visibilityInfo 내 visibleFraction으로 활성화 여부 판단

  • double

    • 1.0 - 활성화

    • 0.0 - 비활성화

// lib/screens/bleepy_screen.dart
@override
Widget build(BuildContext context) {
  ...

  // VisibilityDetector 추가
  return VisibilityDetector(
    key: const Key("banner-screen-visibility-detector"),
    onVisibilityChanged: (visibilityInfo) {
      // visibility change event
      var visibleFlag = visibilityInfo.visibleFraction.toInt();

      if(visibleFlag == 1){
        // 화면 보여질 때 실행
        webViewController?.resume();
      } else {
        // 화면 안 보여질 때 실행
        webViewController?.pause();
      }
    },
    child: SafeArea(...)
  );
}

3단계: Javascript Handler 추가

런처와 Flutter간의 통신을 수행하기 위해 Javascript Handler 추가가 필요합니다. 자바스크립트 핸들러 작성 시 handlerName 값은 BlpLauncher 로 작성해야 합니다. 다음은 런처에서 전달하는 이벤트 케이스 입니다.

  • handlerName: BlpLauncher

Type
Description

closeLauncher

블리피 런처의 Back 버튼 UI 클릭 시 (뒤로가기 처리)

launcherLoaded

블리피 런처 로드가 완료된 시점에 호출

timerMissionComplete

타이머 미션이 완료 되었을 때 호출

giftReceived

육성완료 시 호출

// lib/screens/bleepy_screen.dart
InAppWebView(
  key: webViewKey,
  initialUrlRequest:
  URLRequest(url: WebUri(widget.launcherUrl)),
  initialSettings: options,
  onWebViewCreated: (controller) {
    webViewController = controller;

    // 자바스크립트 핸들러 추가
    controller.addJavaScriptHandler(handlerName: 'BlpLauncher', callback: (message) {
      final data = jsonDecode(message[0]);
      final key = data['type'];

      switch (key) {
        case "closeLauncher":
          closeLauncher();
          break;
        case "launcherLoaded":
          launcherLoaded();
          break;
        case "timerMissionComplete":
          timerMissionComplete();
          break;
        case "giftReceived":
          giftReceived();
          break;

        default:
          break;
      }
    });
  }
)
// lib/screens/bleepy_screen.dart

// 런처 종료
void closeLauncher() {
  Navigator.of(context).pop();
}

// 런처 로드 완료
void closeLauncher() {
  // 런처의 로드가 완료된 후 추가적인 처리 필요 시 사용
}

// 타이머 미션 완료
void closeLauncher() {
  // 타이머 미션 완료 후 추가적인 처리 필요 시 사용
}

// 육성완료
void closeLauncher() {
  // 육성완료 후 추가적인 처리 필요 시 사용
}

4단계: 런처 스크린 세로모드 고정 로직 추가

게임은 세로모드 해상도에 최적화 되어 스크린의 세로모드 고정이 필요합니다.

  1. service.dart 패키지를 추가합니다.

  2. SystemChrome.setPreferredOrientations 메서드를 통해 세로 방향으로 고정합니다.

// lib/screens/bleepy_screen.dart
// 화면 고정을 위해 상단에 다음 패키지를 추가
import 'package:flutter/services.dart';

@override
Widget build(BuildContext context) {
  // ...

  // 세로 위아래 방향 고정
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  
  return VisibilityDetector(
    ...
    child: SafeArea(...)
    );
  }

5단계: 화면 전환 설정

1. shouldOverrideUrlLoading 사용

inAppWebView 에서 제공되는 shouldOverrideUrlLoading 를 사용하여 딥링크 정보를 받을 수 있습니다. URI 정보를 받아 Screen 분기 처리를 진행합니다.

  • URI 정보 - navigationAction.request.url

    • scheme - 딥링크 스키마

    • host - 딥링크 호스트

    • queryParameters - 딥링크 쿼리스트링

// WebView 선언 부분 코드 추가
InAppWebView(
  ...
  shouldOverrideUrlLoading: (controller, navigationAction) async {
    // uri 정보
    var uri = navigationAction.request.url!;
    
    // Deep Link scheme 값으로 조건문 추가
    if (uri.scheme == {scheme}) {
      // Deep Link host 값으로 조건문 추가
      if (uri.host == {host}) {
        // Query string 값을 전달하기 위해 arguments 추가
        Navigator.pushNamed(context, '/{host}', arguments: uri.queryParameters);
        return NavigationActionPolicy.CANCEL;
      }
    }
    return NavigationActionPolicy.ALLOW;
  },
  ...
)

개발에 대한 추가 설명이 더 필요하신가요?

"[Client Admin] 로그인 → 오른쪽 하단 채널톡 위젯" 클릭 후 개발 카테고리에 문의 남겨주시면 기술 개발팀에서 확인 후 연락드리겠습니다.

Last updated