Notifying the User

Your worker is now running and checking for new photos in the background, but the user does not know anything about it. When PhotoGallery finds new photos the user has not seen yet, it should prompt the user to open the app and see the new content.

When your app needs to communicate something to the user, the proper tool is almost always a notification. Notifications are items that appear in the notifications drawer, which the user can access by dragging down from the top of the screen.

Before you can create notifications on Android devices running Android Oreo (API level 26) and higher, you must create a Channel. A Channel categorizes notifications and gives the user fine-grained control over notification preferences. Rather than only having the option to turn off notifications for your entire app, the user can choose to turn off certain categories of notifications within your app. The user can also customize muting, vibration, and other notification settings channel by channel.

For example, suppose you wanted PhotoGallery to send three categories of notifications when new cute animal pictures were fetched: New Kitten Pics, New Puppy Pics, and Totes Adorbs! (for all adorable animal pictures, regardless of species). You would create three channels, one for each of the notification categories, and the user could configure them independently (Figure 27.4).

Figure 27.4  Fine-grained notification configuration for channels

Fine-grained notification configuration for channels

Your application must create at least one channel to support Android Oreo and higher. There is no documented upper limit on the number of channels an app can create. But be reasonable – keep the number small and meaningful for the user. Remember that the goal is to allow the user to configure notifications in your app. Adding too many channels can ultimately confuse the user and make for a poor user experience.

Add a new class named PhotoGalleryApplication. Extend Application and override Application.onCreate() to create and add a channel if the device is running Android Oreo or higher.

Listing 27.9  Creating a notification channel (PhotoGalleryApplication.kt)

const val NOTIFICATION_CHANNEL_ID = "flickr_poll"

class PhotoGalleryApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = getString(R.string.notification_channel_name)
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel =
                    NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
            val notificationManager: NotificationManager =
                    getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }
    }
}

The channel name is a user-facing string, displayed in the notification settings screen for your app (shown in Figure 27.4). Add a string resource to res/values/strings.xml to store the channel name. While you are there, go ahead and add the other strings needed for your notification.

Listing 27.10  Adding strings (res/values/strings.xml)

<resources>
    <string name="clear_search">Clear Search</string>
    <string name="notification_channel_name">FlickrFetchr</string>
    <string name="new_pictures_title">New PhotoGallery Pictures</string>
    <string name="new_pictures_text">You have new pictures in PhotoGallery.</string>
</resources>

Next, update the manifest to point to the new application class you created.

Listing 27.11  Updating the application tag in the manifest (manifests/AndroidManifest.xml)

<manifest ... >
...
  <application
      android:name=".PhotoGalleryApplication"
      android:allowBackup="true"
      ... >

  </application>
</manifest>

To post a notification, you need to create a Notification object. Notifications are created by using a builder object, much like the AlertDialog that you used in Chapter 13. At a minimum, your Notification should have:

  • an icon to show in the status bar

  • a view to show in the notification drawer to represent the notification itself

  • a PendingIntent to fire when the user presses the notification in the drawer

  • a NotificationChannel to apply styling and provide user control over the notification

You will also add ticker text to the notification. This text does not display when the notification shows, but it is sent to the accessibility services so things like screen readers can notify users with visual impairments.

Once you have created a Notification object, you can post it by calling notify(Int, Notification) on the NotificationManager system service. The Int is the ID of the notification from your app.

First you need to add some plumbing code, shown in Listing 27.12. Open PhotoGalleryActivity.kt and add a newIntent(Context) function. This function will return an Intent instance that can be used to start PhotoGalleryActivity. (Eventually, PollWorker will call PhotoGalleryActivity.newIntent(…), wrap the resulting intent in a PendingIntent, and set that PendingIntent on a notification.)

Listing 27.12  Adding newIntent(…) to PhotoGalleryActivity (PhotoGalleryActivity.kt)

class PhotoGalleryActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
    }

    companion object {
        fun newIntent(context: Context): Intent {
            return Intent(context, PhotoGalleryActivity::class.java)
        }
    }
}

Now make PollWorker notify the user that a new result is ready by creating a Notification and calling NotificationManager.notify(Int, Notification).

Listing 27.13  Adding a notification (PollWorker.kt)

class PollWorker(val context: Context, workerParameters: WorkerParameters)
    : Worker(context, workerParameters) {

    override fun doWork(): Result {
        ...
        val resultId = items.first().id
        if (resultId == lastResultId) {
            Log.i(TAG, "Got an old result: $resultId")
        } else {
            Log.i(TAG, "Got a new result: $resultId")
            QueryPreferences.setLastResultId(context, resultId)

            val intent = PhotoGalleryActivity.newIntent(context)
            val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)

            val resources = context.resources
            val notification = NotificationCompat
                .Builder(context, NOTIFICATION_CHANNEL_ID)
                .setTicker(resources.getString(R.string.new_pictures_title))
                .setSmallIcon(android.R.drawable.ic_menu_report_image)
                .setContentTitle(resources.getString(R.string.new_pictures_title))
                .setContentText(resources.getString(R.string.new_pictures_text))
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .build()

            val notificationManager = NotificationManagerCompat.from(context)
            notificationManager.notify(0, notification)
        }

        return Result.success()
    }
}

Let’s go over this from top to bottom.

You use the NotificationCompat class to easily support notifications on both pre-Oreo and Oreo-and-above devices. NotificationCompat.Builder accepts a channel ID and uses the ID to set the notification’s channel if the user is running Oreo or above. If the user is running a pre-Oreo version of Android, NotificationCompat.Builder ignores the channel. (Note that the channel ID you pass here comes from the NOTIFICATION_CHANNEL_ID constant you added to PhotoGalleryApplication.)

In Listing 27.9, you checked the build version SDK before you created the channel, because there is no AppCompat API for creating a channel. You do not need to do that here, because AppCompat’s NotificationCompat does the grunt work of checking the build version for you, keeping your code clean and spiffy. This is one reason you should use an AppCompat version of the Android APIs whenever available.

You configure the ticker text and small icon by calling setTicker(CharSequence) and setSmallIcon(Int). (The icon resource you are using is provided as part of the Android framework, denoted by the package name qualifier android in android.R.drawable.ic_menu_report_image, so you do not have to pull the icon image into your resource folder.)

After that, you configure the appearance of your Notification in the drawer itself. It is possible to create a completely custom look and feel, but it is easier to use the standard look for a notification, which features an icon, a title, and a text area. It will use the value from setSmallIcon(Int) for the icon. To set the title and text, you call setContentTitle(CharSequence) and setContentText(CharSequence), respectively.

Next, you specify what happens when the user presses your Notification. This is done using a PendingIntent object. The PendingIntent you pass into setContentIntent(PendingIntent) will be fired when the user presses your Notification in the drawer. Calling setAutoCancel(true) tweaks that behavior a little bit: The notification will also be deleted from the notification drawer when the user presses it.

Finally, you get an instance of NotificationManager from the current context (NotificationManagerCompat.from) and call NotificationManager.notify(…) to post your notification.

The integer parameter you pass to notify(…) is an identifier for your notification. It should be unique across your application, but it is reusable. A notification will replace another notification with the same ID that is still in the notification drawer. If there is no existing notification with the ID, the system will show a new notification. This is how you would implement a progress bar or other dynamic visuals.

And that is it. Run your app, and you should eventually see a notification icon appear in the status bar (Figure 27.5). (You will want to clear any search terms to speed things along.)

Figure 27.5  New photos notification

New photos notification
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.236.139.73