0

This project is a recycler view fragment that gets images from flickr and display it. I used loopers and handlers to communicate between the recycler fragment and the downloading thumbnail fragment... I'm using Android Studio Bumblebee...

I’m facing an error with requestHandler… Maybe i’m not seeing a specific issue. but i checked the code several times 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 from another fragment

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
        }

as far as i know, i Initialized it here:

@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)
                }
            }
        }
    }

what did i missed to get this error?

here is the file code:

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)
        })
    }
}

PHOTOGalleryFragment:

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> //it should be PhotoHolder not Type or Handler
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.hala_atamleh
            )?: 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
        )
    }
}
Saher Al-Sous
  • 517
  • 4
  • 15
  • You could use `if(::requestHandler.isInitalized)` before accessing it – Ananiya Jemberu May 24 '21 at 17:05
  • I didn't know how to do it... if it is not initialized what should i do? – Saher Al-Sous May 24 '21 at 17:12
  • You could simple enclose it like ```if(::requestHandler.isInitalized) {requestHandler.obtainMessage(MESSAGE_DOWNLOAD, target) .sendToTarget() }``` – Ananiya Jemberu May 24 '21 at 17:14
  • 2
    Any property for which you have to check `isInitialized` shouldn't be a `lateinit` in the first place and should be nullable instead. But in this case, I'm not sure if it's appropriate. I haven't worked with subclassing HandlerThread. – Tenfour04 May 24 '21 at 17:15
  • when i do this: if(::requestHandler.isInitalized) { requestHandler.obtainMessage(MESSAGE_DOWNLOAD, target) .sendToTarget() } } i get --> Unresolved reference: isInitalized – Saher Al-Sous May 24 '21 at 17:18
  • 1
    Oh `isInitialized` – Ananiya Jemberu May 24 '21 at 17:20
  • Plus see what @Tenfour04 recommends – Ananiya Jemberu May 24 '21 at 17:22
  • That solution will just cause it to never queue your first message. The looper of your HandlerThread isn't ready by the time you're calling `queueThumbnail` for the first time. But what's the point in subclassing HandlerThread if you're still using the main Looper for your queue? – Tenfour04 May 24 '21 at 17:22
  • It worked in both ways now... actually i'm following the book that is all... – Saher Al-Sous May 24 '21 at 17:28
  • @Tenfour04 if you please... I need one more help... can you? note sure to post the code because I couldn't know where is the error... the imageview in the recyclerview is not updating to the images downloaded... despite that the code is as how "it should be"... is it okey if i post the git link or edit this question and add the codes here? (would be many...) – Saher Al-Sous May 24 '21 at 18:27
  • I don't have experience using HandlerThread, so I'm just guessing. First, it doesn't make sense to subclass HandlerThread, and then not use it for anything. The Handler you're creating for `requestHandler` is simply using the Main Looper, so you're doing nothing with the thread. Second, probably what is happening is that you instantiate this class on the Main thread, and `onLooperPrepared` might not get called until a little while later, after all main Looper Handler queues are clear (not sure, guessing), so your `handler` isn't created by the time you try to use `queueThumbnail`. – Tenfour04 May 24 '21 at 18:32
  • Try changing this line `requestHandler = object : Handler(Looper.getMainLooper()){` to `requestHandler = object : Handler(){` so the handler is created using the intended thread instead of the main thread. – Tenfour04 May 24 '21 at 18:43
  • @Tenfour04 i added the code of main fragment... i did remove the looper, it didn't change... i noticed that the handler follow the recyclerview with the links. means it gives the needed link for the new holder to download the image... but the downloaded image ( i suppose that it downloads) doesn't load in the UI – Saher Al-Sous May 24 '21 at 18:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/232826/discussion-between-saher-al-sous-and-tenfour04). – Saher Al-Sous May 24 '21 at 18:54

1 Answers1

0

I solved it in 2 steps

1- made requesthandler nullable... thanks to @Tenfour04

2- made the gallery this way

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.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

private const val TAG = "PhotoGalleryFragment"

class PhotoGalleryFragment : Fragment() {

    private lateinit var photoGalleryViewModel: PhotoGalleryViewModel
    private lateinit var photoRecyclerView: RecyclerView
    private lateinit var thumbnailDownloader: ThumbnailDownloader<PhotoHolder>

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

        retainInstance = true

        photoGalleryViewModel =
                ViewModelProviders.of(this).get(PhotoGalleryViewModel::class.java)

        val responseHandler = Handler()
        thumbnailDownloader =
                ThumbnailDownloader(responseHandler) { photoHolder, bitmap ->
                    val drawable = BitmapDrawable(resources, bitmap)
                    photoHolder.bindDrawable(drawable)
                }
        lifecycle.addObserver(thumbnailDownloader.fragmentLifecycleObserver)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        viewLifecycleOwner.lifecycle.addObserver(
            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 view model $galleryItems")
                photoRecyclerView.adapter = PhotoAdapter(galleryItems)
            })
    }

    override fun onDestroyView() {
        super.onDestroyView()
        thumbnailDownloader.clearQueue()
        viewLifecycleOwner.lifecycle.removeObserver(
            thumbnailDownloader.viewLifecycleObserver
        )
    }

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

    private class PhotoHolder(private val itemImageView: ImageView) : RecyclerView.ViewHolder(itemImageView) {

        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 getItemCount(): Int = galleryItems.size

        override fun onBindViewHolder(holder: PhotoHolder, position: Int) {
            val galleryItem = galleryItems[position]
            val placeholder: Drawable = ContextCompat.getDrawable(
                requireContext(),
                R.drawable.bill_up_close
            ) ?: ColorDrawable()
            holder.bindDrawable(placeholder)
            thumbnailDownloader.queueThumbnail(holder, galleryItem.url)
        }
    }

    companion object {
        fun newInstance() = PhotoGalleryFragment()
    }
}
Saher Al-Sous
  • 517
  • 4
  • 15