cxhub_sdk 0.0.2 copy "cxhub_sdk: ^0.0.2" to clipboard
cxhub_sdk: ^0.0.2 copied to clipboard

CxHub SDK library

CxHubSdk #

Обертка для нативных SDK CxHub для использования с Flutter.

Важный момент: оригинальные нативные SDK CxHub предназначены для использования с аккаунтами Firebase/Huawei/Rustore и APNs клиента, само пуш-уведомление приходит на приложение клиента, после чего обрабатывается при помощи SDK CxHub, поэтому внедрение включает в себя модификацию нативных частей Flutter-приложения.

Интеграция в приложение #

Зависимости #

Добавьте следующий код в pubspec.yaml вашего проекта:

dependencies:
  # ...
  cxhub_sdk: 0.0.2
  
dependency_overrides:
  cxhub_android: 0.0.2-firebase
#  cxhub_android: 0.0.2-huawei
#  cxhub_android: 0.0.2-rustore
# ...

Для использования Huawei или Rustore уведомлений необходимо раскомментировать нужный пункт и закомментировать ненужный. То есть переход на нужную имплементацию для Android реализован как переопределение версии Android-модуля сдк.

Для iOS используется только APNs.

Android #

В android/app/src (исходный код Android-части вашего приложения) необходимо добавить json-ключ google-services.json, или agconnect-services.json в зависимости от того какие именно пуш-уведомления вы подключаете. Для использования rustore-имплементации такого файла не нужно.

