Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

포메

[flutter (dart)] 여러 플랫폼에서 DeepLink, Associated Domains 효율적으로 다루기 (Android, IOS, MacOS) 본문

dart & flutter

[flutter (dart)] 여러 플랫폼에서 DeepLink, Associated Domains 효율적으로 다루기 (Android, IOS, MacOS)

포포메 2025. 3. 19. 00:38

관심 배경

 

채팅, 회의, AI, 음성 관련 플랫폼을 만들다 보니, 평소에 별로 눈여겨 보지 않았던 기능이 필요하게 되었다.

바로 줌이나 쿠팡에 있는 기능!

웹 URL을 클릭하면, 자동으로 설치된 앱을 실행하고, 설치된 앱에서 원하는 페이지까지 이동시키는 기능이다.

 

줌은 웹 URL을 누르면 특정 회의실로 저절로 진입하게 해준다. 그 과정에서 설치된 앱을 저절로 켜준다.

쿠팡 역시 (보통 광고의 용도로 많이 사용되지만) 웹 URL을 누르면 쿠팡 앱을 자동으로 켜준다.

 

이 기능이 없이 사용자가 직접 앱을 찾아서 클릭하고 링크나 암호를 직접 입력하게 방식을 사용한다면

편의성이 크게 떨어질 것이라고 생각했다.

 

그래서

이 기능을 구현하기 위해 많은 조사와 고민, 설계를 했다.

 

DeepLink, Associated Domains 등등 기본 개념에 대해서는 굳이 기록하지 않을 것이고, 핵심적인 노하우 위주로 설명하겠다.

 

 

스킴을 어떻게 다뤄야 할까 ?

 

스킴이란 웹 URL의 맨 앞에 위치한 프로토콜을 의미한다.

http, https 이런 것들을 의미한다.

 

특히

http, https 스킴 기반의 URL은 전세계에서 가장 널리 쓰이는데,

 

적어도 DeepLink, Associated Domains를 다룰 때는 해당 스킴의 사용에 약간의 주의가 필요하다고 생각한다.

 

왜냐하면

 

"브라우저가 http, https 스킴의 URL 가로채는 현상이 관찰되었다." (2025년 3월 19일 기준)

적어도 내가 사용하는 안드로이드 폰, 아이패드, 아이폰의 크롬 및 카톡 브라우저에서는 관찰되었다.

 

브라우저가 http, https 스킴의 URL을 가로챘다고 생각하는 이유는 다음과 같다.

나는 이미 이와 관련된 개발을 꽤 오래 전에 완료했기 때문에 우리 앱에는 

플랫폼 별 유효한 assetlinks.json, apple-app-site-association 세팅이 확실하게 되어 있는 상태이다.

 

근데 이 상태에서 다른 모든 조건을 유지한 채

"오직 기존의 커스텀 스킴에서 http, https 스킴으로 변경만 하면 앱이 켜지지 않고, 웹 페이지에서 멈춰 있는다!"

 

시간 관계 상 굳이 브라우저 보안 정책 같은 건 하나하나 찾아보진 않았지만

당연히 웹 브라우저가 여는 것이 너무나 자연스러운 http, https 스킴을 남용할 필요는 없을 거 같다.

그것만 써야하는 상황이라면 모르겠지만,

 

DeepLink, Associated Domains와 관련해서는 intent 스킴이나 커스텀 스킴의 선택지가 있기 때문에 !!

 

내가 브라우저 정책을 잘못 파악했을 수도, 뭔가 세팅을 틀려서 발생한 현상일 수도 있다.

그치만 크게 관심은 없다.

intent 스킴과 커스텀 스킴을 적절히 섞어 쓰는 것이 훨씬 안정적으로 작동한다는 것을 경험적으로 많이 확인했기 때문이다.

그리고 너무나 대중적인 http, https 스킴 특성 상, 브라우저가 그에 관한 보안 정책을 언제든 내놓아도 이상하지 않을 것.

 

어떤 스킴을 쓰든 간에 내 앱 전체의 런타임 효율에는 딱히 지장이 없기 때문에 크게 관심은 없고, 

그냥 경험적으로 가장 안정적이었던 intent 스킴과 커스텀 스킴을 쓰는 것이 최선이라고 결론 내렸다.

 

 

대신, 커스텀 스킴"만" 쓰면 안된다!

 

이유는 간단하다.

카톡을 포함한 보통의 어플들은, http, https 스킴에 대해서만 하이퍼링크 처리를 자동으로 해준다!

 

abcde같은 커스텀 스킴을 사용자가 직접 마주하는 URL에 넣을 경우, 위와 같이 하이퍼링크 처리가 안되기 때문에 

