cxhub_sdk 0.0.1 copy "cxhub_sdk: ^0.0.1" to clipboard
cxhub_sdk: ^0.0.1 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.1
#dependency_overrides:
#  cxhub_android: 0.0.1-huawei
#  cxhub_android: 0.0.1-rustore
# ...

Для использования дефолтной реализации пуш-уведомлений для Android через Firebase не требуется переопределения зависимостей.

Для использования 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.1
#dependency_overrides:
#  cxhub_android: 0.0.1-huawei
#  cxhub_android: 0.0.1-rustore
# ...
  cxhub_ios: ^0.0.1

В платформенной части (.../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();

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