Flutter로 web view 앱 만들기 (feat. inappwebview)
Flutter 내장의 Webview가 있지만 나는 flutter_inappwebview를 선택했다.
내장 웹뷰보다 기능이 많고, js interface와 같은 기능이 필요했기 때문에 선택하게 되었다.
⚒️ 기본설정
Android
android > app > build.gradle 에서 minSdkVersion 17
이상
Pub get
flutter pub add flutter_inappwebview
🧑💻 Code
main()
main 부분에 os에 따른 컨트롤러 설정이 필요하다.
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
// WebView
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
}
runApp(MyApp());
}
InAppWebViewGroupOptions
웹뷰에 사용될 option을 지정해준다.
InAppWebViewGroupOptions _options = InAppWebViewGroupOptions(
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
),
);
-
Webview의 userAgent 변경 또는 뒤에 추가하기
웹뷰에서의 userAgent 값을 완전히 변경하거나, 뒤에 원하는 텍스트를 추가할 수 있다.
InAppWebViewGroupOptions _options = InAppWebViewGroupOptions( android: AndroidInAppWebViewOptions( useHybridComposition: true, ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, ), crossPlatform: InAppWebViewOptions( userAgent: "APP_FLUTTER", // 완전 변경 applicationNameForUserAgent: "APP_FLUTTER", // 맨뒤에 추가 ), );
- 2021.12.29
- Flutter version : 2.8.1
flutter_inappwebview : 5.3.2
Android에서는
applicationNameForUserAgent
가 정상적으로 돌아가는데, iOS에서는 안돌아가더라;;; github issues에 등록된 이슈가 있고, 수정되었다고 하는데 난 왜 안될까…
- Flutter version : 2.8.1
flutter_inappwebview : 5.3.2
Android에서는
- 2021.12.29
InAppWebView
Webview Widget
InAppWebViewController? webViewController;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white, // 배경색
body: SafeArea(
bottom: Platform.isIOS,
child: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse("https://m.naver.com")), // 주소는 꼭 https부터 쓰자!
initialOptions: _options, // 미리 작성했던 옵션
onWebViewCreated: (controller) async {
// WebView 첫 생성시 실행
// initState() 같은 느낌으로 사용
// JS Interface 핸들러를 여기에 작성
webViewController = controller;
},
onConsoleMessage: (controller, consoleMessage) {
// Console 메세지 출력
print("console : $consoleMessage");
},
androidOnPermissionRequest: (controller, origin, resources) async {
return PermissionRequestResponse(
resources: resources,
action: PermissionRequestResponseAction.GRANT,
);
},
),
),
);
}
기본적인 세팅은 여기까지! 이제 각종 옵션을 추가해보자
Plus➕
Android 뒤로가기 버튼을 web내의 뒤로가기로 작동하도록 변경
andrid는 하단에 뒤로가기 버튼이 있는데, 따로 설정해주지 않으면 바로 앱을 꺼버린다. 웹에서 뒷페이지로 가도록 추가해주자.
- 뒤로가기 기능 작성
Future<bool> _goBack(BuildContext context) async {
if (webViewController == null) {
return Future.value(true);
}
if (await webViewController!.canGoBack()) {
webViewController!.goBack();
return Future.value(false);
}
return Future.value(true);
}
- WillPopScope로 감싸기
- WillPopScope는 뒤로가기 기능을 제어할 수 있는 Widget이다. WillPopScope로 감싸면 Appbar에 자동생성되는 뒤로가기가 작동하지 않는다;;;;; 필요하다면 leading으로 IconButton을 추가하여 사용하자.
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: Scaffold(
...
),
);
}
새로고침 적용하기
아래로 끌어서 새로고침
기능을 추가해보자
- refesh 했을때의 동작을 작성한 컨트롤러
_pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.green,
),
onRefresh: () async {
if (Platform.isAndroid) {
webViewController?.reload();
} else if (Platform.isIOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(
url: await webViewController?.getUrl(),
),
);
}
},
);
- InAppWebView에 적용
@override
Widget build(BuildContext context) {
return Scaffold(
...
body: SafeArea(
bottom: Platform.isIOS,
child: InAppWebView(
...
pullToRefreshController: _pullToRefreshController,
onProgressChanged: (controller, progress) {
// 내려서 refresh를 사용했을때 완료되면 종료되도록
if (progress == 100) {
_pullToRefreshController.endRefreshing();
}
},
...
),
),
);
}
JavaScript Handler(JS Interface, JS Message Handler) 적용하기
android에는 WebView JavaScript Interface가 있고, ios에는 WKWebview JavaScript Message Handler가 있다. WebView안의 JS와 데이터를 주고받을때 사용할 수 있다.
- Example html
- 아래 코드는 Webview가 아닌 상황에서는 작동하지 않는다. pc나 모바일 웹 브라우저에서 접속할때는 오류가 발생할 수 있으니까, userAgent를 체크해서 작성해주는게 좋다!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>
<script>
function call() {
window.flutter_inappwebview.callHandler('callHandler').then(function(result) {
console.log("JS Handler!");
console.log(JSON.stringify(result));
});
}
function callWithArgs() {
const args = [1, true, ['bar', 5], {'key':'value'}]
window.flutter_inappwebview.callHandler('argsHandler', ...args).then(function(result) {
console.log("JS Handler with args!");
console.log(JSON.stringify(result));
});
}
</script>
<body>
<h1>JavaScript Handlers</h1>
<FORM>
<INPUT type="BUTTON", style="WIDTH:100pt; HEIGHT:50pt", value="call", onclick='call()'>
<INPUT type="BUTTON", style="WIDTH:100pt; HEIGHT:50pt", value="callWithArgs", onclick='callWithArgs()'>
<br>
</FORM>
</body>
</html>
- Flutter에 callback 작성하기
InAppWebView(
...
initialData : InAppWebViewInitialData(data: """
<!DOCTYPE html>
<html>
...
</html>
"""),
onWebViewCreated: (controller) async {
webViewController = controller;
// call
webViewController?.addJavaScriptHandler(
handlerName: 'callHandler',
callback: (args) async {
return {
'this is': 'return value!',
};
},
);
// call with args
webViewController?.addJavaScriptHandler(
handlerName: 'argsHandler',
callback: (args) async {
print(args);
// -> [1, true, ['bar', 5], {'key':'value'}]
return {
'this is': 'return value!',
};
},
);
},
...
),
위 방법 외에도 Web Message Channels & Listeners도 있으니까, 한번쯤 확인해봐도 좋을 것 같다.
자잘한 기능
- url 이동하기 :
webViewController?.loadUrl(urlRequest: URLRequest(url: Uri.parse(”https://m.naver.com”)));
- FCM Push랑 같이 쓰기
- Push를 클릭해서 들어왔을때 loadURL을 쓰면 열릴때도 있고, 안열릴때도 있다. 웹뷰가 생성되기 전에 push handler가 실행되서 무시되거나, 웹뷰가 생성되자마자 실행되서 첫 url이 무시되는 경우가 있다. 첫 url이 무시되면 뒤로가기 history에 남지 않기 때문에, 뒤로가기를 누르면 앱에 꺼진다. 그래서 timer로 1.5~2초 뒤에 실행되도록 작성해봤다.
void _handleMessage(RemoteMessage message) { print("닫았다가 실행!"); if (message.data['url'] != null) { Timer? _timer; _timer = Timer.periodic(Duration(milliseconds: 500), (timer) { if (timer.tick > 3) { webViewController?.loadUrl( urlRequest: URLRequest(url: Uri.parse(message.data['url'])), ); _timer?.cancel(); } }); } }
- FCM Push랑 같이 쓰기
참고
flutter_inappwebview | Flutter Package Flutter InAppWebView Documentation Flutter WebView JavaScript Communication - InAppWebView 5