자습서: 백 엔드 서비스를 통해 Azure Notification Hubs를 사용하여 Flutter 앱에 푸시 알림 보내기
샘플 다운로드 샘플 다운로드
- Xamarin.Forms
- 플러터
- React 네이티브
이 자습서에서는
ASP.NET Core Web API 백 엔드는 최신 설치 방법을 사용하여 클라이언트에 대한 디바이스 등록 처리하는 데 사용됩니다. 또한 서비스는 플랫폼 간 방식으로 푸시 알림을 보냅니다.
이러한 작업은 백 엔드 작업
이 자습서에서는 다음 단계를 안내합니다.
필수 구성 요소
따라가려면 다음이 필요합니다.
- 리소스를 만들고 관리할 수 있는 Azure 구독.
- Flutter 도구 키트(필수 구성 요소와 함께).
Flutter 및 다트 플러그 인이 설치된 visual Studio Code .- CocoaPods는 라이브러리 종속성을 관리하기 위해 설치되어.
Android (물리적 또는 에뮬레이터 디바이스) 또는 iOS(물리적 디바이스만 해당)에서 앱을 실행하는 기능입니다.
Android의 경우 다음이 있어야 합니다.
- 개발자가 물리적 디바이스 또는 에뮬레이터 잠금 해제(Google Play 서비스가 설치된 API 26 이상 실행).
iOS의 경우 다음이 있어야 합니다.
- 활성 Apple 개발자 계정.
- 개발자 계정
(iOS 13.0 이상 실행) 등록된 물리적 iOS 디바이스입니다. .p12 개발 인증서 키 집합 설치되어 물리적 디바이스앱을실행할 수 있습니다.
메모
iOS 시뮬레이터는 원격 알림을 지원하지 않으므로 iOS에서 이 샘플을 탐색할 때 물리적 디바이스가 필요합니다. 그러나 이 자습서를 완료하기 위해 Android 및 iOS 둘 다에서 앱을 실행할 필요는 없습니다.
이전 경험이 없는 이 첫 번째 원칙 예제의 단계를 따를 수 있습니다. 그러나 다음과 같은 측면을 잘 알고 있으면 도움이 됩니다.
- apple 개발자 포털
. - ASP.NET Core.
- Google Firebase 콘솔
. - Microsoft Azure 및 Azure Notification Hubs사용하여 iOS 앱에 푸시 알림을 보냅니다.
- 플랫폼 간 개발을 위해 Flutter 및 Dart.
- Kotlin은 Android 및 iOS 네이티브 개발을 위한 Swift.
제공된 단계는 macOS
푸시 알림 서비스 및 Azure Notification Hub 설정
이 섹션에서는
Firebase 프로젝트 만들기 및 Android용 Firebase Cloud Messaging 사용
Firebase 콘솔로그인합니다.
프로젝트 이름이 PushDemo입력하는 새 Firebase 프로젝트를 만듭니다. 메모
고유한 이름이 생성됩니다. 기본적으로 제공된 이름의 소문자 변형과 대시로 구분된 생성된 숫자로 구성됩니다. 전역적으로 여전히 고유한 경우 이 값을 변경할 수 있습니다.
프로젝트를 만든 후 Android 앱Firebase 추가
선택합니다. Android 앱 Firebase 추가
Android 앱
Firebase 추가 페이지에서 다음 단계를 수행합니다. Android 패키지 이름패키지 이름을 입력합니다. 예:
com.<organization_identifier>.<package_name>
.지정
앱 등록
선택합니다. 다운로드 google-services.json선택합니다. 그런 다음 나중에 사용할 로컬 폴더에 파일을 저장하고 다음선택합니다.
다음
선택합니다. 콘솔
계속 선택 메모
콘솔로 계속 단추를 사용할 수 없는 경우설치 확인 확인으로 인해 이 단계건너뛰기선택합니다.
Firebase 콘솔에서 프로젝트에 대한 톱을 선택합니다. 그런 다음 프로젝트 설정선택합니다.
선택
메모
google-services.json 파일을 다운로드하지 않은 경우 이 페이지에서 다운로드할 수 있습니다.
위쪽의 클라우드 메시징 탭으로 전환합니다. 나중에 사용할 수 서버 키 복사하고 저장합니다. 이 값을 사용하여 알림 허브를 구성합니다.
복사
푸시 알림에 대한 iOS 앱 등록
iOS 앱에 푸시 알림을 보내려면 Apple에 애플리케이션을 등록하고 푸시 알림에 등록합니다.
앱을 아직 등록하지 않은 경우 Apple 개발자 센터에서 iOS 프로비전 포털 찾습니다. Apple ID를 사용하여 포털에 로그인하고,
인증서, 식별자 & 프로필 이동한 다음,식별자를 선택합니다. + 클릭하여 새 앱을 등록합니다. 새 식별자 등록 화면에서 앱 ID 라디오 단추를 선택합니다. 그런 다음 계속선택합니다.
새 앱에 대해 다음 세 가지 값을 업데이트한 다음 계속선택합니다.
설명: 앱의 설명이 포함된 이름을 입력합니다.
번들 ID: com 양식의 번들 ID를 입력합니다.<organization_identifier>. 앱 배포 가이드설명한 대로<product_name>. 다음 스크린샷에서는
mobcat
값이 조직 식별자로 사용되고 PushDemo 값이 제품 이름으로 사용됩니다.푸시 알림
: 기능 섹션에서푸시 알림 옵션을 확인합니다.새 앱 ID 등록하기 위한
양식 이 작업은 앱 ID를 생성하고 정보를 확인하는 요청을 생성합니다. 계속선택한 다음 등록 선택하여 새 앱 ID를 확인합니다.
확인
등록을 선택하면 새 앱 ID가 인증서, 식별자 & 프로필 페이지에 줄 항목으로 표시됩니다.
인증서의 식별자 & 프로필 페이지의 식별자아래에서 만든 앱 ID 줄 항목을 찾습니다. 그런 다음 해당 행을 선택하여 앱 ID 구성 편집 화면을
표시합니다.
Notification Hubs에 대한 인증서 만들기
알림 허브가 APNS(Apple Push Notification Services) 사용할 수 있도록 하려면 인증서가 필요하며 다음 두 가지 방법 중 하나로 제공할 수 있습니다.
Notification Hub 직접 업로드할 수 있는 p12 푸시 인증서 만들기(원래 방법) 토큰 기반 인증 사용할 수 있는 p8 인증서 만들기(최신 및 권장 방법)
최신 방법은 APNS대한
옵션 1: Notification Hub에 직접 업로드할 수 있는 p12 푸시 인증서 만들기
Mac에서 키 집합 액세스 도구를 실행합니다. 유틸리티 폴더 또는 실행 패드의 기타 폴더에서 열 수 있습니다.
키 집합 액세스 선택하고인증서 도우미 확장한 다음, 인증 기관인증서 요청선택합니다. 요청
메모
기본적으로 키 집합 액세스는 목록의 첫 번째 항목을 선택합니다.
인증서 범주에 있고 Apple Worldwide 개발자 관계 인증 기관목록의 첫 번째 항목이 아닌 경우 문제가 될 수 있습니다. CSR(인증서 서명 요청)을 생성하기 전에 키가 아닌 항목이 있거나 Apple Worldwide 개발자 관계 인증 기관 키가 선택되어 있는지 확인합니다. 사용자 전자 메일 주소 선택하고,일반 이름 값을 입력하고, 디스크저장지정한 다음,계속 선택합니다. 필요하지 않으므로 CA 전자 메일 주소 비워 둡니다. 다른 이름으로 저장 CSR(인증서 서명 요청) 파일 이름을 입력하고위치 위치를 선택한 다음저장선택합니다. 파일 이름 선택
이 작업은 선택한 위치에 CSR 파일 저장합니다. 기본 위치는 Desktop. 파일에 대해 선택한 위치를 기억합니다.
iOS 프로비전 포털 인증서, 식별자 & 프로필 페이지로 돌아가서 확인된푸시 알림 옵션으로 아래로 스크롤한 다음, 구성선택하여 인증서를 만듭니다. Apple 푸시 알림 서비스 TLS/SSL 인증서 창이 나타납니다. 개발 TLS/SSL 인증서 섹션에서 인증서 만들기 단추를 선택합니다.
새 인증서 만들기 화면이 표시됩니다.
메모
이 자습서에서는 개발 인증서를 사용합니다. 프로덕션 인증서를 등록할 때도 동일한 프로세스가 사용됩니다. 알림을 보낼 때 동일한 인증서 유형을 사용해야 합니다.
파일선택하고 CSR 파일을저장한 위치로 이동한 다음 인증서 이름을 두 번 클릭하여 로드합니다. 그런 다음 계속선택합니다.
포털에서 인증서를 만든 후 다운로드 단추를 선택합니다. 인증서를 저장하고 인증서가 저장된 위치를 기억합니다.
인증서가 다운로드되어 Downloads 폴더에 컴퓨터에 저장됩니다.
인증서 파일 찾기
메모
기본적으로 다운로드한 개발 인증서의 이름은 aps_development.cer.
다운로드한 푸시 인증서 aps_development.cer두 번 클릭합니다. 이 작업은 다음 이미지와 같이 키 집합에 새 인증서를 설치합니다.
새 인증서 보여 주는 키 집합 액세스 인증서 목록
메모
인증서의 이름은 다를 수 있지만 이름은 Apple Development iOS Push Services 접두사로 지정되고 적절한 번들 식별자가 연결됩니다.
키 집합 액세스에서 제어 + 인증서 범주에서 만든 새 푸시 인증서에서 클릭합니다.
내보내기 선택하고, 파일 이름을 지정하고,p12 형식을 선택한 다음,저장을 선택합니다. 암호로 인증서를 보호하도록 선택할 수 있지만 암호는 선택 사항입니다. 암호 만들기를 무시하려면 확인 클릭합니다. 내보낸 p12 인증서의 파일 이름과 위치를 기록해 둡다. APN으로 인증을 사용하도록 설정하는 데 사용됩니다.
메모
p12 파일 이름과 위치는 이 자습서의 그림과 다를 수 있습니다.
옵션 2: 토큰 기반 인증에 사용할 수 있는 p8 인증서 만들기
다음 세부 정보를 기록해 둡다.
- 앱 ID 접두사(팀 ID)
- 번들 ID
인증서로 돌아가서 프로필식별자를 & 키클릭합니다.
메모
APNS대해 구성된 키가 이미 있는 경우 만든 직후 다운로드한 p8 인증서를 다시 사용할 수 있습니다. 그렇다면 53 단계를 무시할 수 있습니다.
+ 단추(또는 키 만들기 단추)를 클릭하여 새 키를 만듭니다.
적절한
키 이름 값을 입력한 다음 APNS(Apple Push Notifications Service) 옵션을선택한 다음 계속 클릭한 다음 다음 화면에서등록합니다. 다운로드 클릭한 다음 p8 파일(AuthKey_접두사)을 보안 로컬 디렉터리로 이동한 다음 완료클릭합니다.
메모
p8 파일을 안전한 장소에 보관하고 백업을 저장해야 합니다. 키를 다운로드한 후에는 서버 복사본이 제거되므로 다시 다운로드할 수 없습니다.
키만든 키(또는 대신 사용하도록 선택한 경우 기존 키)를 클릭합니다.
키 ID 값을 기록해 둡니다.
Visual Studio Code등 원하는 적합한 애플리케이션에서 p8 인증서를 엽니다. 키 값(-----BEGIN PRIVATE KEY-----과 -----END PRIVATE KEY-----사이)를 기록해 둡니다.
-----베긴 프라이빗 키-----
<key_value>
----- 프라이빗 키 엔드-----메모
토큰 값 나중에 Notification Hub구성하는 데 사용됩니다.
이 단계의 끝부분에서는 나중에 APNS 정보알림 허브를 구성하기
- 팀 ID
(1단계 참조) - 번들 ID(1단계 참조)
- 키 ID
(7단계 참조) - 토큰 값(8단계에서 가져온 p8 키 값)
앱에 대한 프로비저닝 프로필 만들기
iOS 프로비전 포털돌아가서 인증서, 식별자 & 프로필선택하고 왼쪽 메뉴에서 프로필 선택한 다음, + 선택하여 새 프로필을 만듭니다. 새 프로비저닝 프로필 등록 화면이 나타납니다.
프로비저닝 프로필 유형으로
개발 iOS 앱 개발 선택한 다음, 계속선택합니다. 프로비저닝 프로필 목록
다음으로, 앱 ID 드롭다운 목록에서 만든 앱 ID를 선택하고 계속선택합니다.
선택
인증서 선택 창에서 코드 서명에 사용하는 개발 인증서를 선택하고 계속선택합니다.
메모
이 인증서는 이전 단계
만든 푸시 인증서가 아닙니다. 개발 인증서입니다. 없는 경우 이 자습서의 필수 구성 요소이므로 만들어야. 개발자 인증서는 Xcode 또는Visual Studio 통해 apple 개발자 포털만들 수 있습니다. 인증서, 식별자 & 프로필 페이지로 돌아가서 왼쪽 메뉴에서 프로필 선택한 다음 + 선택하여 새 프로필을 만듭니다. 새 프로비저닝 프로필 등록 화면이 나타납니다.
인증서 선택 창에서 만든 개발 인증서를 선택합니다. 그런 다음 계속선택합니다.
다음으로 테스트에 사용할 디바이스를 선택하고 계속선택합니다.
마지막으로
프로비저닝 프로필 이름 프로필 이름을 선택하고생성선택합니다. 선택
새 프로비저닝 프로필을 만들 때 다운로드선택합니다. 저장되는 위치를 기억합니다.
프로비저닝 프로필의 위치를 찾은 다음 두 번 클릭하여 개발 컴퓨터에 설치합니다.
알림 허브 만들기
이 섹션에서는 알림 허브를 만들고 APNS사용하여 인증을 구성합니다. p12 푸시 인증서 또는 토큰 기반 인증을 사용할 수 있습니다. 이미 만든 알림 허브를 사용하려면 5단계로 건너뛸 수 있습니다.
Azure
로그인합니다. 리소스만들기
클릭한 다음 알림 허브 검색하여 선택한 다음만들기 클릭합니다. 다음 필드를 업데이트한 다음 만들기클릭합니다.
기본 세부 정보
구독: 드롭다운 목록에서 대상 구독 선택합니다.
리소스 그룹: 새 리소스 그룹 만들거나 기존 리소스 그룹을 선택합니다.네임스페이스 세부 정보
Notification Hub 네임스페이스:Notification Hub 네임스페이스에 대한 전역적으로 고유한 이름을 입력합니다.
메모
이 필드에 대해 새 만들기 옵션이 선택되어 있는지 확인합니다.
알림 허브 세부 정보
Notification Hub:Notification Hub 이름을 입력합니다.
위치: 드롭다운 목록에서 적절한 위치 선택
가격 책정 계층: 기본 무료 옵션 유지메모
무료 계층의 최대 허브 수에 도달하지 않는 한
Notification Hub 프로비전되면 해당 리소스로 이동합니다.
새 Notification Hub이동합니다.
목록에서 액세스 정책 선택합니다(관리아래).
해당 연결 문자열 값과 함께 정책 이름 값을 기록해 둡다.
APNS 정보를 사용하여 알림 허브 구성
메모
스토어에서 앱을 구매한 사용자에게 푸시 알림을 보내려는 경우에만 애플리케이션 모드프로덕션 사용합니다.
옵션 1: .p12 푸시 인증서 사용
인증서선택합니다.
파일 아이콘을 선택합니다.
이전에 내보낸 .p12 파일을 선택한 다음열기
선택합니다. 필요한 경우 올바른 암호를 지정합니다.
샌드박스 모드를 선택합니다.
저장을 선택합니다.
옵션 2: 토큰 기반 인증 사용
토큰선택합니다.
이전에 획득한 다음 값을 입력합니다.
- 키 ID
- 번들 ID
- 팀 ID
- 토큰
샌드박스
선택합니다. 저장을 선택합니다.
FCM 정보를 사용하여 알림 허브 구성
- 왼쪽 메뉴의 설정 섹션에서 Google(GCM/FCM) 선택합니다.
- google Firebase 콘솔
적어 두는 서버 키 입력합니다. - 도구 모음에서 저장을 선택합니다.
ASP.NET Core Web API 백 엔드 애플리케이션 만들기
이 섹션에서는 ASP.NET Core Web API 백 엔드를 만들어 디바이스 등록 처리하고 Flutter 모바일 앱에 알림을 보냅니다.
웹 프로젝트 만들기
Visual Studio
파일 새 솔루션 선택합니다. .NET Core>App>ASP.NET Core>API>다음선택합니다.
새 ASP.NET Core Web API 구성 대화 상자에서.NET Core 3.1 대상 프레임워크선택합니다. 프로젝트 이름 PushDemoApi 입력한 다음,만들기선택합니다. 디버깅(명령 + 입력)을 시작하여 템플릿 기반 앱을 테스트합니다.
메모
템플릿 기반 앱은 launchUrlWeatherForecastController 사용하도록 구성됩니다. 속성>launchSettings.json설정됩니다.
잘못된 개발 인증서가 메시지를 발견하라는 메시지가 표시되면 다음을 수행합니다.
이 문제를 해결하기 위해 'dotnet dev-certs https' 도구를 실행하는 데 동의하려면 예 클릭합니다. 'dotnet dev-certs https' 도구는 인증서의 암호와 키 집합의 암호를 입력하라는 메시지를 표시합니다.
새 인증서설치 및 신뢰
메시지가 표시되면 예 클릭한 다음 키 집합의 암호를 입력합니다.
컨트롤러 폴더를 확장한 다음, WeatherForecastController.cs삭제합니다.
WeatherForecast.cs삭제합니다.
Secret Manager 도구사용하여 로컬 구성 값을 설정합니다. 솔루션에서 비밀을 분리하면 소스 제어에서 종료되지 않습니다. 터미널 열고 프로젝트 파일의 디렉터리로 이동하여 다음 명령을 실행합니다.
dotnet user-secrets init dotnet user-secrets set "NotificationHub:Name" <value> dotnet user-secrets set "NotificationHub:ConnectionString" <value>
자리 표시자 값을 고유한 알림 허브 이름 및 연결 문자열 값으로 바꿉니다. 알림 허브 섹션을 만드는
기록해 둡니다. 그렇지 않으면 Azure 조회할 수 있습니다. NotificationHub:Name:
개요맨 위에 있는 Essentials 요약에서 이름 참조하세요.NotificationHub:ConnectionString:
액세스 정책DefaultFullSharedAccessSignature 참조하세요.메모
프로덕션 시나리오의 경우 Azure KeyVault
같은 옵션을 확인하여 연결 문자열을 안전하게 저장할 수 있습니다. 간단히 하기 위해 비밀이 Azure App Service 애플리케이션 설정에 추가됩니다.
API 키를 사용하여 클라이언트 인증(선택 사항)
API 키는 토큰만큼 안전하지는 않지만 이 자습서의 용도로 충분합니다. API 키는 ASP.NET 미들웨어통해 쉽게 구성할 수 있습니다.
API 키 로컬 구성 값에 추가합니다.
dotnet user-secrets set "Authentication:ApiKey" <value>
메모
자리 표시자 값을 사용자 고유의 값으로 바꾸고 기록해 두어야 합니다.
제어 클릭하고PushDemoApi 프로젝트에서추가 메뉴에서 새 폴더선택한 다음 폴더 이름 인증 사용하여추가를 클릭합니다. 컨트롤 + 인증 폴더에서 클릭한 다음 추가 메뉴에서 새 파일... 선택합니다.
일반
빈 클래스 선택하고 이름 대한ApiKeyAuthOptions.cs 입력한 다음새로 만들기 클릭하여 다음 구현을 추가합니다.using Microsoft.AspNetCore.Authentication; namespace PushDemoApi.Authentication { public class ApiKeyAuthOptions : AuthenticationSchemeOptions { public const string DefaultScheme = "ApiKey"; public string Scheme => DefaultScheme; public string ApiKey { get; set; } } }
ApiKeyAuthHandler.csAuthentication 폴더에 다른 빈 클래스 추가한 다음, 다음 구현을 추가합니다.
using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace PushDemoApi.Authentication { public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOptions> { const string ApiKeyIdentifier = "apikey"; public ApiKeyAuthHandler( IOptionsMonitor<ApiKeyAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) {} protected override Task<AuthenticateResult> HandleAuthenticateAsync() { string key = string.Empty; if (Request.Headers[ApiKeyIdentifier].Any()) { key = Request.Headers[ApiKeyIdentifier].FirstOrDefault(); } else if (Request.Query.ContainsKey(ApiKeyIdentifier)) { if (Request.Query.TryGetValue(ApiKeyIdentifier, out var queryKey)) key = queryKey; } if (string.IsNullOrWhiteSpace(key)) return Task.FromResult(AuthenticateResult.Fail("No api key provided")); if (!string.Equals(key, Options.ApiKey, StringComparison.Ordinal)) return Task.FromResult(AuthenticateResult.Fail("Invalid api key.")); var identities = new List<ClaimsIdentity> { new ClaimsIdentity("ApiKeyIdentity") }; var ticket = new AuthenticationTicket( new ClaimsPrincipal(identities), Options.Scheme); return Task.FromResult(AuthenticateResult.Success(ticket)); } } }
메모
인증 처리기 구성표의 동작(이 경우 사용자 지정 API 키 체계)을 구현하는 형식입니다.
ApiKeyAuthenticationBuilderExtensions.cs라는 인증 폴더에 다른 빈 클래스 추가한 다음, 다음 구현을 추가합니다.
using System; using Microsoft.AspNetCore.Authentication; namespace PushDemoApi.Authentication { public static class AuthenticationBuilderExtensions { public static AuthenticationBuilder AddApiKeyAuth( this AuthenticationBuilder builder, Action<ApiKeyAuthOptions> configureOptions) { return builder .AddScheme<ApiKeyAuthOptions, ApiKeyAuthHandler>( ApiKeyAuthOptions.DefaultScheme, configureOptions); } } }
메모
이 확장 메서드는 Startup.cs 미들웨어 구성 코드를 간소화하여 더 읽기 쉽고 일반적으로 따라하기 쉽습니다.
Startup.csConfigureServices 메서드를 업데이트하여 서비스에 대한 호출 아래에 API 키 인증을 구성합니다. AddControllers 메서드.
using PushDemoApi.Authentication; using PushDemoApi.Models; using PushDemoApi.Services; public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme; options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme; }).AddApiKeyAuth(Configuration.GetSection("Authentication").Bind); }
Startup.cs Configure 메서드를 업데이트하여UseAuthentication 호출하고 앱의IApplicationBuilder UseAuthorization 확장 메서드를. UseRouting 후와 앱을 전에 해당 메서드가 호출되는지 확인합니다. UseEndpoints .public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
메모
UseAuthentication 호출하면 이전에 등록된 인증 체계(ConfigureServices)를 사용하는 미들웨어가 등록됩니다. 인증되는 사용자에 따라 달라지는 미들웨어 전에 호출해야 합니다.
종속성 추가 및 서비스 구성
ASP.NET Core는 클래스와 해당 종속성 간의 IoC(Inversion of Control) 달성하기 위한 기술인 DI(종속성 주입) 소프트웨어 디자인 패턴을 지원합니다.
백 엔드 작업 알림 허브 및
컨트롤 + 종속성 폴더에서 클릭한 다음 NuGet 패키지 관리를 선택합니다..
Microsoft.Azure.NotificationHubs
검색하고 확인합니다. 패키지 추가클릭한 다음 사용 조건에 동의하라는 메시지가 표시되면 수락을 클릭합니다.
컨트롤 클릭하고PushDemoApi 프로젝트에서추가 메뉴에서 새 폴더선택한 다음 모델 사용하여추가 폴더 이름 클릭합니다.컨트롤 + Models 폴더에서 클릭한 다음 추가 메뉴에서 새 파일... 선택합니다.
일반>빈 클래스선택하고 이름대한 PushTemplates.cs 입력한 다음 새로 만들기 클릭하여 다음 구현을 추가합니다.
namespace PushDemoApi.Models { public class PushTemplates { public class Generic { public const string Android = "{ \"notification\": { \"title\" : \"PushDemo\", \"body\" : \"$(alertMessage)\"}, \"data\" : { \"action\" : \"$(alertAction)\" } }"; public const string iOS = "{ \"aps\" : {\"alert\" : \"$(alertMessage)\"}, \"action\" : \"$(alertAction)\" }"; } public class Silent { public const string Android = "{ \"data\" : {\"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\"} }"; public const string iOS = "{ \"aps\" : {\"content-available\" : 1, \"apns-priority\": 5, \"sound\" : \"\", \"badge\" : 0}, \"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\" }"; } } }
메모
이 클래스에는 이 시나리오에 필요한 일반 및 자동 알림에 대한 토큰화된 알림 페이로드가 포함되어 있습니다. 페이로드는 서비스를 통해 기존 설치를 업데이트하지 않고도 실험을 허용하도록 설치 외부에서 정의됩니다. 이러한 방식으로 설치에 대한 변경 내용을 처리하는 것은 이 자습서의 범위를 벗어납니다. 프로덕션의 경우 사용자 지정 템플릿
것이 좋습니다. DeviceInstallation.csModels 폴더에 다른 빈 클래스 추가한 다음, 다음 구현을 추가합니다.
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PushDemoApi.Models { public class DeviceInstallation { [Required] public string InstallationId { get; set; } [Required] public string Platform { get; set; } [Required] public string PushChannel { get; set; } public IList<string> Tags { get; set; } = Array.Empty<string>(); } }
NotificationRequest.csModels 폴더에 다른 빈 클래스 추가한 다음, 다음 구현을 추가합니다.
using System; namespace PushDemoApi.Models { public class NotificationRequest { public string Text { get; set; } public string Action { get; set; } public string[] Tags { get; set; } = Array.Empty<string>(); public bool Silent { get; set; } } }
NotificationHubOptions.cs라는 Models 폴더에 다른 빈 클래스 추가한 다음, 다음 구현을 추가합니다.
using System.ComponentModel.DataAnnotations; namespace PushDemoApi.Models { public class NotificationHubOptions { [Required] public string Name { get; set; } [Required] public string ConnectionString { get; set; } } }
ServicesPushDemoApi 프로젝트에 새 폴더를 추가합니다.
INotificationService.csServices 폴더에 빈 인터페이스 추가한 다음, 다음 구현을 추가합니다.
using System.Threading; using System.Threading.Tasks; using PushDemoApi.Models; namespace PushDemoApi.Services { public interface INotificationService { Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token); Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token); Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token); } }
NotificationHubsService.csServices 폴더에 빈 클래스 추가한 다음 다음 코드를 추가하여 INotificationService 인터페이스를 구현합니다.
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using PushDemoApi.Models; namespace PushDemoApi.Services { public class NotificationHubService : INotificationService { readonly NotificationHubClient _hub; readonly Dictionary<string, NotificationPlatform> _installationPlatform; readonly ILogger<NotificationHubService> _logger; public NotificationHubService(IOptions<NotificationHubOptions> options, ILogger<NotificationHubService> logger) { _logger = logger; _hub = NotificationHubClient.CreateClientFromConnectionString( options.Value.ConnectionString, options.Value.Name); _installationPlatform = new Dictionary<string, NotificationPlatform> { { nameof(NotificationPlatform.Apns).ToLower(), NotificationPlatform.Apns }, { nameof(NotificationPlatform.Fcm).ToLower(), NotificationPlatform.Fcm } }; } public async Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token) { if (string.IsNullOrWhiteSpace(deviceInstallation?.InstallationId) || string.IsNullOrWhiteSpace(deviceInstallation?.Platform) || string.IsNullOrWhiteSpace(deviceInstallation?.PushChannel)) return false; var installation = new Installation() { InstallationId = deviceInstallation.InstallationId, PushChannel = deviceInstallation.PushChannel, Tags = deviceInstallation.Tags }; if (_installationPlatform.TryGetValue(deviceInstallation.Platform, out var platform)) installation.Platform = platform; else return false; try { await _hub.CreateOrUpdateInstallationAsync(installation, token); } catch { return false; } return true; } public async Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token) { if (string.IsNullOrWhiteSpace(installationId)) return false; try { await _hub.DeleteInstallationAsync(installationId, token); } catch { return false; } return true; } public async Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && (string.IsNullOrWhiteSpace(notificationRequest?.Text)) || string.IsNullOrWhiteSpace(notificationRequest?.Action))) return false; var androidPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.Android : PushTemplates.Generic.Android; var iOSPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.iOS : PushTemplates.Generic.iOS; var androidPayload = PrepareNotificationPayload( androidPushTemplate, notificationRequest.Text, notificationRequest.Action); var iOSPayload = PrepareNotificationPayload( iOSPushTemplate, notificationRequest.Text, notificationRequest.Action); try { if (notificationRequest.Tags.Length == 0) { // This will broadcast to all users registered in the notification hub await SendPlatformNotificationsAsync(androidPayload, iOSPayload, token); } else if (notificationRequest.Tags.Length <= 20) { await SendPlatformNotificationsAsync(androidPayload, iOSPayload, notificationRequest.Tags, token); } else { var notificationTasks = notificationRequest.Tags .Select((value, index) => (value, index)) .GroupBy(g => g.index / 20, i => i.value) .Select(tags => SendPlatformNotificationsAsync(androidPayload, iOSPayload, tags, token)); await Task.WhenAll(notificationTasks); } return true; } catch (Exception e) { _logger.LogError(e, "Unexpected error sending notification"); return false; } } string PrepareNotificationPayload(string template, string text, string action) => template .Replace("$(alertMessage)", text, StringComparison.InvariantCulture) .Replace("$(alertAction)", action, StringComparison.InvariantCulture); Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmNativeNotificationAsync(androidPayload, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, token) }; return Task.WhenAll(sendTasks); } Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, IEnumerable<string> tags, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmNativeNotificationAsync(androidPayload, tags, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, tags, token) }; return Task.WhenAll(sendTasks); } } }
메모
SendTemplateNotificationAsync
제공된 태그 식은 20개의 태그로 제한됩니다. 대부분의 연산자에 대해 6으로 제한되지만 이 경우 식에는 RS(||)만 포함됩니다. 요청에 20개 이상의 태그가 있는 경우 여러 요청으로 분할해야 합니다. 자세한 내용은 라우팅 및 태그 식 설명서를 참조하세요. Startup.csConfigureServices 메서드를 업데이트하여 NotificationHubsServiceINotificationService단일 구현으로 추가합니다.
using PushDemoApi.Models; using PushDemoApi.Services; public void ConfigureServices(IServiceCollection services) { ... services.AddSingleton<INotificationService, NotificationHubService>(); services.AddOptions<NotificationHubOptions>() .Configure(Configuration.GetSection("NotificationHub").Bind) .ValidateDataAnnotations(); }
알림 API 만들기
컨트롤 클릭한 다음컨트롤러 폴더에서추가 메뉴에서 새 파일...선택합니다. ASP.NET Core>Web API Controller 클래스선택하고 이름대한 NotificationsController 입력한 다음 새클릭합니다.
메모
visual Studio 2019
팔로우하는 경우 읽기/쓰기 작업 템플릿을 사용하여 API 컨트롤러를 선택합니다. 파일 맨 위에 다음 네임스페이스를 추가합니다.
using System.ComponentModel.DataAnnotations; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PushDemoApi.Models; using PushDemoApi.Services;
템플릿 기반 컨트롤러는 ControllerBase 파생되고 ApiController 특성으로 데코레이팅되도록 업데이트합니다.
[ApiController] [Route("api/[controller]")] public class NotificationsController : ControllerBase { // Templated methods here }
메모
Controller 기본 클래스는 보기를 지원하지만 이 경우에는 필요하지 않으므로 ControllerBase 대신 사용할 수 있습니다. Visual Studio 2019
팔로우하는 경우 이 단계를 건너뛸 수 있습니다. API 키 섹션을 사용하여
인증 클라이언트를 완료하도록 선택한 경우 권한 부여 특성으로NotificationsController 데코레이트해야 합니다.[Authorize]
INotificationService 등록된 인스턴스를 인수로 수락하도록 생성자를 업데이트하고 읽기 전용 멤버에 할당합니다.
readonly INotificationService _notificationService; public NotificationsController(INotificationService notificationService) { _notificationService = notificationService; }
launchSettings.json(속성 폴더 내)에서 launchUrl
weatherforecast
api/notifications 변경하여 RegistrationsController경로 특성에 지정된 URL과 일치하도록 합니다.디버깅(명령 + enter)을 시작하여 앱이 새 NotificationsController 작동하는지 확인하고 401 권한 없는 상태를 반환합니다.
메모
Visual Studio는 브라우저에서 앱을 자동으로 시작하지 않을 수 있습니다. 이 시점부터 Postman 사용하여 API를 테스트합니다.
새
탭에서가져오기를Postman 요청을 설정합니다. 자리 표시자 <applicationUrl>속성>launchSettings.json있는 https applicationUrl 바꿔서 아래 주소를 입력합니다. <applicationUrl>/api/notifications
메모
applicationUrl 기본 프로필의 경우 'https://localhost:5001'이어야 합니다.
IIS 사용하는 경우(Windows의 visual Studio 2019기본값) iisSettings 항목에 지정된applicationUrl 사용해야 합니다. 주소가 올바르지 않으면 404 응답을 받게 됩니다.API 키 섹션을 사용하여
클라이언트 인증을 완료하도록 선택한 경우 apikey 값을 포함하도록 요청 헤더를 구성해야 합니다.열쇠 값 apikey <your_api_key> 보내기 단추를 클릭합니다.
메모
일부 JSON 콘텐츠와 함께 200 OK 상태를 받아야 합니다.
SSL 인증서 확인 경고가 표시되면설정 Postman 설정을요청 SSL 인증서 확인을 전환할 수 있습니다. NotificationsController.cs 템플릿 클래스 메서드를 다음 코드로 바꿉다.
[HttpPut] [Route("installations")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> UpdateInstallation( [Required]DeviceInstallation deviceInstallation) { var success = await _notificationService .CreateOrUpdateInstallationAsync(deviceInstallation, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpDelete()] [Route("installations/{installationId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<ActionResult> DeleteInstallation( [Required][FromRoute]string installationId) { var success = await _notificationService .DeleteInstallationByIdAsync(installationId, CancellationToken.None); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpPost] [Route("requests")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> RequestPush( [Required]NotificationRequest notificationRequest) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Text))) return new BadRequestResult(); var success = await _notificationService .RequestNotificationAsync(notificationRequest, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); }
API 앱 만들기
이제 백 엔드 서비스를 호스팅하기 위한 Azure App ServiceAPI 앱 만듭니다.
Azure Portal로그인합니다.
리소스만들기
클릭한 다음 API 앱 검색하여 선택한 다음만들기클릭합니다. 다음 필드를 업데이트한 다음 만들기클릭합니다.
앱 이름:
API 앱 전역적으로 고유한 이름을 입력합니다.구독:
알림 허브를 만든 것과 동일한 대상 구독 선택합니다.리소스 그룹:
알림 허브를 만든 것과 동일한 리소스 그룹 선택합니다.App Service 계획/위치:
새 App Service 계획 만들기메모
기본 옵션에서 SSL 지원이 포함된 계획으로 변경합니다. 그렇지 않으면 http 요청이 차단되지 않도록 모바일 앱을 사용할 때 적절한 단계를 수행해야 합니다.
Application Insights:
제안된 옵션(해당 이름을 사용하여 새 리소스가 생성됨)을 유지하거나 기존 리소스를 선택합니다.API 앱 프로비전되면 해당 리소스로 이동합니다.
개요맨 위에 있는 Essentials 요약에서 URL 속성을 기록해 둡니다. 이 URL은 이 자습서의 뒷부분에서 사용할 백 엔드 엔드포인트.
메모
URL은 앞에서 지정한 API 앱 이름을
https://<app_name>.azurewebsites.net
형식으로 사용합니다.목록에서 구성 선택합니다(설정아래).
아래 설정 각각에 대해 새 애플리케이션 설정 클릭하여 이름 및 값입력한 다음 확인클릭합니다.
이름 값 Authentication:ApiKey
<api_key_value> NotificationHub:Name
<hub_name_value> NotificationHub:ConnectionString
<hub_connection_string_value> 메모
이러한 설정은 이전에 사용자 설정에서 정의한 것과 동일한 설정입니다. 이러한 항목을 복사할 수 있어야 합니다.
인증:ApiKey 설정은 API 키 섹션을 사용하여인증 클라이언트를 완료하도록 선택한 경우에만 필요합니다. 프로덕션 시나리오의 경우 Azure KeyVault같은 옵션을 확인할 수 있습니다. 이러한 설정은 이 경우 편의성을 위해 애플리케이션 설정으로 추가되었습니다. 모든 애플리케이션 설정이 추가되면
저장을 클릭한 다음 계속 클릭합니다.
백 엔드 서비스 게시
다음으로, 모든 디바이스에서 액세스할 수 있도록 앱을 API 앱에 배포합니다.
메모
다음 단계는 Mac용 Visual Studio
아직 디버그릴리스 구성을 변경합니다.
컨트롤 클릭한 다음PushDemoApi 프로젝트를게시 메뉴에서Azure에 게시를 선택합니다. 이렇게 하라는 메시지가 표시되면 인증 흐름을 따릅니다. 이전 사용한 계정을 사용하여 API 앱 섹션을 만듭니다.
이전에 목록에서 만든 Azure App Service API 앱 게시 대상으로 선택한 다음 게시클릭합니다.
마법사를 완료한 후 Azure에 앱을 게시한 다음, 앱을 엽니다. 아직 URL 기록해 두지 않았습니다. 이 URL은 이 자습서의 뒷부분에서 사용되는 백 엔드 엔드포인트.
게시된 API 유효성 검사
새 탭을 열고 PUTPostman 요청을 설정하고 아래 주소를 입력합니다. 자리 표시자를 이전 기록한 기본 주소로 바꾸고 백 엔드 서비스 섹션을 게시합니다. https://<app_name>.azurewebsites.net/api/notifications/installations
메모
기본 주소는
https://<app_name>.azurewebsites.net/
형식이어야 합니다.API 키 섹션을 사용하여
클라이언트 인증을 완료하도록 선택한 경우 apikey 값을 포함하도록 요청 헤더를 구성해야 합니다.열쇠 값 apikey <your_api_key> 본문 원시 옵션을 선택한 다음 서식 옵션 목록에서JSON 선택한 다음 JSON 콘텐츠에일부 자리 표시자를 포함합니다. {}
보내기클릭합니다.
메모
서비스에서 422 UnprocessableEntity 상태를 수신해야 합니다.
1-4단계를 다시 수행하지만 이번에는 요청 엔드포인트를 지정하여 400 잘못된 요청 응답을 받는지 확인합니다.
https://<app_name>.azurewebsites.net/api/notifications/requests
메모
클라이언트 모바일 앱의 플랫폼별 정보가 필요하므로 유효한 요청 데이터를 사용하여 API를 테스트할 수 없습니다.
플랫폼 간 Flutter 애플리케이션 만들기
이 섹션에서는 플랫폼 간 방식으로 푸시 알림을 구현하는 Flutter 모바일 애플리케이션을 빌드합니다.
이를 통해 만든 백 엔드 서비스를 통해 알림 허브에서 등록 및 등록을 취소할 수 있습니다.
작업이 지정되고 앱이 포그라운드에 있을 때 경고가 표시됩니다. 그렇지 않으면 알림 센터에 알림이 표시됩니다.
메모
일반적으로 명시적 사용자 등록/등록 취소 입력 없이 애플리케이션 수명 주기의 적절한 시점(또는 첫 실행 환경의 일부로) 동안 등록(및 등록 취소) 작업을 수행합니다. 그러나 이 예제에서는 이 기능을 보다 쉽게 탐색하고 테스트할 수 있도록 명시적 사용자 입력이 필요합니다.
Flutter 솔루션 만들기
Visual Studio Code새 인스턴스를 엽니다.
명령 팔레트(Shift + 명령 + P)를 엽니다.
Flutter: 새 프로젝트 명령을 선택한 다음 enter누릅니다.
프로젝트 이름push_demo 입력한 다음 프로젝트 위치선택합니다.
이렇게 하라는 메시지가 표시되면 패키지 가져오기
선택합니다. 컨트롤 클릭한 다음 Finderkotlin 폴더(앱 src 기본 아래)에서표시를 선택합니다. 그런 다음 자식 폴더(kotlin 폴더 아래)의 이름을 각각 com
,<your_organization>
및pushdemo
바꿉니다.메모
Visual Studio Code 템플릿을 사용하는 경우 이러한 폴더는 기본적으로 com예제 , . 조직mobcat 사용한다고 가정하면 폴더 구조는 다음과 같이 표시됩니다.project_name - kotlin
- com
- mobcat
- pushdemo
- mobcat
- com
- kotlin
Visual Studio Code
android 앱의 applicationId 값을build.gradle 업데이트합니다. 메모
<your_organization> 자리 표시자에 고유한 조직 이름을 사용해야 합니다. 예를 들어 조직에서 mobcat 사용하면 com.mobcat.pushdemo패키지 이름 값이 생성됩니다.
AndroidManifest.xml 파일의 패키지 특성, src>디버그, src>기본및 src>프로필 각각 업데이트합니다. 값이 이전 단계에서 사용한 applicationId 일치하는지 확인합니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.<your_organization>.pushdemo>"> ... </manifest>
src 주 아래의AndroidManifest.xml 파일에서특성을 업데이트하여 PushDemo . 그런 다음 바로 아래에 특성을 추가하여 해당 값을 false 설정합니다. <application android:name="io.flutter.app.FlutterApplication" android:label="PushDemo" android:allowBackup="false" android:icon="@mipmap/ic_launcher"> ... </application>
app-level
build.gradle 파일(androidapp build.gradle )을 연 다음 aPI 29 사용하도록android 섹션에서compileSdkVersion 업데이트합니다. 그런 다음 minSdkVersion 및 targetSdkVersion 값(defaultConfig 섹션에서)을 각각 26개의29개의 업데이트합니다.메모
이 자습서에서는 API 수준 26 이상을 실행하는 디바이스만 지원되지만 이전 버전을 실행하는 디바이스를 지원하도록 확장할 수 있습니다.
컨트롤 클릭한 다음 Xcodeios 폴더에서열기를 선택합니다. XcodeRunner 클릭합니다(폴더가 아닌 맨 위에 있는 xcodeproj). 그런 다음 Runner 대상을 선택하고 일반 탭을 선택합니다. 모든 빌드 구성을 선택한 상태에서 번들 식별자 업데이트하여
com.<your_organization>.PushDemo
.메모
<your_organization> 자리 표시자에 고유한 조직 이름을 사용해야 합니다. 예를 들어 조직으로 mobcat 사용하면 com.mobcat.PushDemo번들 식별자 값이 생성됩니다.
Info.plist 클릭한 다음번들 이름 값을 pushDemo업데이트합니다. Xcode 닫고 visual Studio Code돌아갑니다. visual Studio Code
pubspec.yaml 열고,http 추가하고, 다트 패키지를 종속성으로flutter_secure_storage . 그런 다음 파일을 저장하고 패키지 가져오기 클릭합니다. dependencies: flutter: sdk: flutter http: ^0.12.1 flutter_secure_storage: ^3.3.3
터미널디렉터리를 ios 폴더(Flutter 프로젝트의 경우)로 변경합니다. 그런 다음 Pod 설치 명령을 실행하여 새 Pod를 설치합니다(flutter_secure_storage 패키지에 필요).
Control + lib 폴더에서 클릭한 다음 main_page.dart 파일 이름으로 사용하여 메뉴에서 새 파일 선택합니다. 그런 다음, 다음 코드를 추가합니다.
import 'package:flutter/material.dart'; class MainPage extends StatefulWidget { @override _MainPageState createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[], ) ) ); } }
main.dart템플릿 코드를 다음으로 바꿉다.
import 'package:flutter/material.dart'; import 'package:push_demo/main_page.dart'; final navigatorKey = GlobalKey<NavigatorState>(); void main() => runApp(MaterialApp(home: MainPage(), navigatorKey: navigatorKey));
터미널각 대상 플랫폼에서 앱을 빌드하고 실행하여 디바이스에서 템플릿 기반 앱 실행을 테스트합니다. 지원되는 디바이스가 연결되어 있는지 확인합니다.
flutter run
플랫폼 간 구성 요소 구현
컨트롤 클릭한 다음lib 폴더에서폴더 이름 모델을 사용하여 메뉴에서 새 폴더 선택합니다.Control + 모델 폴더에서 클릭한 다음 device_installation.dart 파일 이름으로 사용하여 메뉴에서 새 파일 선택합니다. 그런 다음, 다음 코드를 추가합니다.
class DeviceInstallation { final String deviceId; final String platform; final String token; final List<String> tags; DeviceInstallation(this.deviceId, this.platform, this.token, this.tags); DeviceInstallation.fromJson(Map<String, dynamic> json) : deviceId = json['installationId'], platform = json['platform'], token = json['pushChannel'], tags = json['tags']; Map<String, dynamic> toJson() => { 'installationId': deviceId, 'platform': platform, 'pushChannel': token, 'tags': tags, }; }
이 예제에서 지원되는 작업의 열거형을 정의하는 push_demo_action.dart모델 폴더에 새 파일을 추가합니다.
enum PushDemoAction { actionA, actionB, }
services라는 프로젝트에 새 폴더를 추가한 다음 구현을 사용하여 device_installation_service.dart 폴더에 새 파일을 추가합니다.
import 'package:flutter/services.dart'; class DeviceInstallationService { static const deviceInstallation = const MethodChannel('com.<your_organization>.pushdemo/deviceinstallation'); static const String getDeviceIdChannelMethod = "getDeviceId"; static const String getDeviceTokenChannelMethod = "getDeviceToken"; static const String getDevicePlatformChannelMethod = "getDevicePlatform"; Future<String> getDeviceId() { return deviceInstallation.invokeMethod(getDeviceIdChannelMethod); } Future<String> getDeviceToken() { return deviceInstallation.invokeMethod(getDeviceTokenChannelMethod); } Future<String> getDevicePlatform() { return deviceInstallation.invokeMethod(getDevicePlatformChannelMethod); } }
메모
<your_organization> 자리 표시자에 고유한 조직 이름을 사용해야 합니다. 예를 들어 조직으로 mobcat 사용하면 com.mobcat.pushdemo/deviceinstallationMethodChannel 이름이 생성됩니다.
이 클래스는 필수 디바이스 설치 세부 정보를 얻기 위해 기본 네이티브 플랫폼으로 작업하는 것을 캡슐화합니다. MethodChannel 기본 네이티브 플랫폼과의 양방향 비동기 통신을 용이하게 합니다. 이 채널에 대한 플랫폼별 대응은 이후 단계에서 만들어집니다.
다음 구현을 사용하여 notification_action_service.dart 폴더에 다른 파일을 추가합니다.
import 'package:flutter/services.dart'; import 'dart:async'; import 'package:push_demo/models/push_demo_action.dart'; class NotificationActionService { static const notificationAction = const MethodChannel('com.<your_organization>.pushdemo/notificationaction'); static const String triggerActionChannelMethod = "triggerAction"; static const String getLaunchActionChannelMethod = "getLaunchAction"; final actionMappings = { 'action_a' : PushDemoAction.actionA, 'action_b' : PushDemoAction.actionB }; final actionTriggeredController = StreamController.broadcast(); NotificationActionService() { notificationAction .setMethodCallHandler(handleNotificationActionCall); } Stream get actionTriggered => actionTriggeredController.stream; Future<void> triggerAction({action: String}) async { if (!actionMappings.containsKey(action)) { return; } actionTriggeredController.add(actionMappings[action]); } Future<void> checkLaunchAction() async { final launchAction = await notificationAction.invokeMethod(getLaunchActionChannelMethod) as String; if (launchAction != null) { triggerAction(action: launchAction); } } Future<void> handleNotificationActionCall(MethodCall call) async { switch (call.method) { case triggerActionChannelMethod: return triggerAction(action: call.arguments as String); default: throw MissingPluginException(); break; } } }
메모
강력한 형식의 열거형을 사용하여 플랫폼 간 방식으로 처리할 수 있도록 알림 작업의 처리를 중앙 집중화하는 간단한 메커니즘으로 사용됩니다. 이 서비스를 사용하면 기본 네이티브 플랫폼이 알림 페이로드에 지정된 경우 작업을 트리거할 수 있습니다. 또한 일반 코드는 Flutter가 처리할 준비가 되면 애플리케이션을 시작하는 동안 작업이 지정되었는지 여부를 소급하여 확인할 수 있습니다. 예를 들어 알림 센터에서 알림을 탭하여 앱을 시작할 때입니다.
다음 구현을 사용하여 notification_registration_service.dart서비스 폴더에 새 파일을 추가합니다.
import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:push_demo/services/device_installation_service.dart'; import 'package:push_demo/models/device_installation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class NotificationRegistrationService { static const notificationRegistration = const MethodChannel('com.<your_organization>.pushdemo/notificationregistration'); static const String refreshRegistrationChannelMethod = "refreshRegistration"; static const String installationsEndpoint = "api/notifications/installations"; static const String cachedDeviceTokenKey = "cached_device_token"; static const String cachedTagsKey = "cached_tags"; final deviceInstallationService = DeviceInstallationService(); final secureStorage = FlutterSecureStorage(); String baseApiUrl; String apikey; NotificationRegistrationService(this.baseApiUrl, this.apikey) { notificationRegistration .setMethodCallHandler(handleNotificationRegistrationCall); } String get installationsUrl => "$baseApiUrl$installationsEndpoint"; Future<void> deregisterDevice() async { final cachedToken = await secureStorage.read(key: cachedDeviceTokenKey); final serializedTags = await secureStorage.read(key: cachedTagsKey); if (cachedToken == null || serializedTags == null) { return; } var deviceId = await deviceInstallationService.getDeviceId(); if (deviceId.isEmpty) { throw "Unable to resolve an ID for the device."; } var response = await http .delete("$installationsUrl/$deviceId", headers: {"apikey": apikey}); if (response.statusCode != 200) { throw "Deregister request failed: ${response.reasonPhrase}"; } await secureStorage.delete(key: cachedDeviceTokenKey); await secureStorage.delete(key: cachedTagsKey); } Future<void> registerDevice(List<String> tags) async { try { final deviceId = await deviceInstallationService.getDeviceId(); final platform = await deviceInstallationService.getDevicePlatform(); final token = await deviceInstallationService.getDeviceToken(); final deviceInstallation = DeviceInstallation(deviceId, platform, token, tags); final response = await http.put(installationsUrl, body: jsonEncode(deviceInstallation), headers: {"apikey": apikey, "Content-Type": "application/json"}); if (response.statusCode != 200) { throw "Register request failed: ${response.reasonPhrase}"; } final serializedTags = jsonEncode(tags); await secureStorage.write(key: cachedDeviceTokenKey, value: token); await secureStorage.write(key: cachedTagsKey, value: serializedTags); } on PlatformException catch (e) { throw e.message; } catch (e) { throw "Unable to register device: $e"; } } Future<void> refreshRegistration() async { final currentToken = await deviceInstallationService.getDeviceToken(); final cachedToken = await secureStorage.read(key: cachedDeviceTokenKey); final serializedTags = await secureStorage.read(key: cachedTagsKey); if (currentToken == null || cachedToken == null || serializedTags == null || currentToken == cachedToken) { return; } final tags = jsonDecode(serializedTags); return registerDevice(tags); } Future<void> handleNotificationRegistrationCall(MethodCall call) async { switch (call.method) { case refreshRegistrationChannelMethod: return refreshRegistration(); default: throw MissingPluginException(); break; } } }
메모
이 클래스는 DeviceInstallationService 사용 및 백 엔드 서비스에 대한 요청을 캡슐화하여 필수 등록, 등록 취소 및 새로 고침 등록 작업을 수행합니다.
apiKey 인수는 API 키 섹션을 사용하여인증 클라이언트를 완료하도록 선택한 경우에만 필요합니다. 다음 구현을 사용하여 config.dart라는 lib 폴더에 새 파일을 추가합니다.
class Config { static String apiKey = "API_KEY"; static String backendServiceEndpoint = "BACKEND_SERVICE_ENDPOINT"; }
메모
이는 앱 비밀을 정의하는 간단한 방법으로 사용됩니다. 자리 표시자 값을 사용자 고유의 값으로 바꿉 있습니다. 백 엔드 서비스를 빌드할 때 이러한 사항을 기록해 두어야 합니다. API 앱 URL
https://<api_app_name>.azurewebsites.net/
합니다.apiKey 멤버는 API 키 섹션을 사용하여인증 클라이언트를 완료하도록 선택한 경우에만 필요합니다. 이러한 비밀을 소스 제어에 커밋하지 않도록 gitignore 파일에 추가해야 합니다.
플랫폼 간 UI 구현
main_page.dart빌드 함수를 다음으로 바꿉다.
@override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ FlatButton( child: Text("Register"), onPressed: registerButtonClicked, ), FlatButton( child: Text("Deregister"), onPressed: deregisterButtonClicked, ), ], ), ), ); }
필수 가져오기를 main_page.dart 파일의 맨 위에 추가합니다.
import 'package:push_demo/services/notification_registration_service.dart'; import 'config.dart';
_MainPageState 클래스에 필드를 추가하여 NotificationRegistrationService대한 참조를 저장합니다.
final notificationRegistrationService = NotificationRegistrationService(Config.backendServiceEndpoint, Config.apiKey);
_MainPageState 클래스에서 onPressed 이벤트를등록 및등록 취소 단추에 대한 이벤트 처리기를 구현합니다. 해당 Register/등록 취소 메서드를 호출한 다음 결과를 나타내는 경고를 표시합니다.void registerButtonClicked() async { try { await notificationRegistrationService.registerDevice(List<String>()); await showAlert(message: "Device registered"); } catch (e) { await showAlert(message: e); } } void deregisterButtonClicked() async { try { await notificationRegistrationService.deregisterDevice(); await showAlert(message: "Device deregistered"); } catch (e) { await showAlert(message: e); } } Future<void> showAlert({ message: String }) async { return showDialog<void>( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text('PushDemo'), content: SingleChildScrollView( child: ListBody( children: <Widget>[ Text(message), ], ), ), actions: <Widget>[ FlatButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }
이제 main.dart다음 가져오기가 파일 맨 위에 있는지 확인합니다.
import 'package:flutter/material.dart'; import 'package:push_demo/models/push_demo_action.dart'; import 'package:push_demo/services/notification_action_service.dart'; import 'package:push_demo/main_page.dart';
NotificationActionService 인스턴스에 대한 참조를 저장할 변수를 선언하고 초기화합니다.
final notificationActionService = NotificationActionService();
작업이 트리거될 때 경고 표시를 처리하는 함수를 추가합니다.
void notificationActionTriggered(PushDemoAction action) { showActionAlert(message: "${action.toString().split(".")[1]} action received"); } Future<void> showActionAlert({ message: String }) async { return showDialog<void>( context: navigatorKey.currentState.overlay.context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text('PushDemo'), content: SingleChildScrollView( child: ListBody( children: <Widget>[ Text(message), ], ), ), actions: <Widget>[ FlatButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }
기본 함수를 업데이트하여 NotificationActionServiceactionTriggered 스트림을 관찰하고 앱 시작 중에 캡처된 작업을 확인합니다.
void main() async { runApp(MaterialApp(home: MainPage(), navigatorKey: navigatorKey,)); notificationActionService.actionTriggered.listen((event) { notificationActionTriggered(event as PushDemoAction); }); await notificationActionService.checkLaunchAction(); }
메모
이는 푸시 알림 작업의 수신 및 전파를 보여주기 위한 것입니다. 일반적으로 이 경우 경고를 표시하지 않고 특정 보기로 이동하거나 일부 데이터를 새로 고치는 경우처럼 자동으로 처리됩니다.
푸시 알림에 대한 네이티브 Android 프로젝트 구성
Google Services JSON 파일 추가
Control 클릭한 다음 Android Studioandroid 폴더에서열기를 선택합니다. 그런 다음 프로젝트 보기로 전환합니다(아직 없는 경우). Firebase 콘솔PushDemo 프로젝트를 설정할 때 이전에 다운로드한 google-services.json 파일을 찾습니다. 그런 다음, 앱 모듈 루트 디렉터리(android>android>앱)로 끌어옵니다.
빌드 설정 및 권한 구성
프로젝트 보기를 Android전환합니다. AndroidManifest.xml연 다음, 닫는 태그 앞에 애플리케이션 요소 다음에 INTERNET 및 READ_PHONE_STATE 권한을 추가합니다.
<manifest> <application>...</application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> </manifest>
Firebase SDK 추가
android Studio
build.gradle 파일( Gradle Scripts build.gradle(Project: android) )프로젝트 수준 파일을 엽니다. buildscript
> 종속성 노드에 'com.google.gms:google-services' 클래스 경로가 있는지 확인합니다.buildscript { repositories { // Check that you have the following line (if not, add it): google() // Google's Maven repository } dependencies { // ... // Add the following line: classpath 'com.google.gms:google-services:4.3.3' // Google Services plugin } } allprojects { // ... repositories { // Check that you have the following line (if not, add it): google() // Google's Maven repository // ... } }
메모
Android Project만들 때 Firebase 콘솔 제공된 지침에 따라 최신 버전을 참조해야 합니다.
앱 수준
build.gradle 파일(Gradle Scripts build.gradle(Module: app) )에서 google Services Gradle 플러그 인적용합니다. android 노드 바로 위에 플러그 인을 적용합니다. // ... // Add the following line: apply plugin: 'com.google.gms.google-services' // Google Services plugin android { // ... }
동일한 파일의 종속성 노드에서 Cloud Messaging Android 라이브러리에 대한 종속성을 추가합니다.
dependencies { // ... implementation 'com.google.firebase:firebase-messaging:20.2.0' }
메모
Cloud Messaging Android 클라이언트 설명서따라 최신 버전을 참조해야 합니다.
변경 내용을 저장한 다음 도구 모음 프롬프트에서
지금 동기화 단추를 클릭하거나 Gradle Files프로젝트 동기화를.
Android에 대한 푸시 알림 처리
Android Studio Control 클릭합니다.com에서 your_organization .pushdemo 패키지 폴더( 앱 src 기본 kotlin )는새로 만들기 메뉴에서패키지 선택합니다. 서비스 이름으로 입력한 다음 return누릅니다.컨트롤 + 서비스 폴더에서 클릭하고 새 메뉴에서 Kotlin 파일/클래스 선택합니다. DeviceInstallationService 이름으로 입력한 다음 Return누릅니다.
다음 코드를 사용하여 DeviceInstallationService 구현합니다.
package com.<your_organization>.pushdemo.services import android.annotation.SuppressLint import android.content.Context import android.provider.Settings.Secure import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @SuppressLint("HardwareIds") class DeviceInstallationService { companion object { const val DEVICE_INSTALLATION_CHANNEL = "com.<your_organization>.pushdemo/deviceinstallation" const val GET_DEVICE_ID = "getDeviceId" const val GET_DEVICE_TOKEN = "getDeviceToken" const val GET_DEVICE_PLATFORM = "getDevicePlatform" } private var context: Context private var deviceInstallationChannel : MethodChannel val playServicesAvailable get() = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS constructor(context: Context, flutterEngine: FlutterEngine) { this.context = context deviceInstallationChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, DEVICE_INSTALLATION_CHANNEL) deviceInstallationChannel.setMethodCallHandler { call, result -> handleDeviceInstallationCall(call, result) } } fun getDeviceId() : String = Secure.getString(context.applicationContext.contentResolver, Secure.ANDROID_ID) fun getDeviceToken() : String { if(!playServicesAvailable) { throw Exception(getPlayServicesError()) } // TODO: Revisit once we have created the PushNotificationsFirebaseMessagingService val token = "Placeholder_Get_Value_From_FirebaseMessagingService_Implementation" if (token.isNullOrBlank()) { throw Exception("Unable to resolve token for FCM.") } return token } fun getDevicePlatform() : String = "fcm" private fun handleDeviceInstallationCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { GET_DEVICE_ID -> { result.success(getDeviceId()) } GET_DEVICE_TOKEN -> { getDeviceToken(result) } GET_DEVICE_PLATFORM -> { result.success(getDevicePlatform()) } else -> { result.notImplemented() } } } private fun getDeviceToken(result: MethodChannel.Result) { try { val token = getDeviceToken() result.success(token) } catch (e: Exception) { result.error("ERROR", e.message, e) } } private fun getPlayServicesError(): String { val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) if (resultCode != ConnectionResult.SUCCESS) { return if (GoogleApiAvailability.getInstance().isUserResolvableError(resultCode)){ GoogleApiAvailability.getInstance().getErrorString(resultCode) } else { "This device is not supported" } } return "An error occurred preventing the use of push notifications" } }
메모
이 클래스는
com.<your_organization>.pushdemo/deviceinstallation
채널에 대한 플랫폼별 대응을 구현합니다. DeviceInstallationService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 공통 코드에서 네이티브 호스트로 호출됩니다. <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 바꾸어야 합니다. 이 클래스는 알림 허브 등록 페이로드의 일부로 고유한 ID(Secure.AndroidId사용)를 제공합니다.
NotificationRegistrationService서비스 폴더에 다른 Kotlin 파일/클래스 추가한 다음, 다음 코드를 추가합니다.
package com.<your_organization>.pushdemo.services import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class NotificationRegistrationService { companion object { const val NOTIFICATION_REGISTRATION_CHANNEL = "com.<your_organization>.pushdemo/notificationregistration" const val REFRESH_REGISTRATION = "refreshRegistration" } private var notificationRegistrationChannel : MethodChannel constructor(flutterEngine: FlutterEngine) { notificationRegistrationChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, NotificationRegistrationService.NOTIFICATION_REGISTRATION_CHANNEL) } fun refreshRegistration() { notificationRegistrationChannel.invokeMethod(REFRESH_REGISTRATION, null) } }
메모
이 클래스는
com.<your_organization>.pushdemo/notificationregistration
채널에 대한 플랫폼별 대응을 구현합니다. 이는 notificationRegistrationService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 네이티브 호스트에서 공통 코드로 호출됩니다. 다시 말하지만, <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 교체해야 합니다. NotificationActionService서비스 폴더에 다른 Kotlin 파일/클래스 추가한 다음, 다음 코드를 추가합니다.
package com.<your_organization>.pushdemo.services import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class NotificationActionService { companion object { const val NOTIFICATION_ACTION_CHANNEL = "com.<your_organization>.pushdemo/notificationaction" const val TRIGGER_ACTION = "triggerAction" const val GET_LAUNCH_ACTION = "getLaunchAction" } private var notificationActionChannel : MethodChannel var launchAction : String? = null constructor(flutterEngine: FlutterEngine) { notificationActionChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, NotificationActionService.NOTIFICATION_ACTION_CHANNEL) notificationActionChannel.setMethodCallHandler { call, result -> handleNotificationActionCall(call, result) } } fun triggerAction(action: String) { notificationActionChannel.invokeMethod(NotificationActionService.TRIGGER_ACTION, action) } private fun handleNotificationActionCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { NotificationActionService.GET_LAUNCH_ACTION -> { result.success(launchAction) } else -> { result.notImplemented() } } } }
메모
이 클래스는
com.<your_organization>.pushdemo/notificationaction
채널에 대한 플랫폼별 대응을 구현합니다. 이는 NotificationActionService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 양방향으로 호출할 수 있습니다. <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 바꾸어야 합니다. com에 새 Kotlin 파일/클래스 추가합니다.<your_organization>.pushdemo 패키지인 PushNotificationsFirebaseMessagingService다음 코드를 사용하여 구현합니다.
package com.<your_organization>.pushdemo import android.os.Handler import android.os.Looper import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.<your_organization>.pushdemo.services.NotificationActionService import com.<your_organization>.pushdemo.services.NotificationRegistrationService class PushNotificationsFirebaseMessagingService : FirebaseMessagingService() { companion object { var token : String? = null var notificationRegistrationService : NotificationRegistrationService? = null var notificationActionService : NotificationActionService? = null } override fun onNewToken(token: String) { PushNotificationsFirebaseMessagingService.token = token notificationRegistrationService?.refreshRegistration() } override fun onMessageReceived(message: RemoteMessage) { message.data.let { Handler(Looper.getMainLooper()).post { notificationActionService?.triggerAction(it.getOrDefault("action", null)) } } } }
메모
이 클래스는 앱이 포그라운드에서 실행 중일 때 알림을 처리합니다. onMessageReceived수신된 알림 페이로드에 작업이 포함된 경우 NotificationActionServicetriggerAction 조건부로 호출합니다. 또한 Firebase 토큰이 onNewToken 함수를 재정의하여 다시 생성될 때 NotificationRegistrationServicerefreshRegistration 호출합니다.
다시 한 번 <your_organization> 사용하는 모든 조직으로 교체합니다.
AndroidManifest.xml(앱>src>기본)에서
com.google.firebase.MESSAGING_EVENT
의도 필터를 사용하여 애플리케이션 요소의 맨 아래에 PushNotificationsFirebaseMessagingService 추가합니다.<manifest> <application> <!-- EXISTING MANIFEST CONTENT --> <service android:name="com.<your_organization>.pushdemo.PushNotificationsFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> </application> </manifest>
DeviceInstallationService
파일 맨 위에 다음 가져오기가 있는지 확인합니다. package com.<your_organization>.pushdemo import com.<your_organization>.pushdemo.services.PushNotificationsFirebaseMessagingService
메모
<your_organization> 고유한 조직 값으로 대체합니다.
자리 표시자 텍스트 Placeholder_Get_Value_From_FirebaseMessagingService_Implementation 업데이트하여 PushNotificationFirebaseMessagingService토큰 값을 가져옵니다.
fun getDeviceToken() : String { if(!playServicesAvailable) { throw Exception(getPlayServicesError()) } // Get token from the PushNotificationsFirebaseMessagingService.token field. val token = PushNotificationsFirebaseMessagingService.token if (token.isNullOrBlank()) { throw Exception("Unable to resolve token for FCM.") } return token }
MainActivity파일 맨 위에 다음 가져오기가 있는지 확인합니다.
package com.<your_organization>.pushdemo import android.content.Intent import android.os.Bundle import com.google.android.gms.tasks.OnCompleteListener import com.google.firebase.iid.FirebaseInstanceId import com.<your_organization>.pushdemo.services.DeviceInstallationService import com.<your_organization>.pushdemo.services.NotificationActionService import com.<your_organization>.pushdemo.services.NotificationRegistrationService import io.flutter.embedding.android.FlutterActivity
메모
<your_organization> 고유한 조직 값으로 대체합니다.
DeviceInstallationService대한 참조를 저장할 변수를 추가합니다.
private lateinit var deviceInstallationService: DeviceInstallationService
processNotificationActions 함수를 추가하여 의도작업명명된 추가 값이 있는지 확인합니다. 앱 시작 중에 작업이 처리되는 경우 해당 작업을 조건부로 트리거하거나 나중에 사용할 수 있도록 저장합니다.
private fun processNotificationActions(intent: Intent, launchAction: Boolean = false) { if (intent.hasExtra("action")) { var action = intent.getStringExtra("action"); if (action.isNotEmpty()) { if (launchAction) { PushNotificationsFirebaseMessagingService.notificationActionService?.launchAction = action } else { PushNotificationsFirebaseMessagingService.notificationActionService?.triggerAction(action) } } } }
onNewIntent 함수를 재정의하여 processNotificationActions호출합니다.
override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) processNotificationActions(intent) }
메모
MainActivity 대한LaunchMode SingleTop설정되므로 의도 통해 기존 활동 인스턴스로 전송됩니다.onCreate 함수가 아닌 함수를onCreate onNewIntent 함수 모두에서 들어오는의도 처리해야 합니다.onCreate 함수를 재정의하고 deviceInstallationServiceDeviceInstallationService새 인스턴스로 설정합니다.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) } }
PushNotificationFirebaseMessagingServicesnotificationActionService 및 notificationRegistrationService 속성을 설정합니다.
flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) PushNotificationsFirebaseMessagingService.notificationActionService = NotificationActionService(it) PushNotificationsFirebaseMessagingService.notificationRegistrationService = NotificationRegistrationService(it) }
동일한 함수에서 조건부로 FirebaseInstanceId.getInstance().instanceId호출합니다. OnCompleteListener 구현하여 refreshRegistration호출하기 전에 PushNotificationFirebaseMessagingService 결과 토큰 값을 설정합니다.
if(deviceInstallationService?.playServicesAvailable) { FirebaseInstanceId.getInstance().instanceId .addOnCompleteListener(OnCompleteListener { task -> if (!task.isSuccessful) return@OnCompleteListener PushNotificationsFirebaseMessagingService.token = task.result?.token PushNotificationsFirebaseMessagingService.notificationRegistrationService?.refreshRegistration() }) }
여전히 onCreate함수의 끝에서 processNotificationActions 호출합니다. launchAction 인수에 true 사용하여 앱 시작 중에 이 작업이 처리되고 있음을 나타냅니다.
processNotificationActions(this.intent, true)
메모
푸시 알림을 계속 받으려면 실행할 때마다 앱을 다시 등록하고 디버그 세션에서 중지해야 합니다.
푸시 알림에 대한 네이티브 iOS 프로젝트 구성
실행기 대상 및 Info.plist 구성
Visual Studio Code Control 클릭한 다음 Xcodeios 폴더에서열기를 선택합니다. Xcode
Runner (폴더가 아닌 맨 위에 있는xcodeproj )를 클릭한 다음Runner 대상을 선택한 다음 서명 & 기능. 모든 빌드 구성을 선택한 상태에서 팀개발자 계정을 선택합니다. "자동으로 서명 관리" 옵션이 선택되어 있고 서명 인증서 및 프로비저닝 프로필이 자동으로 선택되어 있는지 확인합니다. 메모
새 프로비저닝 프로필 값이 표시되지 않으면 Xcode>기본 설정>계정 선택하여 서명 ID에 대한 프로필을 새로 고침한 다음, 수동 프로필 다운로드 단추를 선택하여 프로필을 다운로드합니다.
+ 기능클릭한 다음 푸시 알림검색합니다.
푸시 알림 두 번 클릭하여 이 기능을 추가합니다.Info.plist 열고 최소 시스템 버전13.0설정합니다.
메모
이 자습서에서는 iOS 13.0 이상에서
실행하는 디바이스만 지원되지만 이전 버전을 실행하는 디바이스를 지원하도록 확장할 수 있습니다. Runner.entitlements 열고APS 환경 설정이 개발설정되었는지 확인합니다.
iOS에 대한 푸시 알림 처리
Control 클릭한 다음Runner 폴더(Runner 프로젝트 내)에서Services 이름으로 사용하여 새 그룹선택합니다. Control + Services 폴더에서 클릭한 다음 새 파일...선택합니다. 그런 다음, Swift 파일 선택하고 다음클릭합니다. 이름에
DeviceInstallationService 지정한 다음만들기클릭합니다. 다음 코드를 사용하여 deviceInstallationService.swift
구현합니다. import Foundation class DeviceInstallationService { enum DeviceRegistrationError: Error { case notificationSupport(message: String) } var token : Data? = nil let DEVICE_INSTALLATION_CHANNEL = "com.<your_organization>.pushdemo/deviceinstallation" let GET_DEVICE_ID = "getDeviceId" let GET_DEVICE_TOKEN = "getDeviceToken" let GET_DEVICE_PLATFORM = "getDevicePlatform" private let deviceInstallationChannel : FlutterMethodChannel var notificationsSupported : Bool { get { if #available(iOS 13.0, *) { return true } else { return false } } } init(withBinaryMessenger binaryMessenger : FlutterBinaryMessenger) { deviceInstallationChannel = FlutterMethodChannel(name: DEVICE_INSTALLATION_CHANNEL, binaryMessenger: binaryMessenger) deviceInstallationChannel.setMethodCallHandler(handleDeviceInstallationCall) } func getDeviceId() -> String { return UIDevice.current.identifierForVendor!.description } func getDeviceToken() throws -> String { if(!notificationsSupported) { let notificationSupportError = getNotificationsSupportError() throw DeviceRegistrationError.notificationSupport(message: notificationSupportError) } if (token == nil) { throw DeviceRegistrationError.notificationSupport(message: "Unable to resolve token for APNS.") } return token!.reduce("", {$0 + String(format: "%02X", $1)}) } func getDevicePlatform() -> String { return "apns" } private func handleDeviceInstallationCall(call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case GET_DEVICE_ID: result(getDeviceId()) case GET_DEVICE_TOKEN: getDeviceToken(result: result) case GET_DEVICE_PLATFORM: result(getDevicePlatform()) default: result(FlutterMethodNotImplemented) } } private func getDeviceToken(result: @escaping FlutterResult) { do { let token = try getDeviceToken() result(token) } catch let error { result(FlutterError(code: "UNAVAILABLE", message: error.localizedDescription, details: nil)) } } private func getNotificationsSupportError() -> String { if (!notificationsSupported) { return "This app only supports notifications on iOS 13.0 and above. You are running \(UIDevice.current.systemVersion)" } return "An error occurred preventing the use of push notifications." } }
메모
이 클래스는
com.<your_organization>.pushdemo/deviceinstallation
채널에 대한 플랫폼별 대응을 구현합니다. DeviceInstallationService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 공통 코드에서 네이티브 호스트로 호출됩니다. <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 바꾸어야 합니다. 이 클래스는 알림 허브 등록 페이로드의 일부로 고유한 ID(UIDevice.identifierForVendor 값 사용)를 제공합니다.
NotificationRegistrationServiceServices 폴더에 다른 Swift 파일 추가한 다음, 다음 코드를 추가합니다.
import Foundation class NotificationRegistrationService { let NOTIFICATION_REGISTRATION_CHANNEL = "com.<your_organization>.pushdemo/notificationregistration" let REFRESH_REGISTRATION = "refreshRegistration" private let notificationRegistrationChannel : FlutterMethodChannel init(withBinaryMessenger binaryMessenger : FlutterBinaryMessenger) { notificationRegistrationChannel = FlutterMethodChannel(name: NOTIFICATION_REGISTRATION_CHANNEL, binaryMessenger: binaryMessenger) } func refreshRegistration() { notificationRegistrationChannel.invokeMethod(REFRESH_REGISTRATION, arguments: nil) } }
메모
이 클래스는
com.<your_organization>.pushdemo/notificationregistration
채널에 대한 플랫폼별 대응을 구현합니다. 이는 notificationRegistrationService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 네이티브 호스트에서 공통 코드로 호출됩니다. 다시 말하지만, <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 교체해야 합니다. NotificationActionServiceServices 폴더에 다른 Swift 파일 추가한 다음, 다음 코드를 추가합니다.
import Foundation class NotificationActionService { let NOTIFICATION_ACTION_CHANNEL = "com.<your_organization>.pushdemo/notificationaction" let TRIGGER_ACTION = "triggerAction" let GET_LAUNCH_ACTION = "getLaunchAction" private let notificationActionChannel: FlutterMethodChannel var launchAction: String? = nil init(withBinaryMessenger binaryMessenger: FlutterBinaryMessenger) { notificationActionChannel = FlutterMethodChannel(name: NOTIFICATION_ACTION_CHANNEL, binaryMessenger: binaryMessenger) notificationActionChannel.setMethodCallHandler(handleNotificationActionCall) } func triggerAction(action: String) { notificationActionChannel.invokeMethod(TRIGGER_ACTION, arguments: action) } private func handleNotificationActionCall(call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case GET_LAUNCH_ACTION: result(launchAction) default: result(FlutterMethodNotImplemented) } } }
메모
이 클래스는
com.<your_organization>.pushdemo/notificationaction
채널에 대한 플랫폼별 대응을 구현합니다. 이는 NotificationActionService.dart앱의 Flutter 부분에 정의되었습니다. 이 경우 양방향으로 호출할 수 있습니다. <your_organization> 사용하는 모든 곳에서 사용자 고유의 조직으로 바꾸어야 합니다. AppDelegate.swift이전에 만든 서비스에 대한 참조를 저장할 변수를 추가합니다.
var deviceInstallationService : DeviceInstallationService? var notificationRegistrationService : NotificationRegistrationService? var notificationActionService : NotificationActionService?
알림 데이터를 처리하기 위해 processNotificationActions 함수를 추가합니다. 앱 시작 중에 작업이 처리되는 경우 해당 작업을 조건부로 트리거하거나 나중에 사용할 수 있도록 저장합니다.
func processNotificationActions(userInfo: [AnyHashable : Any], launchAction: Bool = false) { if let action = userInfo["action"] as? String { if (launchAction) { notificationActionService?.launchAction = action } else { notificationActionService?.triggerAction(action: action) } } }
DeviceInstallationService대한 토큰 값을 설정하는 didRegisterForRemoteNotificationsWithDeviceToken 함수를 재정의합니다. 그런 다음 NotificationRegistrationServicerefreshRegistration 호출합니다.
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { deviceInstallationService?.token = deviceToken notificationRegistrationService?.refreshRegistration() }
userInfo 인수를 processNotificationActions 함수에 전달하는 didReceiveRemoteNotification 함수를 재정의합니다.
override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { processNotificationActions(userInfo: userInfo) }
didFailToRegisterForRemoteNotificationsWithError 함수를 재정의하여 오류를 기록합니다.
override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print(error); }
메모
이것은 매우 자리 표시자입니다. 프로덕션 시나리오에 대한 적절한 로깅 및 오류 처리를 구현하려고 합니다.
didFinishLaunchingWithOptions deviceInstallationService, notificationRegistrationService인스턴스화하고 notificationActionService 변수를 . let controller : FlutterViewController = window?.rootViewController as! FlutterViewController deviceInstallationService = DeviceInstallationService(withBinaryMessenger: controller.binaryMessenger) notificationRegistrationService = NotificationRegistrationService(withBinaryMessenger: controller.binaryMessenger) notificationActionService = NotificationActionService(withBinaryMessenger: controller.binaryMessenger)
동일한 함수에서 조건부로 권한 부여를 요청하고 원격 알림을 등록합니다.
if #available(iOS 13.0, *) { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in if (granted) { DispatchQueue.main.async { let pushSettings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil) application.registerUserNotificationSettings(pushSettings) application.registerForRemoteNotifications() } } } }
launchOptionsremoteNotification 키가 포함된 경우 didFinishLaunchingWithOptions 함수의 끝에서 processNotificationActions 호출합니다. 결과 userInfo 개체를 전달하고 launchAction 인수에 true 사용합니다. true 값은 앱 시작 중에 작업이 처리되고 있음을 표시합니다.
if let userInfo = launchOptions?[.remoteNotification] as? [AnyHashable : Any] { processNotificationActions(userInfo: userInfo, launchAction: true) }
솔루션 테스트
이제 백 엔드 서비스를 통해 알림 보내기를 테스트할 수 있습니다.
테스트 알림 보내기
Postman새 탭을 엽니다.
POST
요청을 설정하고 다음 주소를 입력합니다. https://<app_name>.azurewebsites.net/api/notifications/requests
API 키 섹션을 사용하여
클라이언트 인증을 완료하도록 선택한 경우 apikey 값을 포함하도록 요청 헤더를 구성해야 합니다.열쇠 값 apikey <your_api_key> 본문 원시 옵션을 선택한 다음 서식 옵션 목록에서JSON 선택한 다음 JSON 콘텐츠에일부 자리 표시자를 포함합니다. { "text": "Message from Postman!", "action": "action_a" }
창의 오른쪽 위에 있는 저장 단추 아래에 있는 코드 단추를 선택합니다. 요청은 apikey 헤더를 포함했는지 여부에 따라 HTML 대해 표시되는 경우 다음 예제와 유사하게 표시됩니다.
POST /api/notifications/requests HTTP/1.1 Host: https://<app_name>.azurewebsites.net apikey: <your_api_key> Content-Type: application/json { "text": "Message from backend service", "action": "action_a" }
대상 플랫폼(Android 및 iOS)에서 PushDemo 애플리케이션을 실행합니다.
메모
android
테스트하는 경우 디버그 실행되지 않는지 또는 애플리케이션을 실행하여 앱이 배포되었는지 확인하려면 앱을 강제로 닫고 시작 관리자에서 다시 시작합니다.PushDemo 앱에서 등록 단추를 탭합니다.
Postman돌아가서 코드 조각 생성 창을 닫은 다음(아직 수행하지 않은 경우) 보내기 단추를 클릭합니다.
Postman 200 OK 응답을 받고 알림이 앱에 표시되어받은ActionA 작업이 표시되는지 확인합니다. PushDemo 앱을 닫은 다음 Postman 보내기 단추를 다시 클릭합니다.
Postman200 OK 응답을 다시 받을 수 있는지 확인합니다. 알림이 올바른 메시지와 함께 PushDemo 앱의 알림 영역에 표시되는지 확인합니다.
알림을 탭하여 앱을 열고 알림에 수신된 ActionA 작업이 표시되어 있음을 확인합니다.
Postman다시 작업 값에 대한 action_a 대신 action_b 지정하는 자동 알림을 보내도록 이전 요청 본문을 수정합니다.
{ "action": "action_b", "silent": true }
앱이 계속 열려 있는 상태에서 Postman 보내기 단추를 클릭합니다.
Postman 200 OK 응답을 받고 ActionA 작업이받은대신 ActionB 작업이 수신되었음을 보여 주는 경고가 앱에 표시되는지 확인합니다.PushDemo 앱을 닫은 다음 Postman 보내기 단추를 다시 클릭합니다.
Postman200 OK 응답을 받고 알림 영역에 자동 알림이 표시되지 않는지 확인합니다.
문제 해결
백 엔드 서비스의 응답 없음
로컬로 테스트할 때 백 엔드 서비스가 실행 중이고 올바른 포트를 사용하고 있는지 확인합니다.
Azure API 앱대해 테스트하는 경우 서비스가 실행 중이고 배포되었으며 오류 없이 시작되었는지 확인합니다.
클라이언트를 통해 테스트할 때 Postman 또는 모바일 앱 구성에서 기본 주소를 올바르게 지정했는지 확인해야 합니다. 로컬로 테스트할 때 기본 주소는 https://<api_name>.azurewebsites.net/
또는 https://localhost:5001/
나타냅니다.
디버그 세션을 시작하거나 중지한 후 Android에서 알림을 수신하지 않음
디버그 세션을 시작하거나 중지한 후 다시 등록해야 합니다. 디버거를 사용하면 새 Firebase 토큰이 생성됩니다. 알림 허브 설치도 업데이트해야 합니다.
백 엔드 서비스에서 401 상태 코드 받기
apikey 요청 헤더를 설정하고 이 값이 백 엔드 서비스에 대해 구성한 것과 일치하는지 확인합니다.
로컬로 테스트할 때 이 오류가 표시되는 경우 클라이언트 구성에서 정의한 키 값이 API사용되는 Authentication:ApiKey 사용자 설정 값과 일치하는지 확인합니다.
API 앱테스트하는 경우 클라이언트 구성 파일의 키 값이 API 앱사용 중인 Authentication:ApiKey 애플리케이션 설정과 일치하는지 확인합니다.
메모
백 엔드 서비스를 배포한 후 이 설정을 만들거나 변경한 경우 서비스를 적용하려면 서비스를 다시 시작해야 합니다.
API 키 섹션을 사용하여
백 엔드 서비스에서 404 상태 코드 받기
엔드포인트 및 HTTP 요청 메서드가 올바른지 확인합니다. 예를 들어 엔드포인트는 다음을 나타내야 합니다.
-
[PUT]
https://<api_name>.azurewebsites.net/api/notifications/installations
-
[DELETE]
https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
-
[POST]
https://<api_name>.azurewebsites.net/api/notifications/requests
또는 로컬로 테스트할 때:
-
[PUT]
https://localhost:5001/api/notifications/installations
-
[DELETE]
https://localhost:5001/api/notifications/installations/<installation_id>
-
[POST]
https://localhost:5001/api/notifications/requests
클라이언트 앱에서 기본 주소를 지정할 때 /
끝나는지 확인합니다. 로컬로 테스트할 때 기본 주소는 https://<api_name>.azurewebsites.net/
또는 https://localhost:5001/
나타냅니다.
등록할 수 없고 알림 허브 오류 메시지가 표시됩니다.
테스트 디바이스에 네트워크 연결이 있는지 확인합니다. 그런 다음 중단점을 설정하여 HttpResponseStatusCode 속성 값을 검사합니다.
상태 코드에 따라 적용할 수 있는 이전 문제 해결 제안을 검토합니다.
해당 API에 대해 이러한 특정 상태 코드를 반환하는 줄에 중단점을 설정합니다. 그런 다음 로컬로 디버깅할 때 백 엔드 서비스를 호출해 봅니다.
적절한 페이로드를 사용하여 Postman 통해 백 엔드 서비스가 예상대로 작동하는지 확인합니다. 해당 플랫폼에 대한 클라이언트 코드에서 만든 실제 페이로드를 사용합니다.
플랫폼별 구성 섹션을 검토하여 누락된 단계가 없는지 확인합니다. 적절한 플랫폼에 대한 installation id
및 token
변수에 적합한 값이 확인되고 있는지 확인합니다.
디바이스 오류 메시지의 ID를 확인할 수 없음이 표시됩니다.
플랫폼별 구성 섹션을 검토하여 누락된 단계가 없는지 확인합니다.
관련 링크
- azure Notification Hubs 개요
- macOS Flutter 설치
- Windows Flutter 설치
- 백 엔드 작업에 대한 Notification Hubs SDK
- GitHub Notification Hubs SDK
- 애플리케이션 백 엔드 등록
- 등록 관리
- 태그 작업
- 사용자 지정 템플릿 작업
다음 단계
이제 백 엔드 서비스를 통해 알림 허브에 연결된 기본 Flutter 앱이 있어야 하며 알림을 보내고 받을 수 있습니다.
이 자습서에 사용된 예제를 사용자 고유의 시나리오에 맞게 조정해야 할 수 있습니다. 보다 강력한 오류 처리, 재시도 논리 및 로깅을 구현하는 것도 좋습니다.