Skip to main content

07 - Android Notifications

Notifications

Notifications

  • Status bar

Notifications

  • Drawer

Notifications

  • Lock screen

Notifications

  • Heads-up (5.0+)

Notifications

  • App icon badge
  • Wear OS

Design

Material Design guide

Notifications design guide

Android Wear! maybe its coming back?

  • Oppo, Xiaomi Mi

Notifications

Notifications

Notifications

Notifications

Standard notification

  • Small icon - required
  • App name – by system
  • Time stamp – by system – or setWhen() or setShowWhen(false)
  • Large icon – optional, only for contact photos
  • Title – optional, setContentTitle()
  • Text – optional, setContentText()
  • Actions – add buttons, open app when notification is tapped

Notifications

Changes over versions

  • API 21 (5.0) – Do not disturb mode

  • API 24 (7.0) – Notification grouping, Direct text input from notification

  • API 26 (8.0) - Notification channels are mandatory

    • Better control by user (alarms, visibility, etc.)
    • Max 1 notification sound per 1 second per 1 app
  • Notification is required for foreground service – long lived service in background and noticeable to user – ala media player or gps app.

Creating a Notification

  • Create the channel and set the importance – if on 8.0+

  • Specify the UI information and actions for a notification in a NotificationCompat.Builder object

  • Build the notification object

  • Pass the Notification object to the system by calling NotificationManager.notify()

  • Create channel, call it OnCreate

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"channel",
"Default channel",
NotificationManager.IMPORTANCE_DEFAULT
);
channel.description = "Default channel for Channel Demo"
var notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager;
notificationManager.createNotificationChannel(channel)
}
}
    fun button0OnClick(view: View) {
var builder = NotificationCompat.Builder(this, "channel")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setContentTitle("Title is here")
.setContentText("Content text is this!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)

NotificationManagerCompat.from(this).notify(0, builder.build())
}

Notification action

  • You should add at least one action – to navigate into your app/activity
  • You can also add Buttons for additional actions
  • Action is defined by PendingIntent, containing an Intent
  • Navigation stack - what should happen in your activity, when back is pressed (should it navigate to the home screen or to some other activity)
    fun button1OnClick(view: View) {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent
.getActivity(this,0, intent, PendingIntent.FLAG_IMMUTABLE)


var builder = NotificationCompat.Builder(this, "channel")
.setSmallIcon(android.R.drawable.sym_contact_card)
.setContentTitle("Title is here")
.setContentText("Content text is this!")
//.setContentInfo("info is here")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)


NotificationManagerCompat.from(this).notify(0, builder.build())

}

Expanded layout

Expanded layout – action buttons depend on it (android 4.1+)

Notifications

Update and remove

Update notification

  • Issue notification with a notification ID by calling NotificationManager.notify()
  • Update or create a NotificationCompat.Builder object
  • build a Notification object from it, and issue the Notification with the same ID.
  • If the previous notification is still visible, the system updates it.
  • If the previous notification has been dismissed, a new notification is created.

Remove notification

  • notificatoinManager.cancel(id)
  • notificatoinManager.cancelAll()
  • Or use setTimeoutAfter() for timed removal

Regular activity

Start an activity, that is part of normal workflow of your app

  • ActivityA is your main activity
  • ActivityB is started from notification
  • Backstack: Home <- ActivityA <- ActivityB

Special activity

Start an activity, that is an extension of your notification

  • No backstack needed, touching back takes you directly to Home screen

Regular notification

Define hierarchy in manifest

<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name=".ResultActivity"
    android:parentActivityName=".MainActivity">
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value=".MainActivity"/>
</activity>
  • Create Intent
  • Create stack builder – TaskStackBuilder.Create()
  • Get and use pendingIntent
// Create an Intent for the activity you want to start
val resultIntent = Intent(this, ResultActivity::class.java)

// Create the TaskStackBuilder
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
// Add the intent, which inflates the back stack
addNextIntentWithParentStack(resultIntent)
// Get the PendingIntent containing the entire back stack
getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE)
}

Special notification

Manifest

<activity
    android:name=".ResultActivity"
    android:launchMode="singleTask"
    android:taskAffinity=""
    android:excludeFromRecents="true">
</activity>

Build and issue the notification

  • Create Intent for Activity
  • setFlags() with the flags FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TASK
  • Create a PendingIntent from the Intent by calling getActivity()
fun onClickButtonNotify(view: View) {
// create intent
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

// construct and show notification
var builder = NotificationCompat.Builder(this, "channel")
.setSmallIcon(R.drawable.baseline_android_24)
.setContentTitle("Notification title")
.setContentText("Notification body text")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
// should notification be removed, when user clicks on it
.setAutoCancel(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

NotificationManagerCompat.from(this).notify(0, builder.build())
}

Communication from notification

Action, without starting whole new Activity

  • Use broadcasts, ofcourse!
  • = PendingIntent.getBroadcast()
  • And regular (not local) BroadcastReceiver in your app/activity
    var broadcastReceiverIntentFilter = IntentFilter()
private val broadcastReceiver = CustomBrodcastReceiver()


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

createNotificationChannel()

broadcastReceiverIntentFilter.addAction("ee.itcollege.akaver.cp")

}


