Flutter Webview Intent 처리 ERR_UNKNOWN_URL_SCHEME
Modern Collection View 와 MVVM 패턴 가이드
[iOS] Modern Collection View & MVVM 패턴 가이드 - 인프런 | 강의
MVVM 패턴과 Modern Collection View를 사용해 네트워킹을 구현하고, 다양하고 동적인 Collection View를 자유자재로 다룰 수 있게 됩니다., - 강의 소개 | 인프런
www.inflearn.com
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을 직접 하드코딩으로 넣어줬다.