Never give up

Flutter - kakaomap webview 0.4.0 업데이트 후기(Feat. Webview, Intent scheme) 본문

해왔던 삽질..

Flutter - kakaomap webview 0.4.0 업데이트 후기(Feat. Webview, Intent scheme)

대기만성 개발자 2022. 3. 29. 13:45
반응형

이번에 업로드 한 부분 버그픽스 위주로 작업했습니다

 

먼저 변경된점으로는

 

1. zoom level도 custom script에 넣어줬습니다

 

2. KakaoMapScreen은 deprecated 했습니다 (자세한 내용은 아래에서 말씀드리겠습니다)

 

3. 웹뷰 버전업 후 안드로이드에서 마커있는곳에 center가 안잡히는 문제를 수정했습니다

 

4. 사용중인 패키지를 업데이트 했습니다 (webview flutter, http)

 

 

여기서 2번 문제는 이라는 프로그래머 분께서 문제가 있다고 말씀해주셔서 확인해봤는데

 

Webview에서 안드로이드는 intent, IOS는 itms apps scheme를 사용하는 곳에서

 

정상작동하지 않는 부분을 확인했습니다

사실 Webview쪽에서 직접 지원을 해주는게 좋지 않을까 생각은 하는데..

< 안드로이드 기준 >

 

그래서 이 부분을 어떻게 처리해야될지 고민하다가 안드로이드는 적당한(?) 예제를 발견해서 처리했습니다

 

먼저 Method channel 뼈대(?)를 만들고

package com.example.example

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity : FlutterActivity() {
    private val openIntent: String = "openIntentChannel"

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

        MethodChannel(flutterEngine.dartExecutor, openIntent).setMethodCallHandler(
        	IndentHandler(activity)
        )
    }
}

IndentHandler를 추가해줍니다

package com.example.example

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.util.Log
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class IndentHandler(private val activity: Activity) : MethodChannel.MethodCallHandler {
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        val url: String = call.argument("url") ?: return

        try {
            val intent: Intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)

            Log.d("[Intent] package", intent.`package`.toString())

            val existPackage = 
            activity.packageManager.getLaunchIntentForPackage(intent.`package`.toString())

            Log.d("[Intent] exist package", existPackage.toString())

            if (existPackage != null) {
                activity.startActivity(intent)
            } else {
                val marketIntent = Intent(Intent.ACTION_VIEW)
                marketIntent.data = Uri.parse("market://details?id=" + intent.`package`)
                activity.startActivity(marketIntent)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

 

그리고 KakaoMapScreen에 연결해줍니다

 

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';

class KakaoMapScreen extends StatelessWidget {
  KakaoMapScreen({Key? key, required this.url}) : super(key: key);

  final String url;

  final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
      GlobalKey<ScaffoldMessengerState>();

  final MethodChannel _channel = MethodChannel('openIntentChannel');

  @override
  Widget build(BuildContext context) {
    return ScaffoldMessenger(
      key: _scaffoldMessengerKey,
      child: Scaffold(
          body: SafeArea(
        child: WebView(
            initialUrl: url,
            javascriptMode: JavascriptMode.unrestricted,
            javascriptChannels: <JavascriptChannel>{
              _toasterJavascriptChannel()
            },
            navigationDelegate: (delegate) async {
              debugPrint('[Webview] delegate : ${delegate.url}');

              if (Platform.isAndroid && delegate.url.startsWith('intent://')) {
                await _channel.invokeMethod('intent', {'url': delegate.url});

                return NavigationDecision.prevent;
              } else if (Platform.isIOS && delegate.url.contains('itms-apps')) {
                await _iosNavigate(delegate.url);

                return NavigationDecision.prevent;
              }

              return NavigationDecision.navigate;
            }),
      )),
    );
  }

  JavascriptChannel _toasterJavascriptChannel() {
    return JavascriptChannel(
        name: 'Toaster',
        onMessageReceived: (JavascriptMessage message) {
          _scaffoldMessengerKey.currentState
              ?.showSnackBar(SnackBar(content: Text(message.message)));
        });
  }

  /// navigate to other app.
  /// It needs to test in real device
  Future<void> _iosNavigate(String url) async {
    if (url.contains('id304608425')) {
      // if kakao map exists open app
      if (!(await launch('kakaomap://open'))) {
        // if kakao map doesn't exist open market and navigate to app store
        await launch('https://apps.apple.com/us/app/id304608425');
      }
    } else {
      // rest of them are just launching..
      await launch(url);
    }
  }
}

 

navigation delegate에서는 페이지를 이동할 때마다 url이 어떻게 들어오는지 트래킹 할 수 있고

 

그에 따른 처리를 해줄 수 있습니다

 

먼저 return 값으로는 NavigationDecision enum값으로

 

prevent는 말 그대로 Navigate를 하지 않습니다

 

navigate는 말 그대로 들어온 URL대로 Navigate해줍니다

 

그리고 필자는 OS따라 구분동작을 시켜줬는데

 

intent가 들어온 경우 url을 android method 채널을 통해 url을 전달하고

 

해당 앱이 설치되어있는지 확인하고 있으면 실행, 없으면 마켓으로 이동 하는 처리를 해줬고

 

IOS에서는 별도의 처리가 어렵다는 글들을 보고 URL launcher로 대충 껴맞추기(?)를 했는데

 

카카오맵 id가 들어가 있는경우 open을 해보고 안되면(앱 설치가 안된경우)

 

마켓으로 보내고 나머지는 일단 마켓으로 보내기..(합리적인 척..)

날 잡아서 IOS쪽도 일정수준 이상은 끌어올려야될것 같은 느낌이 많이 들었던것 같습니다..

반응형
Comments