Flutter 내장의 Webview가 있지만 나는 flutter_inappwebview를 선택했다.
내장 웹뷰보다 기능이 많고, js interface와 같은 기능이 필요했기 때문에 선택하게 되었다.
⚒️ 기본설정
Android
android > app > build.gradle 에서 minSdkVersion 17
이상
Pub get
1
| flutter pub add flutter_inappwebview
|
🧑💻 Code
main()
main 부분에 os에 따른 컨트롤러 설정이 필요하다.
1
2
3
4
5
6
7
8
9
10
| Future main() async {
WidgetsFlutterBinding.ensureInitialized();
// WebView
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
}
runApp(MyApp());
}
|
InAppWebViewGroupOptions
웹뷰에 사용될 option을 지정해준다.
1
2
3
4
5
6
7
8
| InAppWebViewGroupOptions _options = InAppWebViewGroupOptions(
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
),
);
|
InAppWebView
Webview Widget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| 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는 하단에 뒤로가기 버튼이 있는데, 따로 설정해주지 않으면 바로 앱을 꺼버린다. 웹에서 뒷페이지로 가도록 추가해주자.
1
2
3
4
5
6
7
8
9
10
11
| 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을 추가하여 사용하자.
1
2
3
4
5
6
7
8
9
| @override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: Scaffold(
...
),
);
}
|
새로고침 적용하기
아래로 끌어서 새로고침
기능을 추가해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| _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(),
),
);
}
},
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| @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를 체크해서 작성해주는게 좋다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| <!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>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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초 뒤에 실행되도록 작성해봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 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();
}
});
}
}
|
참고
flutter_inappwebview | Flutter Package
Flutter InAppWebView Documentation
Flutter WebView JavaScript Communication - InAppWebView 5