line / abc-kmm-notifications

Remote Notification Manager for Kotlin Multiplatform Mobile iOS and Android
Apache License 2.0
60 stars 3 forks source link

abc-kmm-notifications: Remote Notification Manager

Kotlin KMM AGP Gradle Platform

Remote Notification Manager for Kotlin Multiplatform Mobile

Features

Requirements

Installation

Gradle Settings

Add below gradle settings into your KMP (Kotlin Multiplatform Project)

build.gradle.kts in root

buildscript {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:7.0.1")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21")
        classpath("org.jetbrains.kotlin:kotlin-serialization:1.5.21")
        classpath("com.google.gms:google-services:4.3.5")
    }
}

build.gradle.kts in shared

plugins {
    id("com.android.library")
    kotlin("multiplatform")
    kotlin("plugin.serialization")
}

val abcNotifications = "com.linecorp.abc:kmm-notifications:0.4.1"
val kotlinxSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"

kotlin {
    sourceSets {
        ios {
            binaries
                .filterIsInstance<Framework>()
                .forEach {
                    it.transitiveExport = true
                    it.export(abcNotifications)
                }
        }
        android()

        val commonMain by getting {
            dependencies {
                implementation(abcNotifications)
                implementation(kotlinxSerialization)
            }
        }
        val androidMain by getting {
            dependencies {
                implementation(abcNotifications)
                api(abcNotifications)
            }
        }
        val iosMain by getting {
            dependencies {
                implementation(abcNotifications)
                api(abcNotifications)
            }
        }
    }
}

build.gradle.kts in androidApp

plugins {
    id("com.google.gms.google-services")
}

Usage

Definition of payload and map function with generic in commonMain of your project

@Serializable
data class Data(
  val notificationType: String = "",
  val id: Int = 0,
)

@Throws(Throwable::class)
fun NotificationElement.payload() = decodedPayload<Data>()

Android

ABCNotifications
    .onNewToken(this) {
        // TODO: send to register ${ABCDeviceToken.value} to server
    }
    .onDeletedMessages(this) {
        // TODO: sync messages to server
    }
    .onMessageReceived(this) {
        // FCM RemoteMessage
        val remoteMessage = it.remoteMessage

        // decode to Payload with Data
        val payload = it.payload()

        // decode to Data
        val data = it.decodedData<Data>()

        // TODO: present a dialog for push notification
    }
    .beginListening()

iOS

ABCNotifications.Companion()
    .registerSettings {
        $0.add(type: .alert)
        $0.add(type: .badge)
        $0.add(type: .sound)
    }
    .onNewToken(target: self) {
        // TODO: send to register ${ABCDeviceToken.rawValue} to server
    }
    .onMessageReceived(target: self) {
        guard let payload = try? $0.payload() else { return }

        if $0.isInactive {
            // TODO: present a view controller on inactive
        } else {
            // TODO: present a toast message on active
        }
    }.beginListening()

Required implementation in AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        ABCNotificationCenterDelegate.Companion().applicationDidFinishLaunching(options: options)
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        ABCNotificationCenterDelegate.Companion().applicationDidRegisterForRemoteNotifications(deviceToken: deviceToken)
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        ABCNotificationCenterDelegate.Companion().userNotificationCenterWillPresent(notification: notification)
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        ABCNotificationCenterDelegate.Companion().userNotificationCenterDidReceive(response: response)
    }
}

FCM on iOS

  1. Insert the dependency in shared.podspec

    spec.dependency 'FirebaseMessaging'
  2. Insert function into cocoapods definition in build.gradle.kts (shared)

    cocoapods {
        noPodspec()
    }
  3. Initializing

    ABCNotifications.Companion()
        .allowsFCMOnIOS()
        .registerSettings {
            $0.add(type: .alert)
            $0.add(type: .badge)
            $0.add(type: .sound)
        }
        .onNewToken(target: self) {
            print("onNewToken -> ", ABCDeviceToken.Companion().FCMToken)
            // TODO: send to register ${ABCDeviceToken.FCMToken} to server
        }.beginListening()

Advanced

Shared configuration in commonMain


fun ABCNotifications.Companion.configure(block: ABCNotifications.Companion.() -> Unit) {
    apply(block)

    onNewToken(this) {
        // TODO: send to register ${ABCDeviceToken.value} to server
    }.beginListening()
}

Android


ABCNotifications.configure {
    onDeletedMessages(this) {
        // TODO: sync messages to server
    }
    onMessageReceived(this) {
        // FCM RemoteMessage
        val remoteMessage = it.remoteMessage

        // decode to Payload with Data
        val payload = it.payload()

        // decode to Data
        val data = it.decodedData<Data>()

        // TODO: present a dialog for push notification
    }
}

iOS

ABCNotifications.Companion().configure { [unowned self] in
    $0.registerSettings {
        $0.add(type: .alert)
        $0.add(type: .badge)
        $0.add(type: .sound)
    }
    $0.onMessageReceived(target: self) {
        guard let payload = try? $0.payload() else { return }

        if $0.isInactive {
            // TODO: present a view controller on inactive
        } else {
            // TODO: present a toast message on active
        }
    }
}