package:url_launcher
URL을 시작하기 위한 Flutter 플러그인입니다.
앱에서 URL을 눌렀을 때 새 인터넷 창이 열리면서 웹 사이트에 접속해 본 적이 있는가? 혹은 앱 자체에서 인터넷 브라우저가 열린다든지 아니면 유튜브 링크를 눌렀는데 유튜브에 들어가지면서 해당 영상으로 열린 적은 있는가? 그러한 모든 작업을 간단하게 실행시켜 주는 Flutter의 공식 패키지(publisher:flutterdev)가 있는데 그 패키지는 바로 url_launcher이다.
url_launcher는 외부 애플리케이션(웹 브라우저, 전화, 이메일, 문자 메시지 등)을 실행하기 위해 사용하는 공식 패키지이다. 예를 들어, 사용자가 버튼을 눌렀을 때 웹 페이지를 열거나 전화를 걸도록 할 수 있다.
지원되는 URL 체계
스킴 | 예시 | 설명 |
https:<URL> | https://flutter.dev | 기본 브라우저에서 <URL> 열기 |
mailto:<email address>?subject=<subject>&body=<body> | mailto:smith@example.org?subject=News&body=New%20plugin | 기본 이메일 앱에서 <이메일 주소>로 이메일 작성 |
tel:<phone number> | tel:+1-555-010-999 | 기본 전화 앱을 사용하여 <전화번호>로 전화 걸기 |
sms:<phone number> | sms:5550101234 | 기본 메시지 앱을 사용하여 <전화번호>로 SMS 메시지 보내기 |
file:<path> | file:/home | 데스크톱 플랫폼에서 지원되는 기본 앱 연결을 사용하여 파일 또는 폴더 열기 |
일단 이 패키지를 사용하기 위해선 당연하게도 사용하는 프로젝트에 url_launcher 패키지를 추가해야 할 것이다.
먼저 펍데브(pub.dev)에서 url_launcher 패키지를 가져와 pubspec.yaml 파일에 넣어주자.
굳이 이렇게 하기 싫다면 url_launcher는 퍼블리셔가 dart.dev이기 때문에 그냥 터미널에서 바로 pub add 시켜도 된다.
flutter pub add url_launcher
암튼 어떻게든 간에 가져와서 pubspec.yaml 파일에 정상적으로 들어가 있다면 이제 사용해 줄 수 있다.
사용해 줄 수 있다고 말했지만 바로 사용할 수 있는 건 아니다. 물론 Web에서 디버깅용으로 사용한다면 아무 문제 없이 바로 사용할 수 있다. 하지만 실행하는 곳은 안드로이드이기 때문에 여기서 매니페스트(AndroidManifest.xml) 파일에 코드를 추가해줘야 한다.
매니페스트 파일을 일단 열어주자. 위치는 프로젝트이름/android/app/src/main/AndroidManifest.xml이다.
이제 아래의 <intent>를 <queries>안에 복사해서 넣어주자.
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
이렇게 잘 들어갔다면 이제 안드로이드에서 링크 URL을 열 준비가 끝난 것이다.
이 글 아래에서도 다른 몇 가지의 <intent>들을 추가할 예정이기에 지금 추가한 <intent>에 대해서 간략히 설명하고 넘어가겠다.
먼저 <action android:name="android.intent.action.VIEW" />는 앱이 어떤 데이터를 "보는(View)" 동작을 수행할 수 있음을 나타내주는데 쉽게 말해 웹 URL, 사진, 연락처 등을 열기 위한 인텐트이다. 즉 이걸 추가해야 브라우저처럼 특정 URI를 열 수 있는 앱으로 등록이 된다는 것이다.
두 번째로는 <category android:name="android.intent.category.BROWSABLE" />이다.
이 인텐트의 역할은 외부(웹 브라우저나 다른 앱)에서 이 앱의 Activity를 호출할 수 있도록 허용하는 것인데 쉽게 말해 사용자가 웹사이트에서 링크를 클릭했을 때 앱이 열릴 수 있도록 해준다는 것이다. 딥링크를 위해서는 필수로 넣어야 할 카테고리 되시겠다.
마지막으로 <data android:scheme="https" />이다.
이건 이 인텐트 필터는 https:// 스킴으로 시작하는 URI를 처리할 수 있다는 의미로 예시로 https://flutter.dev 같은 링크를 처리하도록 설정해 놓은 것이다. 추가로 host, path, port등을 추가해서 특정 링크만 처리하게 할 수 있다.
<data android:scheme="https" android:host="flutter.dev" />
<data android:scheme="https" android:host="flutter.dev" />
이제 사용해 볼 것인데 사용하는 방법은 엄청나게 쉽다. 그냥 비동기로 launchUrl을 호출한 후 그 파라미터 안에는 링크 텍스트를 넣어주면 끝난다.
ElevatedButton(
onPressed: () async {
await launchUrl(Uri.parse('https://ch5c.tistory.com'));
},
child: Text('Open ch5c blog'),
)
이러면 정말 놀랍게도 끝이다. 아래의 다트 패드를 실행해 보자. 아마 웹 환경이라 새 창이 열릴 것이다.
하지만 이건 좀 사파(?) 방식이고 공식 문서에서는 좀 더 안전하게 사용하는 것을 권장하고 있다.
당연하지만 링크는 위의 코드처럼 그냥 바로 넣어버리면 나중에 그 코드에서 링크 찾을 때 또 힘드니까 따로 빼놓는 게 정신건강에 좋다.
final Uri _url = Uri.parse('https://ch5c.tistory.com');
이제 호출하는 동작은 한번 함수 형태로 빼서 만들어보겠다.
비동기로 호출하기 때문에 함수는 Future을 사용하고 async를 사용하여 만들어주면 된다.
Future<void> _launchUrl() async {}
이제 안에서 launchUrl만 호출하면 끝나는데 그것만 할 거였으면 굳이 함수로 빼지도 않았을 것이다. 한번 예외 처리를 해주겠다.
Future<void> _launchUrl() async {
if (!await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
공식 문서에는 이렇게 한번 조건문으로 걸러주고 있다. 하지만 여기서 만일을 대비하여 한번 더 확인해야 하는 것이 있는데 바로 실행하는 장치에서 URl을 열 수 있는지의 여부이다.
그것의 확인을 위해선 canLaunchUrl을 호출해 주면 된다.
Future<void> _launchUrl() async {
if (await canLaunchUrl(_url) && !await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
이렇게 && 연사자로 묶어서 처리해 주면 깔끔하고 안전하게 사용해 줄 수 있다. canLaunchUrl와 launchUrl의 타입을 확인해 보면 알겠지만 둘 다 Future<bool> 타입을 가지고 있어 해당하지 않을 시 false를 반환하면서 조건문에 걸리게 된다.
암튼 사용하는 곳에서도 그냥 바로 호출해 주면 사용할 수 있다.
ElevatedButton(
onPressed: () => _launchUrl(),
child: Text('Open ch5c blog'),
)
근데 이 코드를 안드로이드에서 실행시켜 보면 알겠지만 웹 브라우저가 현재 앱에서 열린다.
마치 카카오톡에서 링크를 누르면 카카오톡 내에서 실행되는 것처럼 말이다. 물론 이것이 나쁜 것은 아니다. 왜냐하면 이렇게 앱 내부에서 웹 브라우저를 실행시킨다는 것은 사용자의 이탈을 막는데 도움이 되기 때문이다. 하지만 나 같은 경우는 카카오톡에서 링크 열릴 때마다 너무 화났다. 그냥 크롬으로 보내주지.. 그래서 바로 브라우저에서 열리게 할 수도 있다.
다시 함수로 돌아와 보자.
지금 뭐 조건문도 달려있고 그런데 다 때 놓고 봐보자.
Future<void> _launchUrl() async {
await launchUrl(_url);
}
자 이 코드에서 주목할 것은 당연하게도 launchUrl() 함수일 것이다. launchUrl()의 Doc pup을 봐보면 알 수 있다시피 파라미터로 받을 수 있는 것들이 꽤 있다. 여기서 우린 mode 파라미터를 사용해 줄 것이다.
mode는 타입으로 LaunchMode를 갖는데 그대로 사용해 주면 된다.
단지 이제 LaunchMode에서 생성자로 사용할 것은 고르면 될 뿐. 지금 현재 브라우저로 열리는 것을 목적으로 하고 있으니 LaunchMode.externalApplication을 사용해 주면 된다.
Future<void> _launchUrl() async {
await launchUrl(_url, mode: LaunchMode.externalApplication);
}
이렇게 적고 실행해 본다면 정상적으로 브라우저에서 링크가 열리는 모습을 확인할 수 있을 것이다.
이 LaunchMode에 대해서 조금만 더 알아보고 넘어가자.
LaunchMode는 URL을 실행할 때 어떤 방식으로 실행할지를 결정하는 열거형(enum)으로 이는 launchUrl() 함수의 mode 파라미터로 사용되게 된다. 그 생성자들은 아래와 같다.
각 모드별 용도
상황 | 모드 |
사용자가 링크 클릭 → 앱 내부에서 보기 | inAppWebView 또는 inAppBrowserView |
링크 클릭 → 외부 브라우저에서 열기 | externalApplication |
최대 호환성 / 기본 행동 유지 | platformDefault |
이제 이것을 참고하여 원하는 동작 스타일에 맞춰 모드를 설정하면 될 것이다.
또한 그냥 링크에 해당되는 앱이 존재할 경우 그 앱으로 열리게 된다. 즉 유튜브 링크 걸어 놓으면 유튜브로 열린다.
final Uri appUri = Uri.parse('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
Future<void> _launchUrl() async {
if (!await launchUrl(appUri, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch $appUri');
}
}
실행해 보면..
잘 되는 듯하다.
식당에 메뉴 주문을 위해 인터넷에서 그 식당을 검색하여 전화번호를 찾고 눌러서 전화를 건 적이 있는가?
여기서 '전화' 버튼이나 밑의 전화번호를 누르면 알아서 전화 앱이 열리면서 전화번호까지 다 입력이 되는 것을 우리는 알고 있다.
그렇다면 이 동작 만드는 것이 어려울까? 대답은 No. 전혀 아니다. 오히려 쉽다면 겁나 쉽다.
먼저 맨 처음에 했던 것처럼 AndroidManifest.xml에 인텐트를 추가해줘야 한다.
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
위치는 똑같다. 딱 보면 감이 오는가? 아까는 scheme을 https로 선언했다면 이번엔 tel을 선언해 주었다. 즉 이제 텔레콤, 전화를 열 수 있다는 것이다.
사용하는 방법은 간단하다. Uri의 파라미터를 사용해 주면 된다. 단 우리가 흔히들 링크를 열 때 사용하는 Uri.parse 말고 그냥 Uri자체를 사용해줘야 한다.
final Uri launchUri = Uri();
이제 이 Uri 안에서 사용할 파라미터는 scheme과 path되시겠다.
final Uri launchUri = Uri(
scheme: 'tel', // 전화 걸기 앱 사용
path: '1234567890', // 걸 전화번호
);
두 파라미터에 대해 설명을 하자면 Uri 클래스에서 scheme과 path는 URI(Uniform Resource Identifier)의 핵심 구성 요소로 scheme은 URI의 동작 방식을 정의하는 프로토콜 또는 스킴을 넣는 곳이고 path는 URI의 "리소스" 또는 "데이터" 경로를 의미하며 일반적으로 동작의 대상이 되는 정보가 들어간다고 할 수 있겠다.
path는 scheme에 따라 의미가 달라지기 때문에 스킴을 잘 설정해놔야 한다.
이제 사용할 함수를 만들어주고
Future<void> _makePhoneCall() async {}
안에서 launchUrl()의 파라미터에 방금 만든 launchUrl함수를 넣어주면 끝난다.
Future<void> _makePhoneCall() async {
await launchUrl(launchUri);
}
이제 실행해 보면..
아주 잘 작동하는 모습을 볼 수 있다.
이제 기세를 몰아서 문자도 보내보자.
일단 마찬가지로 인텐트를 넣어준다. 근데 이 인텐트는 안 넣고 사용해도 왠지 모르게 난 실행이 잘 된다.. (이유는 모름)
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="sms" />
</intent>
그다음에는 전화와 형식이 크게 다르지 않기에 스킴만 문자 형식인 sms로 바꿔주면 된다.
final Uri smsUri = Uri(
scheme: 'sms',
path: '1234567890',
);
Future<void> _sendSms() async {
await launchUrl(smsUri);
}
기가 막히게 잘 동작한다. 근데 여기에 사실 비밀이 하나 숨겨져 있는데 바로 문자를 미리 입력해 놓을 수 있다는 것이다.
이것 또한 Uri의 파라미터를 사용해 준다. 사용하는 것은 queryParametes라는 파라미터로 살짝 사용방식이 특이하다.
먼저 타입이 Map<String, dynamic>이기 때문에 제네릭으로 명시를 해주고 { }을 사용하여 맵 형식으로 사용해줘야 한다.
queryParameters: <String, String>{
'body': '안녕하세요'
},
이렇게 body를 입력해서 Map 형식으로 넣어주면 안전하게 미리 문구를 입력해 놓을 수 있다.
final Uri smsUri = Uri(
scheme: 'sms',
path: '1234567890',
queryParameters: <String, String>{
'body': '안녕하세요'
},
);
Future<void> _sendSms() async {
await launchUrl(smsUri);
}
굉장히 잘 작동한다. 그다음은 이메일이다.
메일은 딱히 인텐트에 대한 설명을 찾을 수 없어 생략해도 되는 듯하다.
메일의 스킴은 mailto이고 path에는 이제 전화번호 대신 주소를 넣어주면 될 것이다.
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'supportexample.com',
);
Future<void> _sendEmail() async {
await launchUrl(emailUri);
}
이것도 실행해 보면.. (Gmail 앱으로 열리기 때문에 로그인되어 있어야 함)
정상적으로 작동한다. 그리고 이것 또한 메시지와 마찬가지로 미리 입력을 해놓을 수 있는데 바로 제목과 내용을 미리 입력해 놓을 수 있다. 그를 위해선 Uri의 query파라미터를 사용해 주는 방법도 있다만 훨씬 사용하기 편한 queryParameters 파라미터를 사용하는 것이 정신 건강에 이로울 것이다.
일단 이것도 쿼리 문자열 규격을 따르고 있어서 제목은 'subject', 본문은 'body'로 입력해 주면 된다.
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'supportexample.com',
queryParameters: {
'subject': '문의',
'body' : '100만 원만 빌려주세요.'
}
);
이렇게 url_launcher 패키지를 사용하여 URL 링크를 내부 자체에서 열고 거기에 특정 앱을 열어봤으며 전화, 메시지, 이메일 앱까지 여는 방법을 알아봤다. 솔직히 패키지 자체는 어렵지 않고 쉬웠는데 계속하면서 구글 로그인하고 앱 업데이트 하고 하느라 적는데 시간이 오래 걸린 것 같다.. 암튼 도움이 되었길 바라며 마치겠다.