Error: requestHandler has not been initialized - any help?

I’m facing an issue with requestHandler… Maybe i’m not seeing a specific issue. but i checked the code several times and compared it with the code provided from BNR but couldn’t find the missing part…

I get this error:

kotlin.UninitializedPropertyAccessException: lateinit property requestHandler has not been initialized at com.bignerdranch.android.photogallery.ThumbnailDownloader.queueThumbnail(ThumbnailDownloader.kt:94)

it is referring to this one…

fun queueThumbnail(target: T, url: String){ //page 510
        Log.i(TAG, "Got a URL: $url")
        requestMap[target] = url //page 521
        requestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
            .sendToTarget()
    }

this code is being called by the Recyclerview as you know in the PhotoGalleryFragment

 override fun onBindViewHolder(holder: PhotoHolder, position: Int) {
            val galleryItem = galleryItems[position]
            //holder.bindTitle(galleryItem.title)
            val placeholder: Drawable = ContextCompat.getDrawable(
                requireContext(),
                R.drawable.hala_atamleh
            )?: ColorDrawable()
            holder.bindTitle(placeholder) //should be bindDrawable but it didn't work

            thumbnailDownloader.queueThumbnail(holder, galleryItem.url) //page 515
        }

what did i missed to get this error?

here is the file code:

package com.bignerdranch.android.photogallery

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import java.util.concurrent.ConcurrentHashMap

private const val TAG = "ThumbnailDownloader"
private const val MESSAGE_DOWNLOAD = 0

class ThumbnailDownloader<in T>(private val responseHandler: Handler, //page. 524
private val onThumbnailDownloaded : (T, Bitmap) -> Unit)
    : HandlerThread(TAG) /*, LifecycleObserver page 512*/ { //page. 510

    private lateinit var requestHandler: Handler //page516++
    private var hasQuit = false
    private val requestMap = ConcurrentHashMap<T, String>()
    private val flickrFetchr = FlickrFetchr()



    val fragmentLifecycleObserver: LifecycleObserver = //page 527
        object : LifecycleObserver{

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

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

    @Suppress("UNCHECKED_CAST") //page 522
    @SuppressLint("HandlerLeak")
    override fun onLooperPrepared() {
        requestHandler = object : Handler(Looper.getMainLooper()){
            override fun handleMessage(msg: Message) {
                if (msg.what == MESSAGE_DOWNLOAD){
                    val target = msg.obj as T
                    Log.i(TAG, "Got a request for URL: ${requestMap[target]}")
                    handleRequest(target)
                }
            }
        }
    }

    //page 528
    val viewLifecycleObserver : LifecycleObserver =
        object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun tearDown(){
                Log.i(TAG, "Clearing all requests from Queue")
                requestHandler.removeMessages(MESSAGE_DOWNLOAD)
                requestMap.clear()
            }
        }

    override fun quit(): Boolean {
        hasQuit = true
        return super.quit()
    }


    fun queueThumbnail(target: T, url: String){ //page 510
        Log.i(TAG, "Got a URL: $url")
        requestMap[target] = url //page 521
        requestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
            .sendToTarget()
    }

    fun clearQueue() {
        requestHandler.removeMessages(MESSAGE_DOWNLOAD)
        requestMap.clear()
    }


    private fun handleRequest(target: T){
        val url = requestMap[target] ?: return
        val bitmap = flickrFetchr.fetchPhoto(url) ?: return

        responseHandler.post(Runnable { //page.526
            if(requestMap[target] != url || hasQuit){
                return@Runnable
            }
            requestMap.remove(target)
            onThumbnailDownloaded(target, bitmap)
        })
    }
}

anyway i followed the code again. found few differences…

changed requestHandler to nullable and it worked… the only thing is that the images aren’t updating…

same old image…

I think I’m really tired… but i’m still looking for a solution…

package com.bignerdranch.android.photogallery

import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

private lateinit var photoRecyclerView : RecyclerView
private lateinit var photoGalleryViewModel: PhotoGalleryViewModel

private lateinit var thumbnailDownloader: ThumbnailDownloader<PhotoGalleryFragment.PhotoHolder> 

private const val TAG = "PhotoGalleryFragment"

