-
Flutter Webview Intent 처리 ERR_UNKNOWN_URL_SCHEME개발/flutter 2020. 8. 13. 13:34
Modern Collection View 와 MVVM 패턴 가이드
Webview로 결제를 구현해야 하는 순간이 찾아왔다.
url 만 띄우면 될 줄 알았지만 세상은 그렇게 단순하지 않았다.
웹뷰로 url을 호출하면
클라이언트는 결제를 어떤 방식으로 할 건지 선택하면 새로운 url 이 호출되고
만약 은행사의 앱 결제를 클릭할 시 앱을 켜줘야 하고
그 앱이 깔려있지 않을 시 마켓에서 앱을 다운로드할 수 있도록 플레이스토어/앱스토어로 보내줘야 한다.
문제는 앱 결제였다.
카카 오페이던 신한 앱카드 페이건 카카오 앱, 신한 앱을 켜줘야 하고 그것은 intent:// 스키마로 호출한다.
하지만 안드로이드의 웹뷰의 경우 https://가아닌 intent:// 스키마에 대한 처리가 되지 않았다.
이경우 안드로이드 네이티브 코드를 작성해 intent url에서는 특정 작업을 따로 실행해줘야 한다.
기본 웹뷰 구현은 이렇다. (flutter webview plugin 사용)
WebviewScaffold( url: widget.url, ignoreSSLErrors: true, invalidUrlRegex: Platform.isAndroid ? '^(?!https://|http://|about:blank|data:).+' : null, );
Web view plugin 에서는 두 가지 작업을 실행할 수 있다.
State 변경 시 (onStateChanged)
Url 변경시 (onUrlChanged)
State는 페이지 요청 시 시작/끝 이런 상태가 있다.
여기서 해야 할 것은 만약 https 나아닌 다른 스키마를 url로 받아올 때
다른 작업을 실행시켜줘야 하는 것이다.
_onStateChanged = webView.onStateChanged.listen((state) { final type = state.type; final url = state.url; print(url); if (mounted) { print('url $url $type'); if (isAppLink(url)) { handleAppLink(url); } } });
webview가 호출되면서 위 함수가 호출되고
첫 번째로 appLink 인지 확인하고 , 두 번째로 app Link 라면 이를 처리해주는 로직을 실행하면 된다.
App link 인지 아닌지는 단순히 scheme 만 본다.
bool isAppLink(String url) { final appScheme = Uri.parse(url).scheme; return appScheme != 'http' && appScheme != 'https' && appScheme != 'about:blank' && appScheme != 'data'; }
http/https 아니면 앱링크로 판단했다.
그럼 이제 해당 intent url을 처리하는 로직을 실행한다.
여기서 이제 네이티브 코드가 필요한 상황이고 그 네이티브 코드를 실행시키려면 Method channel을 사용하면 된다.
static const platform = MethodChannel('이름아무거나'); Future<String> getAppUrl(String url) async { if (Platform.isAndroid) { return await platform .invokeMethod('getAppUrl', <String, Object>{'url': url}); } else { return url; } }
'getAppUrl' 은 이후 channel의 메서드명이고 파라미터로 url 이 보내졌다.
이러면 네이티브 코드에서 channel '이름 아무거나'가 호출돼 코드를 실행한다.
private static final String CHANNEL = "이름아무거나";
코드를 짜기 위해 해당 플러그인, 패키지들을 임포트 해야 한다.
import android.content.ActivityNotFoundException; import android.content.Intent; import android.util.Log; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar;
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { } } });
자바 코드는 잘 모르므로 설명은 패스하고 저 안에 로직이 들어갈 것이다.
switch (call.method) { case "getAppUrl": try { String url = call.argument("url"); Log.i("url", url); Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); result.success(intent.getDataString()); } catch (URISyntaxException e) { result.notImplemented(); } catch (ActivityNotFoundException e) { result.notImplemented(); } break;
호출된 url 은
intent://kakaopay/pg? url=https://pg-reseller-web.kakao.com/v1/어쩌고 저쩌고
였고 이를 intent parsing을 통해 얻은 결괏값은
kakaotalk://kakaopay/pg?url=https://pg-reseller-web.kakao.com/v1/어쩌고저쩌고
이다 이 url을 통해 카카오톡 앱을 실행시킬 것이다.
result.success를 통해 이 url을 리턴하면 이제 카톡 앱을 실행시키는 로직에서 처리한다,
카톡이든 뭐든 앱을 키는 패키지로 url_launcher를 사용했다.
https://pub.dev/packages/url_launcher
getAppUrl(url).then((value) async { if (await canLaunch(value)) { await launch(value); } else { final marketUrl = await getMarketUrl(url); await launch(marketUrl); } });
getAppUrl로 리턴 받은 url을 canLaunch()로 실행 가능 여부를 리턴 받는다
해당 앱이 있으면 true 가 리턴되고 앱을 그 url 로실행한다
만약 이 앱이 없다면 market url로 유도해야 한다.
이역시 메서드 채널을 이용해 플러터에서 부른 뒤 자바에서 처리한다.
await platform .invokeMethod('getMarketUrl', <String, Object>{'url': url});
case "getMarketUrl": { try { String url = call.argument("url"); Log.i("url", url); Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); String scheme = intent.getScheme(); String packageName = intent.getPackage(); if (packageName != null) { result.success("market://details?id=" + packageName); } result.notImplemented(); } catch (URISyntaxException e) { result.notImplemented(); } catch (ActivityNotFoundException e) { result.notImplemented(); } break; }
자바 코드에서 이 메서드 채널을 통해 플레이스토어 url을 리턴할 수 있도록 한다.
하나카드 앱 intent 의경우 인텐트가 이렇게 나온다
Intent { act=android.intent.action.VIEW dat=cloudpay://? tid=2437248682026208cloudpay://?tid=2437248682026208 pkg=com.hanaskcard.paycla }
Scheme 은 cloudpay
Package name 은 com.hanaskcard.paycla이다
이 패키지 네임을 가지고 ‘market://details? id='를 앞에 붙여 market url을 만들어주어 리턴하여
url_launcher로 플레이스토어를 실행시키면 된다.
iOS는 scheme 에따라 분리해 해당 앱 마켓 url을 직접 하드코딩으로 넣어줬다.
'개발 > flutter' 카테고리의 다른 글
Flutter - 스트래티지 패턴 (Strategy pattern) 구현 (0) 2020.09.03 [Flutter] GridView 스크롤시 움직이는(없어졌다가 등장) 문제 -PageStorageKey (0) 2020.08.31 Flutter Webview 흰 화면만 뜨는에러 (0) 2020.08.13 [Flutter] fatal error: 'FirebaseCore/FIRLogger.h' file not found 에러 (0) 2020.08.03 Flutter 빌드시 Installing build\app\outputs\apk\app.apk 에서 하얀화면과 함께 멈출때 (0) 2020.05.25