Flutter - WebView - 웹 뷰를 이용하여 웹 페이지 표시하기 (webview_flutter 4.0 이상)

반응형

 

Overview

  • 플러터를 이용하여 웹 페이지를 표시하고, 웹 문서를 이용하여 화면 표시가 가능하도록 합니다.

<웹뷰를 활용한 블로그 접속 화면>


포스팅을 보기 전에...

이 글은 webview_flutter 패키지를 통해 안드로이드 가상 에뮬레이터로 웹 페이지를 표시하는 것을 목표로 합니다.

또한 포스팅에서 설명하는 소스 코드는 웹뷰 패키지가 4.0 이상의 버전으로 작성하였습니다.

3.0 버전으로 작성된 웹뷰 코드와 많이 다를 수 있습니다.

이 포스팅에서의 웹 뷰 구현은 WebView가 아닌 WebViewWidget을 사용하며, 컨트롤러를 이용합니다.

WebView는 Deprecated 되어서 이후 버전에서는 사용이 어려울 수 있습니다.

 

포스팅에서의 환경

  • Flutter Version : 3.11.0-6.0.pre.108
  • webview_flutter: ^4.2.0
  • webview_flutter_android: ^3.7.0
  • webview_flutter_wkwebview: ^3.4.3
  • Android Emulator : Pixel_3a_API_33_x86_64

 

예시 -  webview_flutter 3.0에서의 웹 뷰 구현 코드

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebView(
        initialUrl : 'https://luvris2.tistory.com/',
      ),
    );
  }

 

예시 - webview_flutter 4.0 이상, 이 포스팅에서의 다루는 코드

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(
        controller: _controller,
      ),
    );
  }
}

웹 뷰 설정

프로젝트에 웹 뷰 패키지 추가

  • 터미널에서 아래의 명령어 입력
  • 웹뷰 사용시 접속하는 디바이스가 안드로이드일지 iOS일지 모르므로 디바이스에 따라 보여줄 웹뷰 설정을 위함
  • 현재 웹뷰는 모바일 기기에서만 작동합니다. 윈도우나 크롬으로 디버그하면 에러화면이 나옵니다.
flutter pub add webview_flutter
flutter pub add webview_flutter_android
flutter pub add webview_flutter_wkwebview


Android SDK 버전 설정

  • 이 포스팅은 안드로이드 에뮬레이터를 기준으로 포스팅하였습니다.
    • Android Emulator : Pixel_3a_API_33_x86_64
  • 안드로이드에서 웹 뷰 컴파일시 33 이상의 API 버전이 필요합니다.
  • 그렇지 않으면 아래와 같이 에러 출력됩니다.

 

  • (프로젝트 디렉토리에서) android - app - build.gradle 파일 열기

 

  • android 안의 'compileSdkVersion' 부분을 33 로 수정
  • defaultConfig 안의 'minSdkVersion' 부분을 19 로 수정
    • compileSdkVersion : 컴파일 시 사용되는 버전, 최신 기능 사용을 위해 가능한 최신 버전 사용 권장
    • minSdkVersion : 앱 구동 최소 버전, 가능한 많은 기기에서 설치를 위해 가능한 낮은 버전으로 선택


구문

webview_flutter 공식 문서에 따르면 웹뷰는 컨트롤러를 이용하여 웹 페이지를 표시하고,

컨트롤러를 인스턴스화하여 웹뷰에 제공하는 형태의 코드를 제공합니다.

사실 샘플 코드를 보고 실행이 되지 않아 한참을 해맸습니다...

설명이 부실한건지 내가 멍청한 건지... 후자겠죠... ㅠㅠㅠ


웹 뷰 컨트롤러 인스턴스화

controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..setBackgroundColor(const Color(0x00000000))
  ..setNavigationDelegate(
    NavigationDelegate(
      onProgress: (int progress) {
        // Update loading bar.
      },
      onPageStarted: (String url) {},
      onPageFinished: (String url) {},
      onWebResourceError: (WebResourceError error) {},
      onNavigationRequest: (NavigationRequest request) {
        if (request.url.startsWith('https://www.youtube.com/')) {
          return NavigationDecision.prevent;
        }
        return NavigationDecision.navigate;
      },
    ),
  )
  ..loadRequest(Uri.parse('https://flutter.dev'));