class PhotoGalleryFragment:Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true //page 511


        photoGalleryViewModel = //page 494
            ViewModelProvider(this).get(PhotoGalleryViewModel::class.java)


        //thumbnailDownloader = ThumbnailDownloader() //p.513

        val responseHandler = Handler(Looper.getMainLooper())//page 525
        thumbnailDownloader =
            ThumbnailDownloader(responseHandler){ photoHolder, bitmap ->
                val drawable = BitmapDrawable(resources, bitmap)
                photoHolder.bindDrawable(drawable)
            }
        lifecycle.addObserver(thumbnailDownloader.viewLifecycleObserver)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewLifecycleOwner.lifecycle.addObserver( //page. 529
            thumbnailDownloader.viewLifecycleObserver
        )
        val view = inflater.inflate(R.layout.fragment_photo_gallery, container, false)

        photoRecyclerView = view.findViewById(R.id.photo_recycler_view)
        photoRecyclerView.layoutManager = GridLayoutManager(context,3)

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        photoGalleryViewModel.galleryItemLiveData.observe(
            viewLifecycleOwner,
            Observer { galleryItems ->
            Log.d(TAG,"Have gallery items from ViewModel $galleryItems" )

                photoRecyclerView.adapter = PhotoAdapter(galleryItems)

            }
        )
    }

    class PhotoHolder(private val itemImageView: ImageView)
        : RecyclerView.ViewHolder(itemImageView){ //page 496
         val bindDrawable: (Drawable) -> Unit = itemImageView::setImageDrawable
    }

    private inner class PhotoAdapter(private val galleryItems: List<GalleryItem>)
        :RecyclerView.Adapter<PhotoHolder>(){
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoHolder {
        val view = layoutInflater.inflate(
            R.layout.list_item_gallery,
            parent,
            false) as ImageView
            return PhotoHolder(view)
        }

        override fun onBindViewHolder(holder: PhotoHolder, position: Int) {
            val galleryItem = galleryItems[position]
            //holder.bindTitle(galleryItem.title)
            val placeholder: Drawable = ContextCompat.getDrawable(
                requireContext(),
                R.drawable.ic_launcher_foreground
            )?: ColorDrawable()
            holder.bindDrawable(placeholder)

            thumbnailDownloader.queueThumbnail(holder, galleryItem.url) //page 515
        }

        override fun getItemCount(): Int = galleryItems.size

    }


    companion object{
        fun newInstance() = PhotoGalleryFragment()
    }

    override fun onDestroyView() { //page 530
        super.onDestroyView()
        thumbnailDownloader.clearQueue()
        viewLifecycleOwner.lifecycle.removeObserver(
            thumbnailDownloader.viewLifecycleObserver
        )
    }
    override fun onDestroy() {
        super.onDestroy()
        lifecycle.removeObserver(
            thumbnailDownloader.fragmentLifecycleObserver
        )
    }
}

why do you write - requestHandler = object : Handler(Looper.getMainLooper())? it is not main looper. I think you need write: requestHandler = object : Handler() {…

I remember that was deprecated, and later on i changed library version and worked on it that way…

‘constructor Handler()’ is deprecated. Deprecated in Java
Default constructor associates this handler with the Looper for the current thread. If this thread does not have a looper, this handler won’t be able to receive messages so an exception is thrown.

after all when i rebuilt the app for paging 3 i used glide…

Hi! If someone will face this issue, here`s the solution.

Initialization of requestHandler is located in fun setup(), but in BNR`s solution there is no usages of setup() and tearDown()(or clearQueue()).

You need to use these functions in right places or simply delete them like in my example:

val fragmentLifecycleObserver: DefaultLifecycleObserver = object : DefaultLifecycleObserver {
    override fun onCreate(owner: LifecycleOwner) {
        Log.i(TAG, "Starting background thread")
        start()
        looper

    }

    override fun onDestroy(owner: LifecycleOwner) {
        Log.i(TAG, "Destroying background thread")
        quit()
    }

}
1 Like

How about val viewLifecycleObserver? There is function clearQueue() but when I am deleting whole val then I have error in PhotoGalleryFragment because there is unresolved reference: viewLifecycleObserver

val fragmentLifecycleObserver: DefaultLifecycleObserver =
        object : DefaultLifecycleObserver {

            override fun onCreate(owner: LifecycleOwner) {
                Log.i(TAG, "Starting background thread")
                start()
                looper
            }

            override fun onDestroy(owner: LifecycleOwner) {
                Log.i(TAG, "Destroying background thread")
                quit()
            }
        }

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

with Coroutines and Flows now this will be much different.

Yes but I am using LiveData instead of Flows as mentioned in BNR