Understanding Firebase Push Notifications in Android Kotlin with Clean Architecture
Hello, devs! I'm your App Sensei, and today we're talking about Firebase Push Notifications—a great way to connect with your users through their notification trays. We won't just stick to the basics. We'll learn how to add push notifications to our Android app using Kotlin, following Clean Architecture principles. This ensures your app is not only functional but also easy to maintain, scale, and test. So grab a cup of coffee, and let's start the exciting journey! ☕🚀
What Are Firebase Push Notifications?
Before we get our hands dirty with code, let’s take a moment to understand what Firebase Push Notifications are. Push notifications are messages sent by the server to a client application to inform users of new content, events, or updates, even when they’re not actively using the app. Firebase Cloud Messaging (FCM) is the service provided by Firebase that enables this functionality on Android, iOS, and web applications.
With FCM, you can send notifications from the Firebase console or trigger them programmatically via your server. The great thing about push notifications is that they can drive user engagement, boost retention, and help you communicate important updates or offers to your users.
Why Clean Architecture?
Clean Architecture, popularized by Robert C. Martin (Uncle Bob), is a way of structuring your codebase so that it remains clean, maintainable, and scalable. It separates the concerns of your application into different layers, making it easier to test, manage, and extend.
When implementing Firebase Push Notifications, following Clean Architecture principles ensures that your notification logic is decoupled from the Android framework, making it easier to test and modify. This separation also allows for a more modular and organized codebase, which is essential as your project grows.
Project Setup
Step 1: Setting Up Firebase in Your Android Project
Create a Firebase Project: To start integrating Firebase into your Android project, the first step is to set up a Firebase project. Head over to the Firebase Console. Once you're there, click on the "Add Project" button to get started. The setup wizard will walk you through the steps to configure your project. You'll need to enter a project name that fits your app. If you want to use Google Analytics, you can choose or create an account for it, which will give you helpful insights into how users interact with your app. After setting these options, the wizard will create your Firebase project. This might take a little time. Once it's done, you'll have access to a variety of Firebase services that you can use to make your Android app even better and more engaging for users.
Add Firebase to Your Android Project:
First, download the
google-services.json
file from the Firebase Console. This file has all the important settings for your Firebase project. Once you have it, put this file in theapp/
directory of your Android project. This step is key because it lets your app connect with Firebase services using the right settings.Next, you need to add the necessary Firebase dependencies to your project. Open your
build.gradle
file in the project-level directory and make sure the Google services plugin is included. Then, go to thebuild.gradle
file in the app-level directory. Here, you'll add the specific Firebase libraries your app needs. For example, if you want to use Firebase Cloud Messaging for push notifications, you should include thefirebase-messaging
dependency. Adding these dependencies lets your app use Firebase's amazing features, like real-time database, authentication, and analytics, which can really boost your app's functionality and user experience.
// Project-level build.gradle
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.3.15'
}
}
// App-level build.gradle
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation 'com.google.firebase:firebase-messaging-ktx:23.1.0'
}
- Enable Firebase Cloud Messaging: In the Firebase console, start by picking your project. Once you're on the project dashboard, look for "Cloud Messaging" in the menu on the left. Click on it to go to the Cloud Messaging page. Here, you'll see different options and settings for Firebase Cloud Messaging. To turn this feature on, make sure the Cloud Messaging service is activated. This means checking that the service is on and all the necessary settings are ready. Doing this lets your app send and receive push notifications, which can really boost user engagement by keeping them updated and connected with real-time alerts. Don't forget to check any extra settings that might be important for your app, like notification channels or message priority, to make the most of Firebase Cloud Messaging.
Step 2: Setting Up Clean Architecture
We’ll structure our project according to Clean Architecture by dividing it into the following layers:
Data Layer: The Data Layer handles everything related to data, like getting, storing, updating, and working with outside sources such as Firebase. It uses repositories and data sources to make managing data easier. This layer keeps data access and changes consistent, which helps maintain a clear separation of tasks. This makes it easier to maintain, test, and switch data sources or strategies if needed. Plus, it includes caching to boost performance, even when the network connection isn't great.
Domain Layer: The Domain Layer holds the heart of your app's business logic. It sets up use cases for specific tasks and connects the Data Layer with the Presentation Layer. This clear separation helps apply business rules consistently and makes testing easier by keeping the business logic separate. It also includes entities and value objects that represent important concepts and data structures.
Presentation Layer: The Presentation Layer manages the user interface and interactions, connecting users with the app's core functions. It includes views, view models, and controllers to ensure a smooth user experience. This layer presents data from the Domain Layer in a user-friendly way, updating the UI based on user actions and data changes. By separating UI logic from business logic, it simplifies modifying the app's appearance and behavior without impacting functionality, and it improves UI testing and maintenance.
Implementing Firebase Push Notifications
1. Data Layer
In this layer, we’ll set up the interaction with Firebase Cloud Messaging.
FirebaseService.kt: This service in
FirebaseService.kt
takes care of push notifications from Firebase Cloud Messaging. It decodes the message, figures out the notification type, and then does things like showing a notification, updating data, or starting app processes. By keeping everything in one place, it makes sure messages are handled consistently and efficiently, fitting in nicely with the app.class FirebaseService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) // Save or send the token to your server Log.d("FirebaseService", "New token: $token") } override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) // Handle the received message here remoteMessage.notification?.let { Log.d("FirebaseService", "Message Notification Body: ${it.body}") // Trigger the use case to show the notification } } }
NotificationRepository.kt: The
NotificationRepository.kt
links the app's main logic with theFirebaseService
. It handles notifications from Firebase Cloud Messaging, processing them according to their type and content. It fetches notification details and smoothly integrates them into the app's interface, making sure users receive updates promptly. It might also keep a record of notification history, update the app's state, or trigger actions to improve the user experience.interface NotificationRepository { fun saveToken(token: String) fun showNotification(title: String, message: String) } class NotificationRepositoryImpl( private val context: Context, private val notificationManager: NotificationManager ) : NotificationRepository { override fun saveToken(token: String) { // Save the token to a server or local storage Log.d("NotificationRepository", "Token saved: $token") } override fun showNotification(title: String, message: String) { // Build and display the notification val notification = NotificationCompat.Builder(context, "default_channel") .setContentTitle(title) .setContentText(message) .setSmallIcon(R.drawable.ic_notification) .build() notificationManager.notify(System.currentTimeMillis().toInt(), notification) } }
2. Domain Layer
This layer is where we define our business logic and use cases.
SaveTokenUseCase.kt: The
SaveTokenUseCase
takes care of saving the Firebase token, which is super important for push notifications in mobile apps. It makes sure the token is stored properly, whether it's on a remote server or locally. This smooth handling keeps the app and the notification service in sync, making sure users get their notifications right on time and enjoy a better experience.class SaveTokenUseCase(private val notificationRepository: NotificationRepository) { fun execute(token: String) { notificationRepository.saveToken(token) } }
ShowNotificationUseCase.kt: The
ShowNotificationUseCase
is all about making sure users see their notifications promptly and clearly. It collaborates with theNotificationRepository
to create and display notifications with the perfect title, message, and icon. By using the notification manager, it guarantees that each notification is uniquely identified and displayed properly, keeping users in the loop with important updates.class ShowNotificationUseCase(private val notificationRepository: NotificationRepository) { fun execute(title: String, message: String) { notificationRepository.showNotification(title, message) } }
3. Presentation Layer
This is the layer that interacts with the UI components and responds to user interactions.
NotificationViewModel.kt: The
NotificationViewModel.kt
is like the friendly helper that manages data for the user interface. It connects the UI components with the business logic, making sure everything runs smoothly. It takes care of user interactions, updates the UI, and keeps the data fresh. By working with use cases, it ensures the UI stays responsive and seamless. Plus, it looks after the UI data lifecycle, keeping data steady through changes like screen rotations, which boosts the app's reliability.class NotificationViewModel( private val saveTokenUseCase: SaveTokenUseCase, private val showNotificationUseCase: ShowNotificationUseCase ) : ViewModel() { fun saveToken(token: String) { saveTokenUseCase.execute(token) } fun showNotification(title: String, message: String) { showNotificationUseCase.execute(title, message) } }
MainActivity.kt: The
MainActivity.kt
is the central spot for user interactions. It keeps an eye on theNotificationViewModel
for any changes and reacts to user actions, like when you click a button. It sends notifications through theViewModel
, making sure you receive alerts right on time. This setup helps the app run smoothly and stay responsive by managing data flow and updating the UI instantly.class MainActivity : AppCompatActivity() { private lateinit var viewModel: NotificationViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel = ViewModelProvider(this).get(NotificationViewModel::class.java) // Simulate a push notification received viewModel.showNotification("Hello", "This is a push notification!") } }
Dependency Injection with Koin
To manage dependencies efficiently, we’ll use Koin for dependency injection.
AppModule.kt:
val appModule = module { // Provide NotificationManager single { val notificationManager = get<Context>().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager NotificationRepositoryImpl(get(), notificationManager) as NotificationRepository } // Use Cases single { SaveTokenUseCase(get()) } single { ShowNotificationUseCase(get()) } // ViewModel viewModel { NotificationViewModel(get(), get()) } }
MyApplication.kt:
class MyApplication : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApplication) modules(appModule) } } }
Customizing Notifications
Firebase provides a lot of flexibility in how you can customize notifications. You can include different types of data in your notification payloads to customize the notifications based on the content, user preferences, or app state.
For example, you might want to include an image in your notification:
val notification = NotificationCompat.Builder(context, "default_channel")
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(imageBitmap))
.build()
Or, you might want to
add an action button to the notification:
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(context, "default_channel")
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.ic_notification)
.addAction(R.drawable.ic_open, "Open App", pendingIntent)
.build()
And there you have it—a comprehensive guide to implementing Firebase Push Notifications in your Android app using Kotlin and Clean Architecture. By following these principles, you’re not only ensuring that your app’s codebase remains clean and maintainable but also setting yourself up for easier testing and future enhancements.
I hope this article has been helpful to you! Implementing push notifications can enhance user engagement in your app, and doing it with Clean Architecture keeps everything tidy and professional. If you have any questions or run into any issues, feel free to reach out. See you on the next blog.
Connect with Me:
Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? 😊 You can also follow my Instagram page for more mobile development-related content. 📲👨💻 Let’s stay connected, share knowledge and have some fun in the exciting world of app development! 🌟