В android/app/src/main/res/values необходимо добавить файл cxhub.xml следующего вида:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="cxhub_resource_icon_id">[drawable-ресурс иконки сообщения]</string>
    <string name="cxhub_integration_id" translatable="false">[id итеграции из личного кабинета cxhub]</string>
    <string name="cxhub_application_secret" translatable="false">[секретный ключ интеграции из личного кабинета]</string>
    <string name="cxhub_api_host" translatable="false">[путь к вашему проекту cxhub, например https://mytest.cxhub.ru]/callback-service/</string>
    <bool name="cxhub_trust_all_certificates">true</bool>
</resources>

В android/build.gradle необходимо добавить следующие строки:

buildscript{
  repositories {
    google() 
    mavenCentral()
    maven{
      url "https://developer.huawei.com/repo/" // для huawei
    }

    maven{
       url "https://artifactory-external.vkpartner.ru/artifactory/maven" // для rustore
    }
    //...
  }

  dependencies {
    classpath("com.google.gms:google-services:4.4.2") // для firebase
    classpath("com.huawei.agconnect:agcp:1.9.1.302") // для huawei
  }
}

//...

allprojects {
    repositories {
        google()
        mavenCentral()
        maven{
            url "https://developer.huawei.com/repo/" // для huawei
        }
        maven {
            url "https://artifactory-external.vkpartner.ru/artifactory/maven" // для rustore
        }
        //...
    }
}

Репозитории помеченные комментариями добавляются только в случае использования указанного в них транспорта.

В android/app/build.gradle необходимо добавить следующие строки:

plugins {
    //...
    id "com.google.gms.google-services" // если используется firebase
    id "com.huawei.agconnect" // если используется huawei
    //...
}

Плагины помеченные комментариями добавляются только в случае использования указанного в них транспорта.

При сборке с плагином CxHubSdk в merged манифест Android-приложения добавляются следующие разрешения (руками добавлять не нужно):

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

iOS #

Для интеграции CxHubSdk в iOS-часть приложения, необходимо произвести следующие действия

В общей части проекта:

  • установить\добавить во Flutter-проект плагин cxhub_sdk
  • проверить, что в pubspec.yaml вашего проекта есть следуюший код:
dependencies:
  # ...
  cxhub_sdk: 0.0.2
  
dependency_overrides:
  cxhub_android: 0.0.2-firebase
#  cxhub_android: 0.0.2-huawei
#  cxhub_android: 0.0.2-rustore
# ...
  cxhub_ios: ^0.0.2

В платформенной части (.../ios), открыв workspace с помощью XCode:

  • изменить\модифицировать настройки (вкладка "Signing & Capabilities") основного таргета: Добавить следующие "Capabilities":

    • App Groups (идентификатор общей группы должен соответствовать вашему приложению, он будет использован ниже и для extensions)
    • Communication Notifications
    • Push Notifications

    Далее:

    • Добавить "Capabilities"

    Добавить "Capabilities"

    • добавить 2 модуля-extension(s): NotificationService(Extension), ContentExtension

    Добавляем и конфигурируем NotificationService extension (добавляем новый таргет):

    • Добавить "NotificationService (extension)" Добавить "NotificationService (extension)"

    • Задать имя "ServiceExtension" Задать имя "ServiceExtension"

    • Активировать Активировать

    • Добавить "Capabilities" для NotificationService Добавить "Capabilities"

    Добавляем и конфигурируем ContentExtension (добавляем новый таргет):

    • Добавить "ContentExtension (extension)" Добавить "ContentExtension (extension)"

    • Задать имя "ContentExtension" Задать имя "ContentExtension"

    • Активировать Активировать

    • Добавить "Capabilities" для СontentExtension Добавить "Capabilities"

    Во всех модулях проекта и таргетов устанавливаем минимальную версию iOS >= 15.0 (это требование cxhub_sdk (ioS), которая использует iOS 15+):

    • Workspace deployment target

Workspace deployment target

  • Service Extension minimum deployment

Service Extension minimum deployment

  • Content Extension minimum deployment

Content Extension minimum deployment

Далее переходим на основной таргет приложения, вкладка "Build Phases" и меняем последовательность фаз так, чтобы "Thin Binary" оказалась самой нижней фазой

  • Результат: arrange_build_phases_result

Далее:

  • Добавляем в проект файл Notify.plist следующего вида:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Enabled</key>
	<true/>
	<key>Debug</key>
	<true/>
	<key>LibNotify</key>
	<dict>
		<key>UNNotificationExtensionCategory</key>
		<array>
			<string>libnotify_default</string>
			<string>libnotify_button_queue_1</string>
			<string>libnotify_button_queue_2</string>
			<string>libnotify_button_queue_3</string>
			<string>libnotify_button_queue_4</string>
			<string>libnotify_button_queue_5</string>
		</array>
		<key>Activity</key>
		<dict>
			<key>Colors</key>
			<dict>
				<key>BackgroundColor</key>
				<dict>
					<key>Dark</key>
					<string>#030303</string>
					<key>Light</key>
					<string>#DDDDDD</string>
				</dict>
				<key>TextColor</key>
				<dict>
					<key>Dark</key>
					<string>#DDDDDD</string>
					<key>Light</key>
					<string>#030303</string>
				</dict>
				<key>AccentColor</key>
				<dict>
					<key>Dark</key>
					<string>#219653</string>
					<key>Light</key>
					<string>#219653</string>
				</dict>
				<key>ButtonTextColor</key>
				<dict>
					<key>Dark</key>
					<string>#70D098</string>
					<key>Light</key>
					<string>#70D098</string>
				</dict>
				<key>CloseButtonColor</key>
				<dict>
					<key>Dark</key>
					<string>#6FCF97</string>
					<key>Light</key>
					<string>#6FCF97</string>
				</dict>
				<key>DarkModeSupported</key>
				<true/>
			</dict>
			<key>FontType</key>
			<string>Custom</string>
		</dict>
		<key>Enabled</key>
		<true/>
		<key>Application</key>
		<dict>
			<key>ApiUrlHost</key>
			<string>*YOUR_CXHUB_PROJECT_URL*</string>
			<key>IntegrationId</key>
			<string>*YOUR_CXHUB_INTEGRATION_ID*</string>
			<key>Secret</key>
			<string>*YOUR_CXHUB_INTEGRATION_SECRET*</string>
		</dict>
	</dict>
	<key>SharedGroupId</key>
	<string>*YOUR_APPLE_SHARED_GROUP_ID*</string>
</dict>
</plist>
  • модифицируем (заполняем своими параметрами) Root -> LibNotify -> Application:

  • ApiUrlHost: <базовый URL проекта в CxHub>/callback-service/ (пример: https://vgktest.cxhub.ru/callback-service/ )

  • IntegrationId: идентификатор интеграции в CxHub (получаем из настроек интеграции в Web интерфейсе личного кабинета CxHub)

  • Secret: секрет интеграции в CxHub (получаем из настроек интеграции в Web интерфейсе личного кабинета CxHub)

  • модифицируем (заполняем своими параметрами) Root -> SharedGroupId : ваш идентификатор shared_group для приложения

Важно: параметр Root -> Debug по умолчанию установлен True, в релизной сборке приложения его необходимо установить False

Остальные параметры оставляем без изменений.

Файл Notify.plist описывает основные настройки для SDK, поэтому в "Target Membership" у него обязательно должен быть включены "галочки" для всех таргетов приложения (основной, ServiceExtension, ContentExtension)

notify_plist_target_membership

Далее модифицируем Appdelegate.swift (добавляем необходимые для работы SDK вызовы):


import Flutter
import UIKit
import cxhub_ios

@main
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        CxhubSdkPlugin.instance.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
        return super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }
    
    override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        CxhubSdkPlugin.instance.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
        return super.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
    }
}

