본문 바로가기
flutter

Flutter WebView net::ERR_UNKNOWN_URL_SCHEME

by Rogan_Kim 2025. 5. 6.
728x90

flutter Webview를 사용하고 있다면,

그리고 결제 모듈을 Webview로 연동하고 있다면 net::ERR_UNKNOWN_URL_SCHEME 라는 에러를 겪고, 고통 받고 있을 수도 있습니다.

이처럼 (퍼온 이미지 입니다)

그렇다면 해당 포스트를 잘 찾아 오셨습니다 👏🏿👏🏿👏🏿👏🏿👏🏿

해당 포스트는 5분안에 해당 문제를 해결 할 수 있게 끔, 예시 코드와 원인을 설명하고 있습니다. 

 

 

GitHub - kimjuno97/webview_err_unknown_url_scheme_solution: Flutter `net::ERR_UNKNOWN_URL_SCHEME` solution

Flutter `net::ERR_UNKNOWN_URL_SCHEME` solution. Contribute to kimjuno97/webview_err_unknown_url_scheme_solution development by creating an account on GitHub.

github.com

 

 

주 원인

  • webview_flutter나 flutter_inappwebview는 http/https가 아닌 스킴(intent://, kb-acp:// 등)을 기본적으로 지원하지 않기 때문에, 별도로 네이티브 코드나 MethodChannel, 또는 onNavigationRequest/shouldOverrideUrlLoading에서 직접 처리해줘야 합니다.
  • 특히 KB Pay 등 일부 결제는 onNavigationRequest 콜백에도 잡히지 않아 추가적인 처리가 필요할 수 있습니다.

, 결제모듈 연동 가장 흔한 에러는 net::ERR_UNKNOWN_URL_SCHEME 에러이며, 이는 스킴(intent ) 미처리로 인해 발생합니다.

(출처 perplexity)

 

 

해결 방법

해결 방법으로는 첫번째로,

예시코드 프로젝트 기준 android/app/src/main/kotlin/com/example/MainActivity.kt에 MethodChannel 코드를 추가합나다.

 

MainActivity.kt에 따로 추가로 구현한 코드가 없다면 아래와 비슷한 형태로 구현을 마무리 할 수 있습니다.

class MainActivity: FlutterActivity(){
    private val webviewChannel = "com.flutter.webview"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {  
        GeneratedPluginRegistrant.registerWith(flutterEngine);  

        MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, webviewChannel).setMethodCallHandler {
            call, result ->
            when {
                call.method.equals("getAppUrl") -> {
                    try {
                        val url: String = call.argument("url")!!
                        val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
                        result.success(intent.dataString)
                    } catch (e: URISyntaxException) {
                        result.notImplemented()
                    } catch (e: ActivityNotFoundException) {
                        result.notImplemented()
                    }
                }
                call.method.equals("getMarketUrl") -> {
                    try {
                        val url: String = call.argument("url")!!
                        val packageName = Intent.parseUri(url, Intent.URI_INTENT_SCHEME).getPackage()
                        val marketUrl = Intent(
                            Intent.ACTION_VIEW,
                            Uri.parse("market://details?id=$packageName")
                        )
                        result.success(marketUrl.dataString)
                    } catch (e: URISyntaxException) {
                        result.notImplemented()
                    } catch (e: ActivityNotFoundException) {
                        result.notImplemented()
                    }
                }
            }
        }
    }
}

 

코드를 간력하게 설명하자면, 

getAppUrl 호출 시

  • 목적intent:// 스킴 URL → 실제 앱 실행 가능한 URI 변환
  • 동작 과정:
    1. Flutter에서 url 파라미터 전달 (예: intent://payment#Intent;package=com.kb.pay;end)
    2. Intent.parseUri()로 안드로이드 인텐트 객체 생성
    3. intent.dataString 추출 (예: kb-acp://payment)
    4. 변환된 URI를 Flutter로 반환

 getMarketUrl 호출 시

  • 목적: 앱 미설치 시 → Play Store 이동용 URL 생성
  • 동작 과정:
    1. Flutter에서 url 파라미터 전달
    2. 인텐트에서 패키지명 추출 (예: com.kb.pay)
    3. market://details?id=com.kb.pay 형태로 URL 생성
    4. 마켓 URL을 Flutter로 반환

 

두번째로는, Flutter에서 

WebViewController의 setNavigationDelegate에서 android일 경우에만 getAppUrl, getMarketUrl 호출하는 로직을 구현합니다.

late final _controller = WebViewController()
    ..setJavaScriptMode(JavaScriptMode.unrestricted)
    ..setNavigationDelegate(
      NavigationDelegate(
        onNavigationRequest: (NavigationRequest request) async {
          final Uri uri = Uri.parse(request.url);
          final String finalUrl = request.url;
          if (uri.scheme == "http" || uri.scheme == 'https') {
            return NavigationDecision.navigate;
          }

          // Intent URL일 경우, OS별로 구분하여 실행
          if (defaultTargetPlatform == TargetPlatform.android) {
            /// [MEMO] Android의 경우, Native(kotlin)으로 url을 전달해 INTENT처리 후 리턴받는다.
            final appScheme = await _convertIntentToAppUrl(request.url);
            try {
              if (appScheme != null) {
                await launchUrlString(
                  appScheme,
                  mode: LaunchMode.externalApplication,
                );
              }
            } catch (e) {
              // 앱이 설치되어 있지 않는 경우, playStore로 이동
              final appMarketUrl = await _convertIntentToMarketURl(request.url);
              if (appMarketUrl != null) {
                await launchUrlString(
                  appMarketUrl,
                  mode: LaunchMode.externalApplication,
                );
              }
            }
          } else if (defaultTargetPlatform == TargetPlatform.iOS) {
            launchUrlString(finalUrl);
          }
          return NavigationDecision.prevent;
        },
      ),
    )
    ..loadRequest(
      Uri.parse(webviewUrl),
    )

    /// javascriptChannel 사용시
    ..addJavaScriptChannel(
      '채널명',
      onMessageReceived: (_) {},
    );

 

 

 

정리하자면,

WebviewController는 http/https가 아닌 스킴(intent://, kb-acp:// 등)을 라우팅 할 수 없으므로, MethodChannel로 안드로이드 네이티브 코드로 넘긴 이후에, Intent.parseUri() 절차를 가진 후에 다시 받아 온 후에 라우팅을 한다고 생각하면 된다.

 

뭔차이가 있나 싶을 수도 있을것이다.

현대카드로 결제를 한다고 가정하고 finalUrl과 appSheme을 print해보면 아래처럼 나옵니다.

finalString >> intent:hdcardappcardansimclick://appcard?acctid=*******#Intent;package=com.hyundaicard.appcard;end;

appScheme >> hdcardappcardansimclick://appcard?acctid=*******

 

더 궁금한게 있다면 댓글로 남겨주시면 답변드리겠습니다.

728x90

댓글