ktor-native-worker-tutorial

Part 2: Sending Notifications

In this part, we’ll explore how to send Firebase Cloud Messaging (FCM) notifications using the Flareon library in a Kotlin Multiplatform environment.

Overview

The notification system in this project follows the Service pattern with a clean interface-implementation structure:

This design follows clean code principles by separating the interface from the implementation, making it easy to test and swap implementations if needed.

Notification Service Interface

File: src/commonMain/kotlin/me/nathanfallet/ktornativeworkertutorial/services/NotificationService.kt

interface NotificationService {
    suspend fun sendNotification(token: String, title: String, body: String)
}

This simple interface defines a single suspend function for sending notifications. It accepts:

Notification Service Implementation

File: src/commonMain/kotlin/me/nathanfallet/ktornativeworkertutorial/services/NotificationServiceImpl.kt

class NotificationServiceImpl(
    serviceAccountPath: String,
) : NotificationService {

    private val serviceAccountJson = readFile(serviceAccountPath)
    private val credentials = GoogleCredentials.fromJson(serviceAccountJson)
    private val messaging = FcmService(credentials)

    override suspend fun sendNotification(token: String, title: String, body: String) {
        messaging.sendNotification(token, title, body)
    }

}

How It Works

  1. Service Account Loading:
    • The constructor accepts a serviceAccountPath parameter
    • Uses the platform-specific readFile() function to load the JSON file
    • Creates GoogleCredentials from the JSON content
  2. FCM Service Initialization:
    • Creates an FcmService instance using the credentials
    • This service is reused for all notification sending operations
  3. Sending Notifications:
    • The sendNotification() method delegates to the Flareon library’s FcmService
    • The method is marked as suspend, enabling coroutine-based async operations

Using the Flareon Library

The project uses the Flareon library (me.nathanfallet.flareon:messaging), which provides:

Dependency Configuration

In build.gradle.kts, Flareon is included in the common dependencies:

sourceSets {
    commonMain.dependencies {
        implementation(libs.flareon.messaging)
        // ... other dependencies
    }
}

And in gradle/libs.versions.toml:

[versions]
flareon = "0.1.1"

[libraries]
flareon-messaging = { module = "me.nathanfallet.flareon:messaging", version.ref = "flareon" }

Firebase Service Account Setup

To use FCM, you need a Firebase service account JSON file:

  1. Go to the Firebase Console
  2. Select your project
  3. Navigate to Project Settings > Service Accounts
  4. Click “Generate New Private Key”
  5. Save the JSON file (commonly named firebase-admin-sdk.json)
  6. Place it in your project root or specify its path via environment variable

The service account JSON contains:

Platform-Specific File Reading

Notice how the implementation uses readFile(), which is an expect function with platform-specific implementations:

This demonstrates how Kotlin Multiplatform allows you to write common business logic while using platform-specific APIs when necessary.

Error Handling Considerations

The current implementation is straightforward and doesn’t include explicit error handling. In a production environment, you might want to add:

However, this tutorial focuses on the clean, minimal implementation to demonstrate the core concepts.

Integration with Dependency Injection

The NotificationService is registered in the Koin dependency injection container (covered in Part 5), making it available throughout the application:

single<NotificationService> {
    NotificationServiceImpl(
        serviceAccountPath = getEnv("SERVICE_ACCOUNT_PATH") ?: "firebase-admin-sdk.json",
    )
}

This allows the service account path to be configured via environment variable, with a sensible default fallback.

Summary

The notification service demonstrates:

In the next part, we’ll explore how to set up AMQP message brokering with RabbitMQ to enable asynchronous notification processing.