STEPS
Create a Notification
NotificationUtils, EggTimerViewModel, EggTimerFragment
A Basic Notification
-
Implement an extension fun sendN in NotificationManager - to send Ns w/o rewriting the code
- Create an extension fun to send messages
- Create the content intent for the notification to launch this activity
- Create PendingIntent
- Set content intent
- Cancel all notifications
- Get an instance of NotificationCompat.Builder to build the notification
- Set title, text and icon to the Builder
- Call notify
- Use the new notification channel
- Add style
- Add style to the Builder
- Add snooze action
- Set priority
// NotificationUtils.kt package com.example.android.eggtimernotifications.util import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import com.example.android.eggtimernotifications.MainActivity import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.receiver.SnoozeReceiver // Notification ID. private val NOTIFICATION_ID = 0 private val REQUEST_CODE = 0 private val FLAGS = 0 // Step 1.1 extension function to send messages (GIVEN) /** * Builds and delivers the notification. * * @param context, activity context. */ fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { // Create the content intent for the notification, which launches // this activity // TODO: Step 1.11 create intent // TODO: Step 1.12 create PendingIntent // TODO: Step 2.0 add style // TODO: Step 2.2 add snooze action // TODO: Step 1.2 get an instance of NotificationCompat.Builder // Build the notification val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) // TODO: Step 1.8 use the new 'breakfast' notification channel // TODO: Step 1.3 set title, text and icon to builder .setSmallIcon(R.drawable.cooked_egg) .setContentTitle(applicationContext .getString(R.string.notification_title)) .setContentText(messageBody) // TODO: Step 1.13 set content intent // TODO: Step 2.1 add style to builder // TODO: Step 2.3 add snooze action // TODO: Step 2.5 set priority // TODO: Step 1.4 call notify notify(NOTIFICATION_ID, builder.build()) } // TODO: Step 1.14 Cancel all notifications
-
Get an instance of NotificationManager and call sendNotification
EggTimerViewModel
package com.example.android.eggtimernotifications.ui import android.app.* import android.content.Context import android.content.Intent import android.os.CountDownTimer import android.os.SystemClock import androidx.core.app.AlarmManagerCompat import androidx.core.content.ContextCompat import androidx.lifecycle.* import com.example.android.eggtimernotifications.receiver.AlarmReceiver import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.util.sendNotification import kotlinx.coroutines.* class EggTimerViewModel(private val app: Application) : AndroidViewModel(app) { private val REQUEST_CODE = 0 private val TRIGGER_TIME = "TRIGGER_AT" private val minute: Long = 60_000L private val second: Long = 1_000L private val timerLengthOptions: IntArray private val notifyPendingIntent: PendingIntent private val alarmManager = app.getSystemService(Context.ALARM_SERVICE) as AlarmManager private var prefs = app.getSharedPreferences( "com.example.android.eggtimernotifications", Context.MODE_PRIVATE ) private val notifyIntent = Intent(app, AlarmReceiver::class.java) private val _timeSelection = MutableLiveData<Int>() val timeSelection: LiveData<Int> get() = _timeSelection private val _elapsedTime = MutableLiveData<Long>() val elapsedTime: LiveData<Long> get() = _elapsedTime private var _alarmOn = MutableLiveData<Boolean>() val isAlarmOn: LiveData<Boolean> get() = _alarmOn private lateinit var timer: CountDownTimer init { _alarmOn.value = PendingIntent.getBroadcast( getApplication(), REQUEST_CODE, notifyIntent, PendingIntent.FLAG_NO_CREATE ) != null notifyPendingIntent = PendingIntent.getBroadcast( getApplication(), REQUEST_CODE, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ) timerLengthOptions = app.resources.getIntArray(R.array.minutes_array) //If alarm is not null, resume the timer back for this alarm if (_alarmOn.value!!) { createTimer() } } /** * Turns on or off the alarm * * @param isChecked, alarm status to be set. */ fun setAlarm(isChecked: Boolean) { when (isChecked) { true -> timeSelection.value?.let { startTimer(it) } false -> cancelNotification() } } /** * Sets the desired interval for the alarm * * @param timerLengthSelection, interval timerLengthSelection value. */ fun setTimeSelected(timerLengthSelection: Int) { _timeSelection.value = timerLengthSelection } /** * Creates a new alarm, notification and timer */ private fun startTimer(timerLengthSelection: Int) { _alarmOn.value?.let { if (!it) { _alarmOn.value = true val selectedInterval = when (timerLengthSelection) { 0 -> second * 10 //For testing only else ->timerLengthOptions[timerLengthSelection] * minute } val triggerTime = SystemClock.elapsedRealtime() + selectedInterval // Step 1.5 get an instance of NotificationManager and call sendNotification val notificationManager = ContextCompat.getSystemService( app, NotificationManager::class.java ) as NotificationManager notificationManager.sendNotification(app.getString(R.string.timer_running), app) // Step 1.15 call cancel notification AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, notifyPendingIntent ) viewModelScope.launch { saveTime(triggerTime) } } } createTimer() } /** * Creates a new timer */ private fun createTimer() { viewModelScope.launch { val triggerTime = loadTime() timer = object : CountDownTimer(triggerTime, second) { override fun onTick(millisUntilFinished: Long) { _elapsedTime.value = triggerTime - SystemClock.elapsedRealtime() if (_elapsedTime.value!! <= 0) { resetTimer() } } override fun onFinish() { resetTimer() } } timer.start() } } /** * Cancels the alarm, notification and resets the timer */ private fun cancelNotification() { resetTimer() alarmManager.cancel(notifyPendingIntent) } /** * Resets the timer on screen and sets alarm value false */ private fun resetTimer() { timer.cancel() _elapsedTime.value = 0 _alarmOn.value = false } private suspend fun saveTime(triggerTime: Long) = withContext(Dispatchers.IO) { prefs.edit().putLong(TRIGGER_TIME, triggerTime).apply() } private suspend fun loadTime(): Long = withContext(Dispatchers.IO) { prefs.getLong(TRIGGER_TIME, 0) } }
Notification Channels
-
Create an extension fun createChannel (& call it in onCreate)
- Create a NotificationChannel instance - pass id, name & importance
- On the NotificationChannel obj, set the props - enableLights, lightColor, enableVibration, channel description
- Get an instance of NotificationManager by calling getSystemService
- Call createNotificationChannel on NotificationManager and pass the NotificationChannel obj.
- To create the channel, call this createChannel ext. fun in the onCreate fun.
- NotificationUtils β build: verify the channel id you previously set is correct
- Pull the status bar and observe the notification title, content and icon are just like you set in the previous steps.
- To verify the newly created channel, close the app and find the app icon. Perform a long click on the app icon and select app info.
// EggTimerFragment.kt package com.example.android.eggtimernotifications.ui import android.app.NotificationChannel import android.app.NotificationManager import android.graphics.Color import android.os.Build import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.databinding.FragmentEggTimerBinding import com.google.firebase.messaging.FirebaseMessaging class EggTimerFragment : Fragment() { private val TOPIC = "breakfast" override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding: FragmentEggTimerBinding = DataBindingUtil.inflate( inflater, R.layout.fragment_egg_timer, container, false ) val viewModel = ViewModelProvider(this).get(EggTimerViewModel::class.java) binding.eggTimerViewModel = viewModel binding.lifecycleOwner = this.viewLifecycleOwner // Step 1.7 call create channel createChannel( getString(R.string.egg_notification_channel_id), getString(R.string.egg_notification_channel_name) ) return binding.root } private fun createChannel(channelId: String, channelName: String) { // Step 1.6 START create a channel if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationChannel = NotificationChannel( channelId, channelName, // Change importance NotificationManager.IMPORTANCE_LOW ) notificationChannel.enableLights(true) notificationChannel.lightColor = Color.RED notificationChannel.enableVibration(true) notificationChannel.description = "Time for breakfast!" val notificationManager = requireActivity().getSystemService( NotificationManager::class.java ) notificationManager.createNotificationChannel(notificationChannel) } else { Log.d(TAG, "VERSION.SDK_INT < O") Toast.makeText(context, "VERSION.SDK_INT < O", Toast.LENGTH_SHORT).show() } // Step 1.6 END create a channel } companion object { private const val TAG = "EggTimerFragment" fun newInstance() = EggTimerFragment() } }
Add a New Notification
AlarmReceiver
Add Notifications to Your App
-
Create another notification using AlarmReceiver
- Create an AlarmReceiver class that inherits BroadcastReceiver
- Get an instance of NotificationManager by calling getSystemService & call sendNotification on it
package com.example.android.eggtimernotifications.receiver import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.widget.Toast import androidx.core.content.ContextCompat import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.util.sendNotification class AlarmReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // Step 1.10 remove toast (since we're now sending a notification for the same) // Toast.makeText(context, context.getText(R.string.eggs_ready), Toast.LENGTH_SHORT).show() // TODO: Step 1.9 add call to sendNotification val notificationManager = ContextCompat.getSystemService( context, NotificationManager::class.java ) as NotificationManager notificationManager.sendNotification( context.getText(R.string.eggs_ready).toString(), context ) } }
Go Back to the App from the Notification using PendingIntent
NotificationUtils
Add a Content Intent
-
Navigate back to the app on tapping the notification
- Create an intent
- Create a pending intent
- Pass the pending intent to the notification by calling setContentIntent on the NotificationBuilder
- For the notification to dismiss itself as it takes you to the app when you tap on the it, set setAutoCancel to true
package com.example.android.eggtimernotifications.util import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import com.example.android.eggtimernotifications.MainActivity import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.receiver.SnoozeReceiver // Notification ID. private val NOTIFICATION_ID = 0 private val REQUEST_CODE = 0 private val FLAGS = 0 // Step 1.1 extension function to send messages (GIVEN) /** * Builds and delivers the notification. * * @param context, activity context. */ fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { // Create the content intent for the notification, which launches // this activity // Step 1.11 create intent val contentIntent = Intent(applicationContext, MainActivity::class.java) // Step 1.12 create PendingIntent val contentPendingIntent = PendingIntent.getActivity( applicationContext, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT ) // TODO: Step 2.0 add style // TODO: Step 2.2 add snooze action // Step 1.2 get an instance of NotificationCompat.Builder // Build the notification val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) // TODO: Step 1.8 use the new 'breakfast' notification channel // Step 1.3 set title, text and icon to builder .setSmallIcon(R.drawable.cooked_egg) .setContentTitle(applicationContext .getString(R.string.notification_title)) .setContentText(messageBody) // Step 1.13 set content intent .setContentIntent(contentPendingIntent) .setAutoCancel(true) // TODO: Step 2.1 add style to builder // TODO: Step 2.3 add snooze action // TODO: Step 2.5 set priority // Step 1.4 call notify notify(NOTIFICATION_ID, builder.build()) } // TODO: Step 1.14 Cancel all notifications
Clear the Previous Notifications using cancelAll
NotificationUtils, EggTimerViewModel
Cancel the Notification
-
Create an extension fun cancelNotifications
- Add an extension fun on NotificationManager that calls cancelAll
NotificationUtils
funNotificationManager.cancelNotifications() { cancelAll() }
-
Call the extension fun cancelNotifications in startTimer fun
EggTimerViewModel
notificationManager.cancelNotifications()
Styling Your Notification
NotificationUtils
-
Style your Notification in BigPictureStyle
- Load the image from resources using the BitmapFactory
- Create a new BigPictureStyle bigPicStyle & set your image
- For the large icon to go away when the notification is expanded, set the bigLargeIcon to null
- Set the new style bigPicStyle to the NotificationBuilder
- For the image to be displayed as a smaller icon when the notification is collapsed, set your image as setLargeIcon on the builder
package com.example.android.eggtimernotifications.util import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import com.example.android.eggtimernotifications.MainActivity import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.receiver.SnoozeReceiver // Notification ID. private const val NOTIFICATION_ID = 0 private const val REQUEST_CODE = 0 private const val FLAGS = 0 // Step 1.1 extension function to send messages (GIVEN) /** * Builds and delivers the notification. * * @param context, activity context. */ fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { // Create the content intent for the notification, which launches // this activity // Step 1.11 create intent val contentIntent = Intent(applicationContext, MainActivity::class.java) // Step 1.12 create PendingIntent val contentPendingIntent = PendingIntent.getActivity( applicationContext, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT ) // Step 2.0 add style val eggImage = BitmapFactory.decodeResource( applicationContext.resources, R.drawable.cooked_egg ) val bigPicStyle = NotificationCompat.BigPictureStyle() .bigPicture(eggImage) .bigLargeIcon(null) // TODO: Step 2.2 add snooze action // Step 1.2 get an instance of NotificationCompat.Builder // Build the notification val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) // TODO: Step 1.8 use the new 'breakfast' notification channel // Step 1.3 set title, text and icon to builder .setSmallIcon(R.drawable.cooked_egg) .setContentTitle(applicationContext .getString(R.string.notification_title)) .setContentText(messageBody) // Step 1.13 set content intent .setContentIntent(contentPendingIntent) .setAutoCancel(true) // Step 2.1 add style to builder .setStyle(bigPicStyle) .setLargeIcon(eggImage) // TODO: Step 2.3 add snooze action // TODO: Step 2.5 set priority // TODO: Step 1.4 call notify notify(NOTIFICATION_ID, builder.build()) } // Step 1.14 Cancel all notifications fun NotificationManager.cancelNotifications() { cancelAll() }
Add Snooze Action
SnoozeReceiver, NotificationUtils
-
Schedule a new notification & remove the snoozed one
- Get an instance of NotificationManager
package com.example.android.eggtimernotifications.receiver import android.app.AlarmManager import android.app.NotificationManager import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.SystemClock import android.text.format.DateUtils import androidx.core.app.AlarmManagerCompat import androidx.core.content.ContextCompat class SnoozeReceiver: BroadcastReceiver() { private val REQUEST_CODE = 0 override fun onReceive(context: Context, intent: Intent) { val triggerTime = SystemClock.elapsedRealtime() + DateUtils.MINUTE_IN_MILLIS val notifyIntent = Intent(context, AlarmReceiver::class.java) val notifyPendingIntent = PendingIntent.getBroadcast( context, REQUEST_CODE, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, notifyPendingIntent ) val notificationManager = ContextCompat.getSystemService( context, NotificationManager::class.java ) as NotificationManager notificationManager.cancelAll() } }
-
Set up a new alarm to post a new notification after 60 secs when the snooze button is tapped by the user by using a PendingIntent
- Create intent
- Create a PendingIntent
- Call the addAction fun on the NotificationBuilder
package com.example.android.eggtimernotifications.util import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import com.example.android.eggtimernotifications.MainActivity import com.example.android.eggtimernotifications.R import com.example.android.eggtimernotifications.receiver.SnoozeReceiver // Notification ID. private const val NOTIFICATION_ID = 0 private const val REQUEST_CODE = 0 private const val FLAGS = 0 // Step 1.1 extension function to send messages (GIVEN) /** * Builds and delivers the notification. * * @param context, activity context. */ fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { // Create the content intent for the notification, which launches // this activity // Step 1.11 create intent val contentIntent = Intent(applicationContext, MainActivity::class.java) // Step 1.12 create PendingIntent val contentPendingIntent = PendingIntent.getActivity( applicationContext, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT ) // Step 2.0 add style val eggImage = BitmapFactory.decodeResource( applicationContext.resources, R.drawable.cooked_egg ) val bigPicStyle = NotificationCompat.BigPictureStyle() .bigPicture(eggImage) .bigLargeIcon(null) // Step 2.2 add snooze action val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java) val snoozePendingIntent = PendingIntent.getBroadcast( applicationContext, REQUEST_CODE, snoozeIntent, FLAGS ) // Step 1.2 get an instance of NotificationCompat.Builder // Build the notification val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) // TODO: Step 1.8 use the new 'breakfast' notification channel // Step 1.3 set title, text and icon to builder .setSmallIcon(R.drawable.cooked_egg) .setContentTitle(applicationContext .getString(R.string.notification_title)) .setContentText(messageBody) // Step 1.13 set content intent .setContentIntent(contentPendingIntent) .setAutoCancel(true) // Step 2.1 add style to builder .setStyle(bigPicStyle) .setLargeIcon(eggImage) // Step 2.3 add snooze action .addAction( R.drawable.egg_icon, applicationContext.getString(R.string.snooze), snoozePendingIntent ) // TODO: Step 2.5 set priority // Step 1.4 call notify notify(NOTIFICATION_ID, builder.build()) } // Step 1.14 Cancel all notifications fun NotificationManager.cancelNotifications() { cancelAll() }
Change Importance
EggTimerFragment, NotificationUtils
-
Set the picture
// NotificationUtils() // Step 2.0 add style val eggImage = BitmapFactory.decodeResource( applicationContext.resources, R.drawable.cooked_egg ) val bigPicStyle = NotificationCompat.BigPictureStyle() .bigPicture(eggImage) .bigLargeIcon(null) ********************************************************************************** val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) ... // Step 2.1 add style to builder .setStyle(bigPicStyle) .setLargeIcon(eggImage)
-
Change importance from low to high
-
NotificationManager β IMPORTANCE
... class EggTimerFragment : Fragment() { ... private fun createChannel(channelId: String, channelName: String) { // Step 1.6 START create a channel if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationChannel = NotificationChannel( channelId, channelName, // Change importance NotificationManager.IMPORTANCE_HIGH ) notificationChannel.enableLights(true) notificationChannel.lightColor = Color.RED notificationChannel.enableVibration(true) notificationChannel.description = "Time for breakfast!" val notificationManager = requireActivity().getSystemService( NotificationManager::class.java ) notificationManager.createNotificationChannel(notificationChannel) } else { Log.d(TAG, "VERSION.SDK_INT < O") Toast.makeText(context, "VERSION.SDK_INT < O", Toast.LENGTH_SHORT).show() } // Step 1.6 END create a channel } ... }
-
-
Add PRIORITY_HIGH to notification builder object
To support devices running API level 25 or lower, you must call setPriority for each notification, using a constant from the NotificationCompat class. To fix this, we're doing the above.
// *NotificationUtils.kt* fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { ... // Step 1.2 get an instance of NotificationCompat.Builder // Build the notification val builder = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.egg_notification_channel_id) ) ... // Step 2.5 set priority .setPriority(NotificationCompat.PRIORITY_HIGH) ... } ...
Notification Badges
EggTimerFragment
-
Disable badge
- Set setShowBadge to false on the NotificationChannel obj.
// *EggTimerFragment.kt* private fun createChannel(channelId: String, channelName: String) { // Step 1.6 START create a channel if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationChannel = NotificationChannel( channelId, channelName, // Change importance NotificationManager.IMPORTANCE_HIGH ) // Step 2.6 Disable badges for this channel .apply { setShowBadge(false) } ... } }
Top comments (0)