DEV Community

Nathan Fallet
Nathan Fallet

Posted on

Part 2: Sending Notifications - Ktor Native Worker Tutorial

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:

  • NotificationService: Interface defining the contract
  • NotificationServiceImpl: Implementation using Flareon library

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)
}
Enter fullscreen mode Exit fullscreen mode

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

  • token: The FCM device token
  • title: The notification title
  • body: The notification body/message

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)
    }

}
Enter fullscreen mode Exit fullscreen mode

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 (
digital.guimauve.flareon:messaging), which provides:

  • Kotlin Multiplatform support for FCM
  • Native compatibility (works on JVM and Native targets)
  • Simple API for sending notifications
  • Google service account authentication

Dependency Configuration

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

sourceSets {
    commonMain.dependencies {
        implementation(libs.flareon.messaging)
        // ... other dependencies
    }
}
Enter fullscreen mode Exit fullscreen mode

And in gradle/libs.versions.toml:

[versions]
flareon = "0.1.1"

[libraries]
flareon-messaging = { module = "digital.guimauve.flareon:messaging", version.ref = "flareon" }
Enter fullscreen mode Exit fullscreen mode

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:

  • Project ID
  • Private key for authentication
  • Client email
  • Other Firebase project metadata

Platform-Specific File Reading

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

  • JVM: Uses java.io.File.readText()
  • Native: Uses POSIX file operations (fopen, fread, fclose)

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:

  • Error handling for missing or invalid service account files
  • Retry logic for failed notification sends
  • Logging for debugging
  • Validation of notification parameters

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",
    )
}
Enter fullscreen mode Exit fullscreen mode

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

Summary

The notification service demonstrates:

  • Clean separation of interface and implementation
  • Use of Kotlin Multiplatform libraries (Flareon)
  • Platform-specific code integration (file reading)
  • Coroutine-based async operations
  • Dependency injection compatibility

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

Top comments (0)