Listening to the View Lifecycle

Before running PhotoGallery and seeing your hard-won images, there is one last danger you need to account for. If the user rotates the screen, ThumbnailDownloader may be hanging on to invalid PhotoHolders. Your app could crash if ThumbnailDownloader tries to send a bitmap to a PhotoHolder that was part of a view that was destroyed across rotation.

Fix this leak by clearing all the requests out of your queue when the fragment’s view is destroyed. To do this, ThumbnailDownloader needs to know about the fragment’s view lifecycle. (Recall that the lifecycles for the fragment and its view diverge, since you are retaining the fragment: The view will be destroyed across rotation, but the fragment instance itself will not.)

First, refactor your existing fragment lifecycle observer code to make room for a second lifecycle observer implementation.

Listing 25.19  Refactoring your fragment lifecycle observer (ThumbnailDownloader.kt)

class ThumbnailDownloader<in T>(
    private val responseHandler: Handler,
    private val onThumbnailDownloaded: (T, Bitmap) -> Unit
) : HandlerThread(TAG), LifecycleObserver {

    val fragmentLifecycleObserver: LifecycleObserver =
        object : LifecycleObserver {

            @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
            fun setup() {
                Log.i(TAG, "Starting background thread")
                start()
                looper
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun tearDown() {
                Log.i(TAG, "Destroying background thread")
                quit()
            }
        }

    private var hasQuit = false
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun setup() {
        start()
        looper
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun tearDown() {
        Log.i(TAG, "Background thread destroyed")
        quit()
    }
    ...
}

Next, define a new observer that will eventually listen to lifecycle callbacks from the fragment’s view.

Listing 25.20  Adding a view lifecycle observer (ThumbnailDownloader.kt)

class ThumbnailDownloader<in T>(
    private val responseHandler: Handler,
    private val onThumbnailDownloaded: (T, Bitmap) -> Unit
) : HandlerThread(TAG) {

    val fragmentLifecycleObserver: LifecycleObserver =
        object : LifecycleObserver {
        ...
    }

    val viewLifecycleObserver: LifecycleObserver =
        object : LifecycleObserver {

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun clearQueue() {
                Log.i(TAG, "Clearing all requests from queue")
                requestHandler.removeMessages(MESSAGE_DOWNLOAD)
                requestMap.clear()
            }
        }
    ...
}

When observing a fragment’s view lifecycle, Lifecycle.Event.ON_DESTROY maps to Fragment.onDestroyView(). (To learn how the other Lifecycle.Event constants map to the fragment’s lifecycle callbacks when you observe the fragment’s view lifecycle, see getViewLifecycleOwner in the API reference for Fragment at developer.android.com/​reference/​kotlin/​androidx/​fragment/​app/​Fragment.)

Now update PhotoGalleryFragment to register the refactored fragment observer. Also, register the newly added lifecycle observer to listen for the fragment’s view lifecycle.

Listing 25.21  Registering the view lifecycle observer (PhotoGalleryFragment.kt)

class PhotoGalleryFragment : Fragment() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        thumbnailDownloader =
            ThumbnailDownloader(responseHandler) {
            ...
        }
        lifecycle.addObserver(thumbnailDownloader.fragmentLifecycleObserver)
    }

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        viewLifecycleOwner.lifecycle.addObserver(
            thumbnailDownloader.viewLifecycleObserver
        )
        ...
    }
    ...
}

You can safely observe the view lifecycle in Fragment.onCreateView(…) so long as you return a non-null view at the end of the onCreateView(…) function. See the section called Challenge: Observing View LifecycleOwner LiveData near the end of this chapter to learn more about observing viewLifecycleOwner, which offers more flexibility in configuring the view lifecycle observation.

Finally, unregister thumbnailDownloader as a fragment view lifecycle observer in Fragment.onDestroyView(). Update the existing code that unregisters thumbnailDownloader as a fragment lifecycle observer to reflect your recent refactor.

Listing 25.22  Unregistering the view lifecycle observer (PhotoGalleryFragment.kt)

class PhotoGalleryFragment : Fragment() {
    ...
    override fun onDestroyView() {
        super.onDestroyView()
        viewLifecycleOwner.lifecycle.removeObserver(
            thumbnailDownloader.viewLifecycleObserver
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycle.removeObserver(
            thumbnailDownloader.fragmentLifecycleObserver
        )
    }
    ...
}

As with the fragment lifecycle observers, you do not have to explicitly unregister fragment viewLifecycleOwner.lifecycle observers. The fragment’s view lifecycle registry, which keeps track of all of the view lifecycle observers, gets nulled out when the fragment’s view is destroyed. However, just as before, we prefer to explicitly unregister observers rather than rely on garbage collection.

With that, your work for this chapter is complete. Run PhotoGallery. Scroll around to see images dynamically loading (Figure 25.10).

Figure 25.10  Images on display

Images on display

PhotoGallery has achieved its basic goal of displaying images from Flickr. In the next few chapters, you will add more functionality, like searching for photos and opening each photo’s Flickr page in a web view.

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

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