Providing User Control over Polling

Some users may not want your app to run in the background. An important control to provide users is the ability to enable and disable background polling.

For PhotoGallery, you will add a menu item to the toolbar that will toggle your worker when selected. You will also update your work request to run your worker periodically instead of just once.

To toggle your worker, you first need to determine whether the worker is currently running. To do this, supplement your QueryPreferences to store a flag indicating if the worker is enabled.

Listing 27.14  Saving Worker state (QueryPreferences.kt)

private const val PREF_SEARCH_QUERY = "searchQuery"
private const val PREF_LAST_RESULT_ID = "lastResultId"
private const val PREF_IS_POLLING = "isPolling"

object QueryPreferences {
    ...
    fun setLastResultId(context: Context, lastResultId: String) {
        ...
    }

    fun isPolling(context: Context): Boolean {
        return PreferenceManager.getDefaultSharedPreferences(context)
            .getBoolean(PREF_IS_POLLING, false)
    }

    fun setPolling(context: Context, isOn: Boolean) {
        PreferenceManager.getDefaultSharedPreferences(context).edit {
            putBoolean(PREF_IS_POLLING, isOn)
        }
    }
}

Next, add the string resources your options menu item needs. You will need two strings, one to prompt the user to enable polling and one to prompt them to disable it.

Listing 27.15  Adding poll-toggling resources (res/values/strings.xml)

<resources>
    ...
    <string name="new_pictures_text">You have new pictures in PhotoGallery.</string>
    <string name="start_polling">Start polling</string>
    <string name="stop_polling">Stop polling</string>
</resources>

With your strings in place, open up your res/menu/fragment_photo_gallery.xml menu file and add a new item for your polling toggle.

Listing 27.16  Adding a poll-toggling item (res/menu/fragment_photo_gallery.xml)

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    ...
    <item android:id="@+id/menu_item_clear"
          android:title="@string/clear_search"
          app:showAsAction="never" />

    <item android:id="@+id/menu_item_toggle_polling"
          android:title="@string/start_polling"
          app:showAsAction="ifRoom|withText"/>
</menu>

The default text for this item is the start_polling string. You will need to update this text if the worker is already running. Open PhotoGalleryFragment.kt and update onCreateOptionsMenu(…) to check whether the worker is already running and, if so, to set the correct title text. Also, delete the OneTimeWorkRequest logic from the onCreate(…) function, since it is no longer needed.

Listing 27.17  Setting correct menu item text (PhotoGalleryFragment.kt)

class PhotoGalleryFragment : Fragment() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        lifecycle.addObserver(thumbnailDownloader)

        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.UNMETERED)
            .build()
        val workRequest = OneTimeWorkRequest
            .Builder(PollWorker::class.java)
            .setConstraints(constraints)
            .build()
        WorkManager.getInstance()
            .enqueue(workRequest)
    }
    ...
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        ...
        searchView.apply {
            ...
        }

        val toggleItem = menu.findItem(R.id.menu_item_toggle_polling)
        val isPolling = QueryPreferences.isPolling(requireContext())
        val toggleItemTitle = if (isPolling) {
            R.string.stop_polling
        } else {
            R.string.start_polling
        }
        toggleItem.setTitle(toggleItemTitle)
    }
    ...
}

Finally, update onOptionsItemSelected(…) to respond to the poll-toggling item clicks. If the worker is not running, create a new PeriodicWorkRequest and schedule it with the WorkManager. If the worker is running, stop it.

Listing 27.18  Handling poll-toggling item clicks (PhotoGalleryFragment.kt)

private const val TAG = "PhotoGalleryFragment"
private const val POLL_WORK = "POLL_WORK"

class PhotoGalleryFragment : Fragment() {
    ...
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.menu_item_clear -> {
                photoGalleryViewModel.fetchPhotos("")
                true
            }
            R.id.menu_item_toggle_polling -> {
               val isPolling = QueryPreferences.isPolling(requireContext())
               if (isPolling) {
                   WorkManager.getInstance().cancelUniqueWork(POLL_WORK)
                   QueryPreferences.setPolling(requireContext(), false)
               } else {
                   val constraints = Constraints.Builder()
                       .setRequiredNetworkType(NetworkType.UNMETERED)
                       .build()
                   val periodicRequest = PeriodicWorkRequest
                       .Builder(PollWorker::class.java, 15, TimeUnit.MINUTES)
                       .setConstraints(constraints)
                       .build()
                   WorkManager.getInstance().enqueueUniquePeriodicWork(POLL_WORK,
                       ExistingPeriodicWorkPolicy.KEEP,
                       periodicRequest)
                   QueryPreferences.setPolling(requireContext(), true)
               }
               activity?.invalidateOptionsMenu()
               return true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
    ...
}

Focus first on the else block you added here. If the worker is currently not running, then you schedule a new work request with the WorkManager. In this case, you are using the PeriodicWorkRequest class to make your worker reschedule itself on an interval. The work request uses a builder, like the OneTimeWorkRequest you used previously. The builder needs the worker class to run, as well as the interval it should use to execute.

If you are thinking that 15 minutes is a long time for an interval, you are right. However, if you tried to enter a smaller interval value, you would find that your worker still executes on a 15-minute interval. This is the minimum interval allowed for a PeriodicWorkRequest so that the system is not tied up running the same work request all the time. This saves system resources – and the user’s battery life.

The PeriodicWorkRequest builder accepts constraints, just like the one-time request, so you can add the unmetered network requirement. When you want to schedule the work request, you use the WorkManager class, but this time you use the enqueueUniquePeriodicWork(…) function. This function takes in a String name, a policy, and your work request. The name allows you to uniquely identify the request, which is useful when you want to cancel it.

The existing work policy tells the work manager what to do if you have already scheduled a work request with a particular name. In this case you use the KEEP option, which discards your new request in favor of the one that already exists. The other option is REPLACE, which, as the name implies, will replace the existing work request with the new one.

If the worker is already running, then you need to tell the WorkManager to cancel the work request. In this case, you call the cancelUniqueWork(…) function with the "POLL_WORK" name to remove the periodic work request.

Run the application. You should see your new menu item to toggle polling. If you do not want to wait for the 15-minute interval, you can disable the polling, wait a few seconds, then enable polling to rerun the work request.

PhotoGallery can now keep the user up to date with the latest images automatically, even when the app is not running. But there is one problem: The user will get notifications any time new images arrive – even when the app is already running. This is not desirable, because it takes the user’s attention away from your app. Plus, if the user presses the notification, a new instance of PhotoGalleryActivity will be launched and added to your app’s back stack.

You will fix this in the next chapter by preventing notifications from appearing while PhotoGallery is running. In making these updates, you will learn how to listen for broadcast intents and how to handle such intents using a broadcast receiver.

..................Content has been hidden....................

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