Добавляем\модифицируем NotificationService.swift:

import UserNotifications
import cxhub_ios
import CXHubCore
import CXHubNotify

class NotificationService: UNNotificationServiceExtension {
    
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?
    private var apiIsInitialized :  Bool = false
    
    override init() {
        apiIsInitialized = CxhubSdkPlugin.initCXHubSDK()
    }
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
            if apiIsInitialized {
                if CxhubSdkPlugin.didReceive(request, withContentHandler: contentHandler) {
                    return
                }
                else {
                    contentHandler(bestAttemptContent)
                }
            }
            
            else {
                contentHandler(bestAttemptContent)
            }
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            if apiIsInitialized {
                if CxhubSdkPlugin.serviceExtensionTimeWillExpire() {
                    return
                }
                else {
                    contentHandler(bestAttemptContent)
                }
            }
            
            else {
                contentHandler(bestAttemptContent)
            }
        }
    }
    
}

Модифицируем NotificationViewController.swift (модуль\папка ContentExtension):


import UIKit
import UserNotifications
import UserNotificationsUI
import cxhub_ios
import CXHubNotify

class NotificationViewController: UIViewController, UNNotificationContentExtension {
    
    private var apiIsInitialized :  Bool = false

    @IBOutlet var bigContentImage: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        apiIsInitialized = CxhubSdkPlugin.initCXHubSdkWithContentExtensionImage(bigImage: self.bigContentImage)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any required interface initialization here.
    }
    
    func didReceive(_ notification: UNNotification) {
        self.label?.text = notification.request.content.body
    }
    
    func didReceive(_ notification: UNNotification) {
        //This variant is for CxhubSdkPlugin as CXContentExtensionDelegate
        let processed = apiIsInitialized && CxhubSdkPlugin.instance.didReceive(notification)
        
        if (!processed) {
            //Do some custom logic with a particular notification as it is not originated from CXHubSDK API.
        }
        
    }
    
    func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
        if apiIsInitialized {
            if !CxhubSdkPlugin.instance.didReceive(response, context: self.extensionContext, completionHandler: completion) {
                //Catch action with UNNotificationContentExtensionResponseOption yourself
            }
        }
        else {
            //Catch action with UNNotificationContentExtensionResponseOption yourself, cause CXHubSDK wasn't initialized correctly
        }
    }
    
}

Файл Info.plist для ContentExtension модифицируем следующим образом:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionAttributes</key>
		<dict>
			<key>UNNotificationExtensionCategory</key>
			<array>
				<string>libnotify_default</string>
				<string>libnotify_button</string>
				<string>libnotify_button_queue_1</string>
				<string>libnotify_button_queue_2</string>
				<string>libnotify_button_queue_3</string>
				<string>libnotify_button_queue_4</string>
				<string>libnotify_button_queue_5</string>
			</array>
			<key>UNNotificationExtensionInitialContentSizeRatio</key>
			<integer>0</integer>
		</dict>
		<key>NSExtensionMainStoryboard</key>
		<string>MainInterface</string>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.usernotifications.content-extension</string>
	</dict>
</dict>
</plist>