컨트롤러를 웹 뷰 위젯으로 전달

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Flutter Simple Example')),
    body: WebViewWidget(controller: controller),
  );
}

코딩 (기능 구현)

웹 뷰 사용을 위한 패키지 임포트

  • 웹 뷰를 표시할 webview_flutter 패키지
  • 안드로이드용 웹 뷰 패키지 : webview_flutter_android
  • iOS용 웹 뷰 패키지 : webview_flutter_wkwebview
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

웹 뷰 위젯 구현

  • 웹 뷰를 보여주려면 'WebViewWidget' 위젯을 이용하여야 합니다.
  • 페이지 표시는 컨트롤러를 통해 제공합니다.
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      // 웹 뷰
      body: WebViewWidget(
        controller: _controller,
      ),
    );
  }

컨트롤러 생성

  • 웹뷰 컨트롤러 인스턴스를 생성합니다.
    • late : 변수를 액세스 하기 전에 값이 할당됨을 나타냄, 초기 값을 미리 제공하지 않는 변수 선언
    • final : 나주엥 값이 할당되고 초기화 후 재할당 할 수 없는 변수 선언
// 웹뷰 컨트롤러 인스턴스 생성
late final WebViewController _controller;

 

  • 컨트롤러를 생성하기 위해 initState 를 이용하여 앱 실행시 컨트롤러를 생성해줍니다.
  • 아래의 코드들은 모두 initState에서 작성하기 때문에 아래의 코드에선 '}' 기호를 사용하여 initState를 닫지 않습니다.
@override
initState() {
    super.initState();

 

  • 가장 먼저 컨트롤러가 어떤 환경의 기기에서 실행되는지 확인합니다.
// 플랫폼별 웹 뷰 컨트롤러 생성에 필요한 매개변수
late final PlatformWebViewControllerCreationParams params;

// 디바이스가 iOS일 경우
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
  params = WebKitWebViewControllerCreationParams(
    allowsInlineMediaPlayback: true,
    mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
  );
}
// 디바이스가 Android일 경우
else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
  params = const PlatformWebViewControllerCreationParams();
}

 

  • 실행된 환경에 맞는 컨트롤러를 생성합니다.
// 플랫폼에 맞는 컨트롤러 생성
final WebViewController controller =
    WebViewController.fromPlatformCreationParams(params);

if (controller.platform is AndroidWebViewController) {
  AndroidWebViewController.enableDebugging(true);
  (controller.platform as AndroidWebViewController)
      .setMediaPlaybackRequiresUserGesture(false);
}

 

  • 컨트롤러를 설정합니다.
    • 참고 : onNavigationRequest에서 웹 페이지 접근 금지, 표시 처리를 할 수 있습니다.
    • 그러나 아무리 테스트해봐도 prevent 한 웹페이지가 접속이 됩니다. 알아본 결과 loadRequest가 먼저 실행되어서 prevent 설정이 무시된다고는 하는데 자세히는 모르겠습니다. 알고 계신분 알려주세요...
// 컨트롤러 설정
controller
  // 웹뷰에서 자바스크립트 코드 실행 여부 : 제한없이 사용
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  // 웹 뷰 배경색 설정 : 투명 배경
  ..setBackgroundColor(const Color(0x00000000))
  // 웹 뷰 이벤트 및 요청을 처리할 대리자 설정
  ..setNavigationDelegate(
    NavigationDelegate(
      // 페이지 로딩 진행률 표시
      onProgress: (int progress) {
        // Update loading bar.
        print('Update loading bar : $progress');
      },
      // 새 페이지가 로드 될 때 실행되는 코드
      onPageStarted: (String url) {
        print('onPageStarted : $url');
      },
      // 페이지 로드가 완료 될 때 실행되는 코드
      onPageFinished: (String url) {
        print('onPageFinished : $url');
      },
      // 웹 리소스 로드 오류 시 실행 되는 코드
      onWebResourceError: (WebResourceError error) {
        print('WebResourceError : $error');
      },
      // 내비게이션 요청 시 실행되는 코드
      // 요청된 URL을 기반으로 표시를 허용할지 금지할지 결정
      onNavigationRequest: (NavigationRequest request) {
        // url이 유튜브일 경우 : 웹 페이지 표시 금지
        if (request.url.startsWith('https://www.youtube.com/')) {
          return NavigationDecision.prevent;
        }
        // url이 유튜브가 아닐 경우 : 웹 페이지 표시
        return NavigationDecision.navigate;
      },
    ),
  )
  // 보여줄 페이지 URL
  ..loadRequest(Uri.parse('https://luvris2.tistory.com/'));

 

  • 생성한 컨트롤러를 웹뷰 컨트롤러 인스턴스에 저장합니다.
  • 저장 후 iniState 의 함수를 닫습니다.
