ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter Webview Intent 처리 ERR_UNKNOWN_URL_SCHEME
    개발/flutter 2020. 8. 13. 13:34

    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을 직접 하드코딩으로 넣어줬다.

     

     

    개발자 이직 비법 보러가기

    댓글

Designed by Tistory.