Кроме этого в ContentExtension добавляем UIImageView на View для NotificationViewController в MainInterface.storyboard, устанавливаем для него необходимые constraints, и связываем его со свойством bigContentImage:

пример:

сontent_extension_storyboard

Закрываем workspace и редактируем Podfile:


# Uncomment this line to define a global platform for your project
platform :ios, '15.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  target 'RunnerTests' do
    inherit! :search_paths
  end
end

target 'ServiceExtension' do
  use_frameworks!
  use_modular_headers!
  flutter_install_ios_engine_pod File.dirname(File.realpath(__FILE__))
  pod 'cxhub_ios', path: '.symlinks/plugins/cxhub_ios/ios'
end

target 'ContentExtension' do
  use_frameworks!
  use_modular_headers!
  flutter_install_ios_engine_pod File.dirname(File.realpath(__FILE__))
  pod 'cxhub_ios', path: '.symlinks/plugins/cxhub_ios/ios'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
  end
end

Инициализация #

Для инициализации сдк с использованием Firebase или Huawei добавьте следующий код в функцию main:

CxHubSdk.init();

В случае использования Rustore:

CxHubSdk.init("[yourRustoreProjectId]");

Для минимального функционала (прием пушей) этого достаточно.

API #

Апи представлено одним статическим интерфейсом "CxHubSdk".

Методы: #

static void init({String? param});

Инициализация. Может принимать идентификатор проекта RuStore, если используются пуши RuStore.

static Future<String?> getMobileInstance();

Геттер мобильного инстанса. Возвращает сгенерированный SDK мобильный идентификатор клиента.

static Future<String?> getPushToken();

Геттер пуш-токена. Возвращает пуш-токен, выданный системой доставки пуш-уведомлений.

static Stream<String?> subscribeToPushToken();

Подписка на пуш-токен. В системах доставки пуш-уведомлений случается обновление токена. При этом в возвращаемый поток эмитится новое значение. При подписке эмитится текущее значение токена.

static Future<MapEntry<String, String>?> getUserId();

Геттер идентификатора юзера. Идентификатор юзера - пользовательская настройка. Опциональна. Пользователь может быть идентифицирован одним из уникальных значений предоставляемых им личных данных, по совместному решению разработчика приложения и оператора ЛК CxHub. Например, это почта или телефон. Ключом в возвращаемом MapEntry является тип этого значения, например Phone или Email. Другие типы пользовательских параметров можно посмотреть, а так же добавить в личном кабинете CxHUb в разделе "пользовательские параметры". В значении MapEntry содержится значение параметра. Если ранее не было задано в приложении - возвратит null.

static Future setUserId(String userIdType, String userIdValue)

Сеттер идентификатора юзера. Здесь userIdType - ключ из MapEntry предыдущего метода, а userIdValue - значение параметра.

static Future setUserProperties(Map<String, String> properties);

Сеттер прочих пользовательских параметров. Например FirstName, SecondName, MiddleName, Address... Полный список может быть наден и модифицирвоан в ЛК CxHub в разделе "пользовательские параметры".

static Future collectEvent(String key,{String? value,Map<String, String>? properties,bool deliverImmediately = false});

Отправить событие. SDK автоматически отправляет события связанные с пуш-уведомлениями, которые оно обрабатывает. Разработчик приложения может дополнительно отправлять события, отображаемые в ЛК CxHub типом "AppCustom". Здесь:

  • key - название события
  • value - необязательное единичное значение события
  • properties - необязательный набор дополнительных значений с указанием их типов/названий
  • deliverImmediately - необходимость срочной доставки (если false) то будет отправлено не сразу, а с остальными событиями по расписанию
enum PostNotificationPermission {
  unknown,
  denied,
  granted,
}

Enum для состояния разрешения показа нотификаций. Значения:

  • unknown - может быть запрошено
  • denied - запрещено, неизменяемый статус, разрешение может быть выдано только путем изменения разрешений самим пользователем в настройках ОС
  • granted - предоставлено
static Future<PostNotificationPermission> checkPermission();

Проверить текущий статус разрешения на показ пуш-уведомлений

static Future<PostNotificationPermission> requestPermission();

Запросить разрешение на показ пуш-уведомлений