_controller = controller;
}

전체 소스 코드

  • main.dart
더보기
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

void main() => runApp(const MaterialApp(home: MyWebView()));

class MyWebView extends StatefulWidget {
  const MyWebView({super.key});

  @override
  State<MyWebView> createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  late final WebViewController _controller;

  @override
  initState() {
    super.initState();

    // 플랫폼별 웹 뷰 컨트롤러 생성에 필요한 매개변수
    late final PlatformWebViewControllerCreationParams params;

    // 디바이스가 iOS일 경우
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    }
    // 디바이스가 Android일 경우
    else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
      params = const PlatformWebViewControllerCreationParams();
    }

    // 플랫폼에 맞는 컨트롤러 생성
    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);

    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController)
          .setMediaPlaybackRequiresUserGesture(false);
    }

    // 컨트롤러 설정
    controller
      // 웹뷰에서 자바스크립트 코드 실행 여부 : 제한없이 사용
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      // 웹 뷰 배경색 설정 : 투명 배경
      ..setBackgroundColor(const Color(0x00000000))
      // 웹 뷰 이벤트 및 요청을 처리할 대리자 설정
      ..setNavigationDelegate(
        NavigationDelegate(
          // 페이지 로딩 진행률 표시
          onProgress: (int progress) {
            // Update loading bar.
            print('Update loading bar : $progress');
          },
          // 새 페이지가 로드 될 때 실행되는 코드
          onPageStarted: (String url) {
            print('onPageStarted : $url');
          },
          // 페이지 로드가 완료 될 때 실행되는 코드
          onPageFinished: (String url) {
            print('onPageFinished : $url');
          },
          // 웹 리소스 로드 오류 시 실행 되는 코드
          onWebResourceError: (WebResourceError error) {
            print('WebResourceError : $error');
          },
          // 내비게이션 요청 시 실행되는 코드
          // 요청된 URL을 기반으로 표시를 허용할지 금지할지 결정
          onNavigationRequest: (NavigationRequest request) {
            // url이 유튜브일 경우 : 웹 페이지 표시 금지
            if (request.url.startsWith('https://www.youtube.com/')) {
              return NavigationDecision.prevent;
            }
            // url이 유튜브가 아닐 경우 : 웹 페이지 표시
            return NavigationDecision.navigate;
          },
        ),
      )
      // 보여줄 페이지 URL
      ..loadRequest(Uri.parse('https://luvris2.tistory.com/'));

    _controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(
        controller: _controller,
      ),
    );
  }
}

앱 실행

  • 앱 실행 화면

 

  • 콘솔 화면
    • 컨트롤러 설정 시 내비게이션 대리자를 통한 정보를 콘솔에 표시한 화면


코드 간소화

  • 코드가 너무 길어서 사용하기 불편하다면 모든 설정을 생략하고 간소화하여 웹페이지를 표시할 수 있습니다.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() => runApp(const MaterialApp(home: MyWebView()));

class MyWebView extends StatefulWidget {
  const MyWebView({super.key});

  @override
  State<MyWebView> createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  // 웹뷰 컨트롤러 인스턴스 생성
  late final WebViewController _controller;

  @override
  initState() {
    super.initState();

    // 보여줄 페이지 URL
    _controller = WebViewController()
      ..loadRequest(Uri.parse('https://luvris2.tistory.com/'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(
        controller: _controller,
      ),
    );
  }
}

 

  • 실행 화면

 


참고

 

반응형