Руководство. Отправка push-уведомлений в приложения Flutter с помощью Центров уведомлений Azure через серверную службу
В этом руководстве вы используете Центры уведомлений Azure для отправки push-уведомлений в приложение Flutter, предназначенные для Android и iOS.
Серверная часть веб-API
Эти операции обрабатываются с помощью пакета SDK для центров уведомлений для внутренних операций. Дополнительные сведения о общем подходе приведены в документации по регистрации из серверной части приложения документации.
В этом руководстве описаны следующие действия.
- настройка служб push-уведомлений и Центров уведомлений Azure.
- создание серверного приложения ASP.NET Core Web API.
- создание кроссплатформенного приложения Flutter.
- настроить собственный проект Android для push-уведомлений.
- Настроить собственный проект iOS для push-уведомлений.
- протестировать решение.
Необходимые условия
Для выполнения дальнейших инструкций вам потребуется:
- подписке Azure, где можно создавать ресурсы и управлять ими.
- Набор средств Flutter (а также необходимые компоненты).
- Visual Studio Code с установленными подключаемы ми модулями Flutter и Dart.
- CocoaPods установлен для управления зависимостями библиотеки.
- Возможность запуска приложения на Android (физические или эмуляторные устройства) или iOS (только физические устройства).
Для Android необходимо иметь следующее:
- Разработчик разблокировал физическое устройство или эмулятор (с установленным API 26 и более поздних версий с установленными службами Google Play).
Для iOS необходимо:
- Активная учетной записи разработчика Apple.
- Физическое устройство iOS, зарегистрированное в учетной записи разработчика(под управлением iOS 13.0 и более поздних версий).
- сертификата разработки
.p12 , установленного в цепочке ключей , что позволяетзапускать приложение на физическом устройстве .
Заметка
Симулятор iOS не поддерживает удаленные уведомления, поэтому при изучении этого примера в iOS требуется физическое устройство. Однако для выполнения этого руководства не требуется запускать приложение как на Android, так и iOS.
Вы можете выполнить действия, описанные в этом примере с использованием первых принципов без предыдущего опыта. Тем не менее, вы получите преимущество от знакомства со следующими аспектами.
- портал разработчика Apple.
- ASP.NET Core.
- консоли Google Firebase.
- Microsoft Azure и отправлять push-уведомления в приложения iOS с помощью Центров уведомлений Azure.
- Flutter и Dart для кроссплатформенной разработки.
- Kotlin и Swift для разработки в машинном коде Android и iOS.
Описанные действия относятся к macOS. Вы можете следовать Windows, пропуская аспекты iOS.
Настройка служб push-уведомлений и Центра уведомлений Azure
В этом разделе описано, как настроить Firebase Cloud Messaging (FCM) и службы push-уведомлений Apple (APNS). Затем вы создаете и настраиваете концентратор уведомлений для работы с этими службами.
Создание проекта Firebase и включение Firebase Cloud Messaging для Android
Войдите вконсоли Firebase
. Создайте проект Firebase, введющий PushDemo в качестве имени проекта. Заметка
Для вас будет создано уникальное имя. По умолчанию это состоит из нижнего регистра имени, указанного плюс созданного числа, разделенного дефисом. Это можно изменить, если вы хотите предоставить его по-прежнему глобально уникальным.
После создания проекта выберите Добавить Firebase в приложение Android.
На странице приложения Android
добавить Firebase выполните следующие действия. В поле имя пакета Androidвведите имя пакета. Например,
com.<organization_identifier>.<package_name>
.Выберите Зарегистрировать приложение.
Выберите Скачать google-services.json. Затем сохраните файл в локальную папку для последующего использования и нажмите кнопку Далее.
Выберите Далее.
Выберите Продолжить консоль
Заметка
Если кнопка
продолжить консоли не включена, из-за проверки установкинажмите кнопку Пропустить этот шаг .
В консоли Firebase выберите шестеренку для проекта. Затем выберите параметры проекта.
Заметка
Если вы не скачали файл google-services.json, его можно скачать на этой странице.
Перейдите на вкладку Cloud Messaging в верхней части. Скопируйте и сохраните ключа сервера
для последующего использования. Это значение используется для настройки концентратора уведомлений.
Регистрация приложения iOS для push-уведомлений
Чтобы отправить push-уведомления в приложение iOS, зарегистрируйте приложение в Apple, а также зарегистрируйтесь для push-уведомлений.
Если вы еще не зарегистрировали приложение, перейдите на портал подготовки iOS
в Центре разработчиков Apple. Войдите на портал с помощью идентификатора Apple ID, перейдите к сертификатам, идентификаторам & профилям, а затем выберите идентификаторы. Щелкните +, чтобы зарегистрировать новое приложение. На экране Регистрация нового идентификатора выберите идентификаторы приложений переключателя. Затем выберите Продолжить.
Обновите следующие три значения для нового приложения, а затем выберите Продолжить:
описание. Введите описательное имя приложения.
идентификатор пакета : введите идентификатор пакета формыcom. , как упоминалось вруководстве по распространению приложенийorganization_identifier . product_name . На следующем снимке экрана значение mobcat
используется в качестве идентификатора организации, а значение PushDemo используется в качестве имени продукта.страницы идентификатора приложения
push-уведомлений . Проверьте параметрpush-уведомлений в разделе Возможности .форма
Это действие создает идентификатор приложения и запросы, которые вы подтверждаете информацию. Выберите продолжить, а затем выберите Зарегистрировать, чтобы подтвердить новый идентификатор приложения.
После выбора регистрациивы увидите новый идентификатор приложения в виде элемента строки на странице Сертификатов, идентификаторов & профилей.
На странице "Сертификаты
идентификаторы" & профили в разделе "Идентификаторы" найдите созданный элемент строки идентификатор а приложения. Затем выберите строку, чтобы отобразить экран Изменить конфигурацию идентификатора приложения.
Создание сертификата для центров уведомлений
Сертификат необходим, чтобы центр уведомлений работал с службами push-уведомлений Apple (APNS) и может быть предоставлен одним из двух способов:
создание push-сертификата p12, который можно отправить непосредственно в центр уведомлений (исходного подхода)
создание сертификата p8, который можно использовать для проверки подлинности на основе маркеров (более новый и рекомендуемый подход)
Более новый подход имеет ряд преимуществ, как описано в проверке подлинности на основе токенов (HTTP/2) дляAPNS. Для конкретных сценариев требуется меньше шагов, но также требуется. Однако для обоих подходов были предоставлены шаги, так как они будут работать в целях этого руководства.
ВАРИАНТ 1. Создание push-сертификата p12, который можно отправить непосредственно в Центр уведомлений
На компьютере Mac запустите средство доступа к цепочке ключей. Его можно открыть из папки Служебные программы или папку Other на панели запуска.
Выберите
доступ к цепочке ключей , развернитепомощника по сертификату, а затем выберите Запросить сертификат из центра сертификации .Заметка
По умолчанию доступ к цепочке ключей выбирает первый элемент в списке. Это может быть проблема, если вы находитесь в категории сертификатов и Центр сертификации Apple По всему миру разработчиков не является первым элементом в списке. Убедитесь, что у вас есть элемент, отличный от ключа, или Центр сертификации Apple По всему миру разработчиков ключ выбран, прежде чем создавать CSR (запрос на подписи сертификатов).
Выберите
адрес электронной почты пользователя, введите значение общего имени , убедитесь, чтосохранено на диске , а затем нажмите кнопкуПродолжить . Оставьте адрес электронной почты ЦС пустым, так как он не требуется.Введите имя файла
запроса подписи сертификата (CSR) в сохранить как , выберите расположение вгде , а затем нажмите кнопкуСохранить .Это действие сохраняет CSR-файл в выбранном расположении. Расположение по умолчанию — desktop. Помните расположение, выбранное для файла.
Вернитесь на страницу сертификатов, идентификаторов & профилей на портале подготовки iOS, прокрутите страницу вниз до флажка push-уведомлений, а затем выберите Настроить для создания сертификата.
Появится появится окно TLS/SSL-сертификат ов службы push-уведомлений Apple. Нажмите кнопку создать сертификат в разделе Сертификат TLS/SSL.
Отображается экран создание нового сертификата.
Заметка
В этом руководстве используется сертификат разработки. При регистрации рабочего сертификата используется тот же процесс. Просто убедитесь, что при отправке уведомлений используется тот же тип сертификата.
Выберите Выбрать файл, перейдите к расположению, где вы сохранили CSR-файл, а затем дважды щелкните имя сертификата, чтобы загрузить его. Затем выберите Продолжить.
После создания сертификата на портале нажмите кнопку "Скачать". Сохраните сертификат и запомните расположение, в котором он сохранен.
Сертификат скачан и сохранен на компьютер в папке загрузки.
Заметка
По умолчанию скачанный сертификат разработки называется aps_development.cer.
Дважды щелкните скачанный push-сертификат aps_development.cer. Это действие устанавливает новый сертификат в цепочке ключей, как показано на следующем рисунке:
Заметка
Хотя имя в сертификате может отличаться, имя будет префиксировано с помощью push-служб Apple Development iOS и имеет соответствующий идентификатор пакета.
В access Control + Click на новом push-сертификате, созданном в категории сертификатов. Выберите экспорт, назовите файл, выберите формат p12 и нажмите кнопку Сохранить.
Вы можете защитить сертификат паролем, но пароль необязателен. Нажмите кнопку ОК, если вы хотите обойти создание пароля. Запишите имя файла и расположение экспортированного сертификата p12. Они используются для включения проверки подлинности с помощью APN.
Заметка
Имя и расположение файла p12 могут отличаться от того, что изображено в этом руководстве.
ВАРИАНТ 2. Создание сертификата p8, который можно использовать для проверки подлинности на основе токенов
Запишите следующие сведения:
- префикс идентификатора приложения
( идентификатор команды ) - идентификатор пакета
- префикс идентификатора приложения
Вернитесь в сертификаты, идентификаторы & профили, щелкните ключи.
Заметка
Если у вас уже есть ключ, настроенный для APNS, можно повторно использовать сертификат p8, скачанный сразу после его создания. В этом случае можно игнорировать шаги 3 через 5.
Нажмите кнопку + (или кнопку Создать ключ), чтобы создать новый ключ.
Укажите подходящее значение имени ключа, а затем проверьте параметр службы push-уведомлений Apple (APNS), а затем нажмите кнопку Продолжить, а затем Зарегистрировать на следующем экране.
Щелкните Скачать, а затем переместите файл p8 (префикс с AuthKey_) в безопасный локальный каталог, а затем щелкните Готово.
Заметка
Не забудьте сохранить p8-файл в безопасном месте (и сохранить резервную копию). После скачивания ключа его невозможно повторно скачать по мере удаления копии сервера.
На клавишищелкните созданный ключ (или существующий ключ, если вы решили использовать это).
Запишите значение идентификатора ключа
. Откройте сертификат p8 в подходящем приложении, например Visual Studio Code. Запишите значение ключа (между закрытым ключом -----BEGIN----- и -----END PRIVATE KEY-----).
ЗАКРЫТЫЙ КЛЮЧ -----BEGIN-----
<key_value>
-----END PRIVATE KEY-----Заметка
Это значение маркера , которое будет использоваться позже для настройки концентратора уведомлений.
В конце этих действий вам должны быть приведены следующие сведения для последующего использования в Настройка концентратора уведомлений с помощью сведений APNS:
- идентификатор команды (см. шаг 1)
- идентификатор пакета (см. шаг 1)
- идентификатор ключа (см. шаг 7)
- значение маркера (значение ключа p8, полученное на шаге 8)
Создание профиля подготовки для приложения
Вернитесь кпортала подготовки iOS
, выберите сертификаты, идентификаторы & профили , выберитепрофили в меню слева, а затем выберите, чтобы создать новый профиль. Появится экран Регистрация нового профиля подготовки. Выберите
разработки приложений iOS в разделе "Разработка " в качестве типа профиля подготовки, а затем выберите "Продолжить ".Затем выберите идентификатор приложения, созданный в раскрывающемся списке идентификатор приложения, и выберите Продолжить.
В окне Выбор сертификатов выберите сертификат разработки, используемый для подписывания кода, и выберите Продолжить.
Заметка
Этот сертификат не является push-сертификатом, созданным на предыдущем шаге . Это ваш сертификат разработки. Если он не существует, необходимо создать его, так как это предварительный для этого руководства. Сертификаты разработчика можно создавать на портале разработчиков Apple,с помощью Xcode или Visual Studio.
Вернитесь на страницу профилей
, идентификаторы & профилей , выберитепрофили в меню слева, а затем выберите, чтобы создать новый профиль. Появится экран Регистрация нового профиля подготовки. В окне Выбор сертификатов выберите созданный сертификат разработки. Затем выберите Продолжить.
Затем выберите устройства, которые будут использоваться для тестирования, и выберите Продолжить.
Наконец, выберите имя профиля в имя профиля подготовкии выберите Создать.
При создании нового профиля подготовки выберите Скачать. Помните расположение, в котором он сохранен.
Перейдите к расположению профиля подготовки и дважды щелкните его, чтобы установить его на компьютере разработки.
Создание центра уведомлений
В этом разделе описано, как создать концентратор уведомлений и настроить проверку подлинности с помощью APNS. Вы можете использовать push-сертификат p12 или проверку подлинности на основе маркеров. Если вы хотите использовать созданный центр уведомлений, можно перейти к шагу 5.
Войдите в Azure.
Щелкните Создать ресурс, а затем найдите и выберите Центр уведомлений, а затем щелкните Создать.
Обновите следующие поля, а затем щелкните Создать:
БАЗОВЫЕ СВЕДЕНИЯ
подписка : выберите целевую подписку из раскрывающегося списка
группа ресурсов : создать новую группу ресурсов (или выбрать существующую)СВЕДЕНИЯ О ПРОСТРАНСТВЕ ИМЕН
пространство имен центра уведомлений : Введите глобально уникальное имя для пространства имен центра уведомлений
Заметка
Убедитесь, что для этого поля выбран параметр "Создать новую".
СВЕДЕНИЯ ЦЕНТРА УВЕДОМЛЕНИЙ
Центр уведомлений : введите имя центра уведомлений
расположение: Выбрать подходящее расположение из раскрывающегося списка
ценовая категория : Сохранить параметр бесплатного по умолчаниюЗаметка
Если вы не достигли максимального количества центров на уровне "Бесплатный".
После подготовки Центра уведомлений
перейдите к ресурсу. Перейдите к новому центру уведомлений .
Выберите политики доступа из списка (в разделе MANAGE).
Запишите значения имени политики
вместе со значениями строки подключения .
Настройка центра уведомлений с помощью сведений APNS
В разделеслужб уведомлений
Заметка
Используйте рабочую для режима приложений только в том случае, если вы хотите отправлять push-уведомления пользователям, которые приобрели приложение из магазина.
ВАРИАНТ 1. Использование push-сертификата P12
Выберитесертификата
. Щелкните значок файла.
Выберите P12-файл, экспортируемый ранее, и выберите Открыть.
При необходимости укажите правильный пароль.
Выберите режим песочницы.
Выберите Сохранить.
ВАРИАНТ 2. Использование проверки подлинности на основе токенов
Выберите токена.
Введите следующие значения, полученные ранее:
- идентификатор ключа
- идентификатор пакета
- идентификатор команды
- маркера
Выберите песочницу.
Выберите Сохранить.
Настройка концентратора уведомлений с помощью данных FCM
- Выберите Google (GCM/FCM) в разделе "Параметры " меню слева.
- Введите ключ сервера , который вы указали из консоли Google Firebase.
- Выберите Сохранить на панели инструментов.
Создание серверного приложения веб-API ASP.NET Core
В этом разделе описано, как создать ASP.NET веб-API core серверной части для обработки регистрации устройств и отправки уведомлений в мобильное приложение Flutter.
Создание веб-проекта
В Visual Studioвыберите файл>новый.
Выберите .NET Core>App>ASP.NET Core>API>Далее.
В диалоговом окне Настройка нового веб- API ASP.NET Core Web API выберите Target Framework.NET Core 3.1.
Введите PushDemoApi для имени проекта и выберите Создать.
Начните отладку (команда + ВВОД) для тестирования шаблонного приложения.
Заметка
Шаблонное приложение настроено для использования WeatherForecastController в качестве launchUrl. Этот параметр задан в Свойства>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 .
Проверка подлинности клиентов с помощью ключа 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.cs, а затем добавьте следующую реализацию.
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)); } } }
Добавьте еще один
пустой класс в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.csобновите метод ConfigureServices, чтобы настроить проверку подлинности ключа 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 иМетоды расширения UseAuthorization вIApplicationBuild er приложения. Убедитесь, что эти методы вызываются после 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 поддерживает шаблон внедрения зависимостей (DI) программного обеспечения, который является способом достижения инверсии управления (IoC) между классами и их зависимостями.
Использование концентратора уведомлений и пакета SDK для центров уведомлений для внутренних операций инкапсулируется в службе. Служба зарегистрирована и доступна с помощью подходящей абстракции.
Элемент управления + щелкните в папке зависимостей, а затем выберите Управление пакетами NuGet....
Выполните поиск Microsoft.Azure.NotificationHubs и убедитесь, что он установлен.
Щелкните Добавить пакеты, а затем щелкните Принять при появлении запроса на принятие условий лицензии.
Элемент управления щелкните в проектеPushDemoApi , выберитесоздать папку в меню"Добавить ", а затем щелкнитеДобавить с помощью моделейв качестве имени папки .Элемент управления Щелкните в папке модели, а затем выберитеСоздать файл... в менюДобавить .Выберите
Общие пустого класса , введите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.cs , а затем добавьте следующую реализацию.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>(); } }
Добавьте еще один пустой класс в папку Models с именем NotificationRequest.cs, а затем добавьте следующую реализацию.
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 , а затем добавьте следующую реализацию.using System.ComponentModel.DataAnnotations; namespace PushDemoApi.Models { public class NotificationHubOptions { [Required] public string Name { get; set; } [Required] public string ConnectionString { get; set; } } }
Добавьте новую папку в проект PushDemoApi с именем Services.
Добавьте пустого интерфейса
в папку служб с именем INotificationService.cs , а затем добавьте следующую реализацию.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.cs , а затем добавьте следующий код для реализации интерфейса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 для большинства операторов, но выражение содержит только OR (||) в этом случае. Если в запросе существует более 20 тегов, они должны быть разделены на несколько запросов. Дополнительные сведения см. в документации по
выражения маршрутизации и тегов. В
Startup.cs обновите методConfigureServices , чтобы добавить NotificationHubsServiceNotificationHubsService в качестве единой реализации INotificationService .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 уведомлений
Элемент управления щелкните в папке контроллеров, а затем выберите Создать файл... в менюДобавить .Выберитекласс контроллера веб-API
ASP.NET Core , введите 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 }
Заметка
Базовый класс контроллера
обеспечивает поддержку представлений, но в этом случае это не требуется, поэтому вместо этого можно использовать ControllerBase ControllerBase. Если вы используете Visual Studio 2019, этот шаг можно пропустить. Если вы решили завершить проверку подлинности клиентов
с помощью раздела ключа API, необходимо также украсить notificationsControllerс помощью атрибута авторизовать . [Authorize]
Обновите конструктор, чтобы принять зарегистрированный экземпляр INotificationService в качестве аргумента и назначить его элементу чтения.
readonly INotificationService _notificationService; public NotificationsController(INotificationService notificationService) { _notificationService = notificationService; }
В
launchSettings.json (в папке свойств) измените launchUrl сна api/notifications , чтобы он соответствовал URL-адресу, указанному в атрибутеRegistrationsController Route .Запустите отладку (команда + ВВОД) для проверки работы приложения с новым NotificationsController и возвращает состояние 401 Unauthorized.
Заметка
Visual Studio может не запускать приложение в браузере автоматически. Вы будете использовать Postman для тестирования API с этой точки.
На новой вкладке Postman задайте для запроса GET. Введите приведенный ниже адрес, заменив заполнитель
httpsapplicationUrl applicationUrl , найденный вlaunchSettings.json свойств. <applicationUrl>/api/notifications
Заметка
applicationUrl должен быть "https://localhost:5001" для профиля по умолчанию. Если вы используете IIS (по умолчанию в Visual Studio 2019 в Windows), следует использовать applicationUrl, указанные в элементе iisSettings. Если адрес неверный, вы получите ответ 404.
Если вы решили выполнить аутентификацию клиентов с помощью раздела ключа API, обязательно настройте заголовки запроса, чтобы включить значение apikey.
Ключ Ценность apikey <your_api_key> Нажмите кнопку "Отправить
". Заметка
Вы должны получить состояние
200 OK с некоторыми содержимого JSON .Если вы получаете предупреждение
проверки SSL-сертификата , можно переключить проверку проверки SSL-сертификата запроса впараметровPostman . Замените методы шаблонного класса в 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
Теперь вы создадите приложения API
Щелкните Создать ресурс, а затем найдите и выберите приложение API, а затем щелкните Создать.
Обновите следующие поля, а затем щелкните Создать.
имя приложения :
Введите глобально уникальное имя приложения APIподписка :
Выберите тот же целевой подписки, в которую вы создали концентратор уведомлений.Группа ресурсов :
Выберите ту же группу ресурсов, в которую вы создали концентратор уведомлений.план или расположение службы приложений :
Создание плана службы приложенийЗаметка
Переход от параметра по умолчанию к плану, который включает поддержку SSL-. В противном случае при работе с мобильным приложением необходимо выполнить соответствующие действия, чтобы предотвратить блокировку запросов http.
Application Insights:
Сохраните предлагаемый параметр (новый ресурс будет создан с помощью этого имени) или выберите существующий ресурс.После подготовки приложения API
перейдите к ресурсу. Запишите свойство URL-адреса
в сводке Essentials в верхней частиобзора. Этот 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, чтобы сделать его доступным на всех устройствах.
Заметка
Следующие действия относятся к Visual Studio для Mac. Если вы используете Visual Studio 2019 в Windows, поток публикации будет отличаться. См. статью Публикации в Службе приложений Azure в Windows.
Измените конфигурацию с отладки на выпуск, если это еще не сделано.
Элемент управления + щелкните проект PushDemoApi, а затем выберите Опубликовать в Azure... в меню публикации.
Следуйте потоку проверки подлинности, если появится запрос на это. Используйте учетную запись, используемую в предыдущем создайте приложение API.
Выберите приложение API службы приложений Azure, созданное ранее в списке в качестве целевого объекта публикации, а затем щелкните Опубликовать.
После завершения работы мастера он публикует приложение в Azure, а затем открывает приложение. Запишите URL-адрес , если это еще не сделано. Этот URL-адрес — это серверная конечная точка, которая используется далее в этом руководстве.
Проверка опубликованного API
В Postman откройте новую вкладку, задайте для запроса PUT и введите указанный ниже адрес. Замените заполнитель базовым адресом, который вы заметили в предыдущем опубликовать серверную службу раздела.
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 мобильное приложение, реализующее push-уведомления кроссплатформенным способом.
Он позволяет зарегистрировать и отменить регистрацию из центра уведомлений с помощью созданной серверной службы.
Оповещение отображается при указании действия и приложение находится на переднем плане. В противном случае уведомления отображаются в центре уведомлений.
Заметка
Обычно вы выполняете действия регистрации (и дерегистрации) во время соответствующего момента жизненного цикла приложения (или как часть первого запуска) без явного регистрации или отмены регистрации входных данных. Однако в этом примере потребуются явные входные данные пользователя, которые позволяют просматривать и тестировать эту функцию более легко.
Создание решения Flutter
Откройте новый экземпляр Visual Studio Code.
Откройте палитру команд
( shift Command P ).Выберите команду
Flutter: Создать проект и нажмите клавишу ВВОД. Введите
push_demo для именипроекта, а затем выберите расположение проекта .При появлении запроса выберите получить пакеты.
Элемент управления + щелкните в папке kotlin (в разделе приложения>src>main), а затем выберите Показать в finder. Затем переименуйте дочерние папки (в папке kotlin) в
com
,<your_organization>
иpushdemo
соответственно.Заметка
При использовании шаблона
Visual Studio Code эти папки по умолчанию используются для com ,примера . Если mobcat используется для организации, структура папок должна отображаться примерно следующим образом:project_name - kotlin
- com
- mobcat
- pushdemo
- mobcat
- com
- kotlin
Еще в Visual Studio Codeобновите значение applicationId в приложении android>>build.gradle до
com.<your_organization>.pushdemo
.Заметка
Для заполнителя <your_organization> следует использовать собственное имя организации. Например, использование mobcat, так как организация приведет к имени пакета значению com.mobcat.pushdemo.
Обновите атрибут пакета
в файлах AndroidManifest.xml , в разделеsrc отладки ,src main иsrc профилей соответственно. Убедитесь, что значения соответствуют applicationId, которые вы использовали на предыдущем шаге. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.<your_organization>.pushdemo>"> ... </manifest>
Обновите атрибут
android:label
в файле AndroidManifest.xml в разделе src>main, чтобы PushDemo. Затем добавьте атрибутandroid:allowBackup
непосредственно вandroid:label
, задав его значение false.<application android:name="io.flutter.app.FlutterApplication" android:label="PushDemo" android:allowBackup="false" android:icon="@mipmap/ic_launcher"> ... </application>
Откройте файл уровня приложения
build.gradle ( android app build.gradle ), а затем обновитекомпиляцииSdkVersion (из раздела android ) для использования API29 . Затем обновите значения minSdkVersion и targetSdkVersion (из раздела defaultConfig), чтобы 26 и 29 соответственно.Заметка
Для целей этого руководства поддерживаются только те устройства, на которых запущены уровня API 26 и более поздних версий.
Элемент управления + щелкните в папке ios, а затем выберите Открыть в Xcode.
В Xcodeщелкните Runner (xcodeproj вверху, а не папку). Затем выберите целевой объект
Runner и перейдите на вкладку "Общие ". Выбрав конфигурацию сборкиall , обновите идентификатор пакетадо . Заметка
Для заполнителя <your_organization> следует использовать собственное имя организации. Например, использование mobcat, так как организация приведет к идентификатору пакета значению com.mobcat.PushDemo.
Щелкните
Info.plist затем обновите имя пакетадо PushDemo Закройте Xcode и вернитесь к Visual Studio Code.
Еще в Visual Studio Codeоткройте pubspec.yaml, добавьте http и flutter_secure_storageпакеты Dart в качестве зависимостей. Затем сохраните файл и щелкните получить пакеты при появлении запроса.
dependencies: flutter: sdk: flutter http: ^0.12.1 flutter_secure_storage: ^3.3.3
В терминалеизмените каталог на папку ios (для проекта Flutter). Затем выполните команду pod install, чтобы установить новые модули pod (требуется пакетом flutter_secure_storage).
Элемент управления щелкните в папке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 , а затем выберитесоздать папку в меню с помощью моделейв качестве имени папки. Элемент управления щелкните в папке моделей, а затем выберите создать файл в меню с помощью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, }
Добавьте новую папку в проект с именем служб затем добавьте в нее новый файл с именем 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, так как организация приведет к MethodChannel имени com.mobcat.pushdemo/deviceinstallation.
Этот класс инкапсулирует работу с базовой собственной платформой для получения необходимых сведений об установке устройства. 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.
Добавьте новый файл в папку
lib с именем config.dart со следующей реализацией.class Config { static String apiKey = "API_KEY"; static String backendServiceEndpoint = "BACKEND_SERVICE_ENDPOINT"; }
Заметка
Это используется в качестве простого способа определения секретов приложения. Замените значения заполнителей собственными. При создании серверной службы необходимо заметить их. URL-адрес приложения API
должен быть . Элемент apiKey требуется только в том случае, если вы решили выполнить проверку подлинности клиентов с помощью раздела ключа API. Не забудьте добавить это в файл Gitignore, чтобы избежать фиксации этих секретов в системе управления версиями.
Реализация кроссплатформенного пользовательского интерфейса
В
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 реализуйте обработчики событий для register и Deregister button onPressed events. Вызовите соответствующие методы Register/Deregister, а затем показать оповещение, чтобы указать результат.
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(); }
Заметка
Это просто для демонстрации получения и распространения действий push-уведомлений. Как правило, они будут обрабатываться автоматически, например переход к определенному представлению или обновлению некоторых данных, а не отображение оповещения в этом случае.
Настройка собственного проекта Android для push-уведомлений
Добавление JSON-файла служб Google
Элемент управления щелкните в папкеandroid, а затем выберите Открыть в Android Studio . Затем перейдите в представление Project (если это еще не так).Найдите скачанный ранее файл
google-services.json при настройке проектаPushDemo вконсоли Firebase. Затем перетащите его в корневой каталог модуля ( 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>
Добавление пакетов SDK Firebase
В
Android Studio откройте файлbuild.gradle build.gradle ( le (Project: android)). и убедитесь, что в узлескрипты Gradle build.grad 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 // ... } }
Заметка
Убедитесь, что вы ссылаетесь на последнюю версию согласно инструкциям, приведенным в консоли Firebase
при создании проекта Android .В файле build.gradle уровня приложения (скрипты Gradle>build.gradle (модуль: приложение) примените подключаемый модуль 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.
Обработка push-уведомлений для Android
В
Android Studio элемент управления щелкните наcom. (your_organization папку пакета pushdemo src main kotlin ), выберитепакет в менюNew . Введите службы в качестве имени, а затем нажмите клавишу Return.Элемент управления щелкните в папке служб, выберите Kotlin File/Class в меню"Создать ". Введите 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
. Это было определено в части приложения Flutter в DeviceInstallationService.dart. В этом случае вызовы выполняются из общего кода к собственному узлу. Обязательно замените <your_organization> собственной организацией, где бы это ни было.Этот класс предоставляет уникальный идентификатор (используя Secure.AndroidId) в рамках полезных данных регистрации концентратора уведомлений.
Добавьте еще один Kotlin File/Class в папку служб с именем NotificationRegistrationService, а затем добавьте следующий код.
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
. Это было определено в части приложения Flutter в NotificationRegistrationService.dart. В этом случае вызовы выполняются из собственного узла в общий код. Опять же, необходимо заменить <your_organization> собственной организацией, где бы это ни было.Добавьте еще один Kotlin File/Class в папку служб с именем NotificationActionService, а затем добавьте следующий код.
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
. Это было определено в части приложения Flutter в NotificationActionService.dart. В этом случае вызовы можно выполнять в обоих направлениях. Обязательно замените <your_organization> собственной организацией, где бы это ни было.Добавьте новый Kotlin File/Class в com.<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)) } } } }
Заметка
Этот класс отвечает за обработку уведомлений при запуске приложения на переднем плане. При условном вызове триггера triggerAction на NotificationActionService, если действие включается в полезные данные уведомления, полученные в onMessageReceived. Это также вызовет refreshRegistration в NotificationRegistrationService при повторном создании маркера Firebase путем переопределения функции onNewToken.
Еще раз обратите внимание, чтобы заменить <your_organization> вашей собственной организацией, где бы она ни использовалась.
В AndroidManifest.xml (app>src>main), добавьте PushNotificationsFirebaseMessagingService в нижней части элемента приложения с фильтром намерений
com.google.firebase.MESSAGING_EVENT
.<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) }
Заметка
Так
как launchMode дляMainActivity задано значениеSingleTop , намерениебудет отправлено в существующий экземпляр действия через onNe Функция , а не функцияonCreate , поэтому необходимо обрабатывать входящиеНамерения как вonCreate , так ив функцияхNewIntent .Переопределите функцию onCreate, задайте deviceInstallationService на новый экземпляр DeviceInstallationService.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) } }
Задайте свойства notificationActionService и notificationRegistrationService для PushNotificationFirebaseMessagingServices.
flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) PushNotificationsFirebaseMessagingService.notificationActionService = NotificationActionService(it) PushNotificationsFirebaseMessagingService.notificationRegistrationService = NotificationRegistrationService(it) }
В той же функции условно вызовите FirebaseInstanceId.getInstance().instanceId. Реализуйте
OnCompleteListener, чтобы задать полученное значение маркераPushNotificationFirebaseMessagingService перед вызовомrefreshRegistration .if(deviceInstallationService?.playServicesAvailable) { FirebaseInstanceId.getInstance().instanceId .addOnCompleteListener(OnCompleteListener { task -> if (!task.isSuccessful) return@OnCompleteListener PushNotificationsFirebaseMessagingService.token = task.result?.token PushNotificationsFirebaseMessagingService.notificationRegistrationService?.refreshRegistration() }) }
По-прежнему в onCreateвызовите processNotificationActions в конце функции. Используйте
true для аргумента launchAction, чтобы указать, что это действие обрабатывается во время запуска приложения. processNotificationActions(this.intent, true)
Заметка
Необходимо повторно зарегистрировать приложение при каждом запуске и остановить его от сеанса отладки, чтобы продолжить получение push-уведомлений.
Настройка собственного проекта iOS для push-уведомлений
Настройка целевого объекта runner и Info.plist
В
Visual Studio Code элемент управления в папкещелкните ios , а затем выберитеОткрыть в Xcode .В Xcodeщелкните Runner (xcodeproj вверху, а не папку), а затем выберите целевой объект Runner, а затем подписывание возможностей&. Выбрав конфигурацию сборки "Все", выберите учетную запись разработчика для команды . Убедитесь, что флажок "Автоматическое управление подписыванием" установлен, а профиль подписывания и подготовки автоматически выбраны.
Заметка
Если вы не видите новое значение профиля подготовки, попробуйте обновить профили для удостоверения подписи, выбрав
Xcode Настройки учетной записи , а затем нажмите кнопку Скачать профили вручную , чтобы скачать профили.Щелкните
+ Возможности , а затем найдитеpush-уведомлений. дважды щелкните на push-уведомлений, чтобы добавить эту возможность. Откройте Info.plist и задайте минимальную версию системы 13.0.
Заметка
Для целей этого руководства поддерживаются только те устройства, которые работают iOS 13.0 и более поздних версий.
Откройте
Runner.entitlements и убедитесь, что параметр среды APS установленразработки.
Обработка push-уведомлений для iOS
Элемент управления Щелкните в папкеRunner (в проекте Runner), а затем выберитеСоздать группу с помощью служб. Элемент управления Щелкните в папке служб, а затем выберите Создать файл... . Затем выберитеSwift File и нажмите кнопкуДалее . Укажите 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
. Это было определено в части приложения Flutter в DeviceInstallationService.dart. В этом случае вызовы выполняются из общего кода к собственному узлу. Обязательно замените <your_organization> собственной организацией, где бы это ни было.Этот класс предоставляет уникальный идентификатор (используя значение UIDevice.identifierForVendor) в рамках полезных данных регистрации концентратора уведомлений.
Добавьте еще один Swift File в папку служб с именем NotificationRegistrationService, а затем добавьте следующий код.
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
. Это было определено в части приложения Flutter в NotificationRegistrationService.dart. В этом случае вызовы выполняются из собственного узла в общий код. Опять же, необходимо заменить <your_organization> собственной организацией, где бы это ни было.Добавьте еще один
Swift File в папку службс именем NotificationActionService , а затем добавьте следующий код.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
. Это было определено в части приложения Flutter в NotificationActionService.dart. В этом случае вызовы можно выполнять в обоих направлениях. Обязательно замените <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) } } }
Переопределите функцию
didRegisterForRemoteNotificationsWithDeviceToken , задав значение маркерадля notificationRegistrationServiceDeviceInstallationService . Затем вызовитеrefreshRegistration на. override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { deviceInstallationService?.token = deviceToken notificationRegistrationService?.refreshRegistration() }
Переопределите функцию didReceiveRemoteNotificationпередачи аргумента userInfo в функцию processNotificationActions.
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() } } } }
Если launchOptions содержит ключ remoteNotification, вызовите processNotificationActions в конце функции didFinishLaunchingWithOptions. Передайте полученный объект
userInfo и используйтеtrue для аргумента launchAction. Значение 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" }
Нажмите кнопку код, которая находится под кнопкой Сохранить в правом верхнем углу окна. Запрос должен выглядеть примерно так, как показано в следующем примере при отображении
HTML- (в зависимости от того, включен ли заголовок apikey). 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" }
Запустите приложение pushDemo
на одной или обеих целевых платформах ( Android иiOS ).Заметка
Если вы тестируете Android убедитесь, что вы не работаете в отладкеили если приложение было развернуто, запустив приложение, принудительно закройте приложение и запустите его снова с средства запуска.
В приложении PushDemo нажмите кнопку Зарегистрировать.
Вернитесь в Postman, закройте окно создания фрагментов кода (если это еще не сделано), а затем нажмите кнопку Отправить.
Убедитесь, что вы получите ответ
200 OK в , а оповещение отображается в приложении сPostman действие ActionA, полученное .Закройте приложение pushDemo
, а затем нажмите кнопку Отправить еще раз в .Postman Убедитесь, что вы получите ответ 200 OK в Postman еще раз. Убедитесь, что уведомление отображается в области уведомлений для приложения PushDemo с правильным сообщением.
Коснитесь уведомления, чтобы убедиться, что оно открывает приложение и отображает действие ActionA, полученное оповещение.
В Postmanизмените текст предыдущего запроса, чтобы отправить автоматическое уведомление, указывающее action_b вместо action_a для значения действия.
{ "action": "action_b", "silent": true }
При открытии приложения нажмите кнопку Отправить в Postman.
Убедитесь, что в
появитPostman ся ответ , полученное.ОК 200 ОК, а недействие ActionA Закройте приложение pushDemo
, а затем нажмите кнопку Отправить еще раз в .Postman Убедитесь, что вы получите ответ 200 ОК в Postman и что автоматическое уведомление не отображается в области уведомлений.
Устранение неполадок
Нет ответа от серверной службы
При локальном тестировании убедитесь, что серверная служба запущена и использует правильный порт.
Если тестирование на приложения API Azure, проверьте, запущена ли служба и была развернута и запущена без ошибок.
Убедитесь, что вы правильно указали базовый адрес в Postman или в конфигурации мобильного приложения при тестировании через клиент. Базовый адрес должен быть https://<api_name>.azurewebsites.net/
или https://localhost:5001/
при локальном тестировании.
Не получая уведомления в Android после запуска или остановки сеанса отладки
Убедитесь, что вы снова зарегистрируетесь после запуска или остановки сеанса отладки. Отладчик приведет к созданию нового маркера Firebase. Также необходимо обновить установку центра уведомлений.
Получение кода состояния 401 из серверной службы
Убедитесь, что вы задаете заголовок запроса 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/
при локальном тестировании.
Не удается зарегистрировать и отображается сообщение об ошибке центра уведомлений
Убедитесь, что тестовое устройство имеет сетевое подключение. Затем определите код состояния ответа Http, задав точку останова, чтобы проверить значение свойства StatusCode
Ознакомьтесь с предыдущими предложениями по устранению неполадок, применимыми в зависимости от кода состояния.
Задайте точку останова в строках, возвращающих эти определенные коды состояния для соответствующего API. Затем попробуйте вызвать серверную службу при локальной отладке.
Убедитесь, что серверная служба работает должным образом с помощью Postman с помощью соответствующей полезных данных. Используйте фактические полезные данные, созданные клиентским кодом для платформы.
Просмотрите разделы конфигурации для конкретной платформы, чтобы убедиться, что никаких шагов не было пропущено. Убедитесь, что подходящие значения разрешаются для installation id
и token
переменных для соответствующей платформы.
Не удается устранить идентификатор сообщения об ошибке устройства
Просмотрите разделы конфигурации для конкретной платформы, чтобы убедиться, что никаких шагов не было пропущено.
Связанные ссылки
- Обзор Центров уведомлений Azure
- установка Flutter в macOS
- установка Flutter в Windows
- пакет SDK Центров уведомлений для внутренних операций
- пакет SDK для центров уведомлений на сайте GitHub
- Регистрация с помощью серверной части приложения
- управления регистрацией
- Работа с тегами
- Работа с пользовательскими шаблонами
Дальнейшие действия
Теперь у вас должно быть базовое приложение Flutter, подключенное к центру уведомлений через серверную службу, и может отправлять и получать уведомления.
Скорее всего, вам потребуется адаптировать пример, используемый в этом руководстве, чтобы соответствовать собственному сценарию. Кроме того, рекомендуется реализовать более надежную обработку ошибок, логику повторных попыток и ведение журнала.
Центра приложений Visual Studio можно быстро включить в мобильные приложения, предоставляющие аналитику и диагностику для устранения неполадок.