fun button3OnClick(view: View) {
val notifyView = RemoteViews(packageName, R.layout.notification)

val intentLaunch = Intent(this, MainActivity::class.java)
val pendingIntentLaunch = PendingIntent.getActivity(this,0, intentLaunch,0)

val intent = Intent("ee.itcollege.akaver.cp")
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent,0)

notifyView.setOnClickPendingIntent(R.id.imageButtonCheckpoint, pendingIntent);
notifyView.setTextViewText(R.id.textViewCounter, "some text")

var builder = NotificationCompat.Builder(this, "channel")
.setSmallIcon(android.R.drawable.sym_contact_card)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntentLaunch)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notifyview)
.setContent(notifyview)

NotificationManagerCompat.from(this).notify(0, builder.build())
}


private inner class CustomBrodcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("receiver", "onReceive")
}

}

override fun onResume() {
super.onResume()
this.registerReceiver(broadcastReceiver,broadcastReceiverIntentFilter )
}

override fun onPause() {
super.onPause()
this.unregisterReceiver(broadcastReceiver)
}

Custom layout

Use RemoteViews

Define layout in usual way (xml), max height 64dp (expanded 256dp)

val intentCp = Intent("ee.itcollege.akaver.cp")
val pendingIntentCp = PendingIntent.getBroadcast(this, 0, intentCp, 0)
val notifyview = RemoteViews(packageName, R.layout.notification)
notifyview.setOnClickPendingIntent(R.id.imageButtonCheckpoint, pendingIntentCp)

// construct and show notification
var builder = NotificationCompat.Builder(this, "channel")
.setSmallIcon(R.drawable.baseline_my_location_black_24)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notifyview)
.setContent(notifyview)

NotificationManagerCompat.from(this).notify(0, builder.build())

RemoteViews

Supports only these layouts:

  • AdapterViewFlipper
  • FrameLayout
  • GridLayout
  • GridView
  • LinearLayout
  • ListView
  • RelativeLayout
  • StackView
  • ViewFlipper

And only these widgets:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextClock
  • TextView

Descendants of these classes are also not supported.

NB!
Don’t set onClick on shared views (views used both in regular layouts and notifications)

RemoteViews - update content

  • Update texts
    • remoteView.setTextViewText(R.id.text, "text")
  • Set intents to button clicks
    • remoteView.setOnClickPendingIntent(R.id.button, pendingIntent)
  • Since this is pending intent, you need to use proper broadcastReceiver to receive it
    • registerReceiver(mBroadcastReceiver, intentFilter)
    • unregisterReceiver(mBroadcastReceiver)

Custom Layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:internal="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="64dp"
internal:layout_maxHeight="64dp"
internal:layout_minHeight="64dp" >

<ImageView
android:id="@+id/notification_icon_iv"
android:layout_width="64dp"
android:layout_height="64dp"
android:padding="10dp"
android:layout_alignParentLeft="true"
android:scaleType="center"
android:src="@drawable/notification_icon"
android:background="#3333B5E5" />

<ImageButton
android:id="@+id/notification_closebtn_ib"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:scaleType="centerInside"
android:src="@drawable/notification_exitbtn"
android:background="@drawable/notification_imagebtn_bg"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:gravity="top"
android:minHeight="64dp"
android:layout_toRightOf="@id/notification_icon_iv"
android:layout_toLeftOf="@id/notification_closebtn_ib"
android:orientation="vertical"
android:paddingBottom="2dp"
android:paddingEnd="8dp"
android:paddingTop="2dp" >

<TextView
android:id="@+id/notification_title_tv"
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:ellipsize="marquee"
android:paddingTop="6dp"
android:singleLine="true"
android:text="JMols Service" />

<TextView
android:id="@+id/notification_contenttext_tv"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Service running in the background" />

</LinearLayout>

</RelativeLayout>

Notifcation Background

<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">

<item android:state_pressed="true" android:drawable="@drawable/notification_bg_normal_pressed" />
<item android:state_pressed="false" android:drawable="@drawable/notification_bg_normal" />
</selector>

Imagebutton background

<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">

<item android:state_pressed="true" android:drawable="@drawable/notification_imagebtn_bg_normal_pressed" />
<item android:state_pressed="false" android:drawable="@drawable/notification_imagebtn_bg_normal" />
</selector>

Foreground service and notification

         // construct and show notification
var builder = NotificationCompat.Builder(applicationContext, C.NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.baseline_gps_fixed_24)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

builder.setContent(notifyview)

// Super important, start as foreground service - ie android considers this as an active app. Need visual reminder - notification.
// must be called within 5 secs after service starts.
startForeground(C.NOTIFICATION_ID, builder.build())