# 외부 앱과 통신(AOS, iOS)

### Android

* [https://developer.android.com/training/basics/intents?hl=ko](https://developer.android.com/training/basics/intents?hl=ko)
*   다른 앱으로 사용자보내기

    1. [`Intent`](https://developer.android.com/reference/android/content/Intent?hl=ko)를 사용하여 앱이 실행하려는 작업, 즉 '의도'를 정의
    2. [`startActivity()`](https://developer.android.com/reference/android/app/Activity?hl=ko#startActivity\(android.content.Intent\))와 같은 메서드로 시스템에 [`Intent`](https://developer.android.com/reference/android/content/Intent?hl=ko)를 전달

    ```kotlin
    private val paymentAppPackageName = "com.test.app"
    private val paymentAppClassName = "$paymentAppPackageName.className"

    fun onCall() {
    		var intent = Intent(Intent.ACTION_MAIN)
    		
    		intent.addCategory(Intent.CATEGORY_LAUNCHER)
    		var compName = ComponentName(paymentAppPackageName, paymentAppClassName)
    		intent.setComponent(compName)
    		
    		try {
    		    startActivityForResult(intent, 1)
    		} catch (e: ActivityNotFoundException) {
    		     var downloadIntent = Intent(Intent.ACTION_VIEW, Uri.parse("<https://play.google.com/store/apps/details?id=$paymentAppPackageName>"))
    		     startActivity(downloadIntent)
    		}
    }
    ```
*   활동으로부터 결과 가져오기

    1. [`BroadCast Receiver`](https://developer.android.com/reference/android/content/BroadcastReceiver)를 사용하여 결과값을 수신

    ```kotlin
    // AndroidManifest.xml
    <receiver
        android:name=".Receiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.app.broadcast" />
        </intent-filter>
    </receiver>

    // Receiver.kt
    class Receiver : BroadcastReceiver() {
        private var onListener: ReceiverListener? = null

        fun setOnListener(_onListener: ReceiverListener?) {
            onListener = _onListener
        }

        override fun onReceive(context: Context, intent: Intent) {
            val b: Bundle? = intent.extras
            val iter: Iterator<String> = b!!.keySet().iterator()
            while (iter.hasNext()) {
                val key = iter.next()
                val value: Any? = b.get(key)
                Log.e("[BroadcastReceiver]", "[$key] $value")
            }
            if (onListener != null) onListener!!.GetIntent(intent)
        }
    }

    // ReceiverListener.kt
    interface ReceiverListener {
        fun GetIntent(intent: Intent?)
    }

    // MainActivity.kt
    private val paymentAppPackageName = "com.test.app"
    private val paymentAppBroadcastReceiverName = "$paymentAppPackageName.broadcast"
    private var receiver: Receiver? = null

    fun onCall() {
    		receiver = EasyCheckReceiver()
    		receiver!!.setOnListener(object : EasyCheckReceiverListener {
    		    override fun GetIntent(intent: Intent?) {
    		        Log.d("DEBUG", "broadcast getintent")
    		
    		        val jsonObject = JSONObject()
    		        val extras: Bundle? = intent!!.extras
    		        for (key in extras!!.keySet()) {
    		            jsonObject.put(key, extras.get(key))
    		            Log.e("[BroadcastReceiver]", "[$key] ${extras.get(key)}")
    		        }
    		    }
    		})
    		var filter = IntentFilter(paymentAppBroadcastReceiverName)
    		this.registerReceiver(receiver, filter)
    }

    override fun onDestroy() {
        super.onDestroy()
        this.unregisterReceiver(receiver)
    }

    ```
* 다른 앱이 내 활동을 시작하도록 허용



### iOS

* [https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app)
* URL Scheme 정의
  1.  다른 앱의 Scheme 정의

      ```
      // info.plist
      <key>LSApplicationQueriesSchemes</key>
      <array>
      	<string>{URL scheme}</string>
      </array>
      ```
  2.  결과를 받을 자사 앱의 Scheme 정의

      ```
      // info.plist
      <key>CFBundleURLTypes</key>
      <array>
      	<dict>
      		<key>CFBundleURLSchemes</key>
      		<array>
      			<string>linksample</string>
      		</array>
      		<key>CFBundleURLName</key>
      		<string>co.kr.linksample</string>
      	</dict>
      </array>
      ```
*   URL Open

    ```swift

    fun onCall() {
    		let strURL = String(format: "{URL scheme}://?PARAMETER=%@", parameter)
    		let encodedString = strURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
    		
    		if let url = URL(string: encodedString) {
    		    if UIApplication.shared.canOpenURL(url) {
    						// 앱이 설치된 경우
    		        UIApplication.shared.open(url, options: [:], completionHandler: nil)
    		    } else {
    		        // 앱이 설치되지 않은 경우
    		        if let url = URL(string: "<http://itunes.apple.com/><country>/app/<app–name>/id<app-ID>?mt=8") {
    		          UIApplication.shared.open(url, options: [:], completionHandler: nil)
    		        }
    		    }
    		}
    }
    ```
*   활동으로부터 결과를 가져오기

    ```swift
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // URL Scheme으로 열린 앱에서 전달받은 데이터 처리
        if let encodedString = url.absoluteString.components(separatedBy: "?").last {
            let decodedString = encodedString.removingPercentEncoding ?? ""
            let queryItems = decodedString.components(separatedBy: "&").map { $0.components(separatedBy: "=") }
            var jsonObject = [String: Any]()
            queryItems.forEach { item in
                if item.count == 2 {
                    jsonObject[item[0]] = item[1]
                }
            }
            let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [])
            if let jsonData = jsonData {
                let jsonString = String(data: jsonData, encoding: .utf8)
            }
        }

        return true
    }
    ```



### Troubleshooting

* Error Domain=NSOSStatusErrorDomain
  * URL 포맷 확인 {URL scheme}://?{파라미터}&{파라미터}…
  * Info.plist에서 LSApplicationQueriesSchemes 확인
* custom scheme

앱이 지원하는 커스텀 URL 스키마를 정의합니다.

**`CFBundleURLTypes`** 키의 값은 배열로 구성되며, 각 요소는 앱이 지원하는 URL 스키마에 대한 정보를 제공하는 딕셔너리입니다. 위 코드에서는 \*\*`linksample`\*\*이라는 URL 스키마를 지원하도록 정의되어 있습니다.

* **`CFBundleURLSchemes`** : URL 스키마를 정의하는데 사용되는 키입니다. 해당 스키마를 사용하여 앱의 기능을 호출할 수 있습니다.
* **`CFBundleURLName`** : URL 스키마의 이름을 정의하는데 사용되는 키입니다. 일반적으로 앱의 번들 ID와 URL 스키마 이름을 조합하여 사용합니다.

```jsx
// info.plist

<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>linksample</string>
			</array>
			<key>CFBundleURLName</key>
			<string>co.kr.linksample</string>
		</dict>
	</array>
```