편의성이 크게 떨어진다.

 

그래서 사용자가 마주하는 "껍데기 URL"에는 http, https 스킴을 쓰는 것이 좋다.

본인 웹 페이지가 있다면 그냥 그대로 쓰고, 앱 redirection용 route를 하나 파는 방식으로 하면 좋다.

 

 

 

 

 

 

예를 들어 내가 기존에 운영하던 웹페이지가 https://example.com이라고 하자. 

 

이 경우 유저에게 제공되는 "껍데기 URL"은 대략 다음과 같은 형태로 하면 될 것이다.

https://example.com/app-enter-wrapper?roomKey=${roomKey} 

 

 

그리고 해당 URL에 대응되는 웹 페이지 내부의 코드에서, 

해당 route로 접근하자 마자

"커스텀 스킴 주소로 redirection" (window.location.href OR button click 방식)

을 구현하면 된다 !

예를 들어

 

window.location.href = abcde://example.com/room?roomKey=${roomKey}

이런 식의 코드를 쓰면 된다 !!

 

물론, 나의 어플의 android, ios, macos, windows 및 flutter 코드 등등에는

"abcde라는 커스텀 스킴을 처리하는 로직" + room route이하의 처리 로직이 필요할 것 !!

 

 

 

 

 

 

 

예시 코드는 다음과 같다.

(nextJS, tailwindCSS 기준)

"use client";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect } from "react";
import { isAndroid, isIOS, isMacOs } from "react-device-detect";

interface AppEnterWrapperProps {
	domainName: string;
	nextPort: string;
}

const AppEnterWrapper: React.FC<AppEnterWrapperProps> = ({
	domainName,
	nextPort,
}) => {
	const searchParams = useSearchParams();
	const roomKey = searchParams.get("key");

	const getIntentPath = useCallback(
		(domain: string, port: string): string => {
			// ? https://github.com/duskload/react-device-detect/blob/master/docs/selectors.md
			if (isAndroid) {
				// ? intent path는 안드로이드만 가능
				return `intent://${domain}:${port}/0-room?key=${roomKey}#Intent;scheme=https;package=com.example.my_app;end;`;
			} else if (isIOS || isMacOs) {
				return `abcde://${domain}:${port}/0-room?key=${roomKey}`;
			} else {
				return `abcde://${domain}:${port}/0-room?key=${roomKey}`;
			}
		},
		[roomKey],
	);

	useEffect(() => {
		window.location.href = getIntentPath(domainName, nextPort);
	}, [domainName, nextPort, getIntentPath, roomKey]);

	return (
		<Link href={getIntentPath(domainName, nextPort)}>
			<div className="rounded-lg bg-[#ffd369] px-8 py-4 shadow-md hover:opacity-80">
				Join Meeting
			</div>
		</Link>
	);
};

export default AppEnterWrapper;


핵심은 페이지에 들어가자마자

window.location.href를 통해 원하는 커스텀 스킴 or intent 링크로 강제 redirection 시킨다는 것.

그리고 유저가 거절했을 때나 기타 상황을 대비해

눌러서 접속 가능한 버튼도 하나 만들어 놓으면 대충 괜찮을 거 같다.

 

안드로이드에서 intent path를 쓰지 않고, 커스텀 스킴을 써도 딱히 상관은 없을 거 같다.

 

 

추가로 주의할 점.

 

플랫폼에 맞게 

/.well-known 하위의

apple-app-site-association, assetlinks.json를 추가할 때 약간 주의할 점이 있다.

nextJS 기준으로 /public 하위에 .well-known 폴더를 추가하는 방식으로 구현할 경우 아마 제대로 안될 것이다.

 

apple-app-site-association 때문.

보다시피 확장자 같은 정보가 없으나, 사실은 json 헤더와 함께 애플 측에 전달되어야 하는 파일이다.

이것도 경험적으로 알아냈다. json 헤더를 추가하지 않고 public 폴더에 냅다 넣어버리면,

https://app-site-association.cdn-apple.com/a/v1/coupang.com

 

위의 쿠팡의 경우처럼 apple측에 캐싱되지 않을 것.

 

nextJS 기준으로는

server route route.js / route.ts에 GET request하나 구현해서 json 헤더를 주면 잘 될 거다 !!

 

 

 

참고한 사이트. 설명이 길지만 상세하고 잘 되어 있음 !!

https://codewithandrea.com/articles/flutter-deep-links/

 

Flutter Deep Linking: The Ultimate Guide

A step-by-step tutorial showing how to implement deep links in Flutter using GoRouter, including the native Android and iOS platform setup.

codewithandrea.com