1

The function Navigation.findNavController() need a Activity parmater in Code A, but I can only get a context via itemView.context.

How can I get Activity from RecyclerView.ViewHolder(itemView) in Android Studio ?

At present, I have to pass private val mActivity: Activity from Code B to Code A, it works, but I think it's not a good way.

Added content:

To Miriana Itani: Thanks!

Your code works well. I paste FrameLayout xml file, you can see Code C

I'm very strange how the code Navigation.findNavController(temView)... find the NavController control? Could you tell me?

You know it's easy to understand to use the code Navigation.findNavController(mActivity, R.id.fragment_container), because the ID of NavController is fragment_container, and it's unique.

Code A

class VideoListAdapter (private val videoList: List<File>, private val mActivity: Activity) : RecyclerView.Adapter<VideoListAdapter.MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoListAdapter.MyViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.layout_fragment_video_list_recycleview, parent, false)
        return MyViewHolder(v)
    }

    override fun onBindViewHolder(holder: VideoListAdapter.MyViewHolder, position: Int) {
        holder.bindItems(videoList[position])
    }

    override fun getItemCount(): Int {
        return videoList.size
    }

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(aFile: File) {           
            itemView.tvVideoFileName.text=aFile.name
            itemView.btnPlay.setOnClickListener {
                Navigation.findNavController(mActivity, R.id.fragment_container).navigate(
                    UIFragmentVideoListDirections.aVideoList2Video((aFile.absolutePath)) )

            }
        }
    }

}

Code B

class UIFragmentVideoList : Fragment() {

    private lateinit var listFile: MutableList<File>

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.layout_fragment_video_list, container, false)


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val outputDirectoryOfVideo = getVideoOutputDirectory(requireContext())
        listFile =outputDirectoryOfVideo.listFiles{file -> VIDEO_EXTENSION_WHITELIST.contains(file.extension.toUpperCase())}
                   .sorted().reversed().toMutableList()

        videoRecyclerView.layoutManager =  LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
        val aVideoListAdapter=VideoListAdapter(listFile,requireActivity())
        videoRecyclerView.adapter=aVideoListAdapter

    }

}

Code C

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    >

    <fragment
            android:id="@+id/fragment_container"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph" />

</FrameLayout>
HelloCW
  • 843
  • 22
  • 125
  • 310
  • 1
    Just move click handling logic to your fragment by using an interface,then call `Navigation.findNavController()` in your `UIFragmentVideoList` – Mohammad Sianaki Nov 07 '19 at 19:12

3 Answers3

2

As a matter of fact, I don't think you need to pass the activity for the NavController to work:

I think a better approach would be the following:

Navigation.findNavController(temView).navigate(
            UIFragmentVideoListDirections.aVideoList2Video((aFile.absolutePath))
        )

Please try and let me know.

Added Answer:

NavHostFragment — implementation of NavHost for creating fragment destinations. It has its own NavController and navigation graph. Typically you would have one NavHostFragment per activity.

Navigation — helper class for obtaining NavController instance and for connecting navigation to UI events like a button click.

NavigationUI — class for connecting app navigation patterns like drawer, bottom navigation or actionbar with NavController.

Miriana Itani
  • 865
  • 9
  • 25
0

You can retrieve the Activity from the View's Context like this:

tailrec fun Context.findActivity(): Activity {
    if (this is Activity) {
        @Suppress("UNCHECKED_CAST")
        return this
    } else {
        if (this is ContextWrapper) {
            return this.baseContext.findActivity()
        }
        throw IllegalStateException("The context chain does not contain Activity!")
    }
}

Now you can do

val activity = view.context.findActivity()
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • This encourages coupling everything to Activity instead of using interfaces, which will make unit testing harder. – charles-allen Nov 08 '19 at 00:40
  • 1
    But it does answer the question `How can I get Activity from RecyclerView.ViewHolder(itemView)` which was my original intention. If we were to handle it as normally would, you can of course use interfaces or functional types like in https://stackoverflow.com/a/51867792/2413303 – EpicPandaForce Nov 08 '19 at 01:12
0

you can pass a function to the adapter in the fragment. this way you dont have to pass the activity

class VideoListAdapter (private val videoList: List<File>, val btnPlayClick: (File) -> Unit) : RecyclerView.Adapter<VideoListAdapter.MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoListAdapter.MyViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.layout_fragment_video_list_recycleview, parent, false)
        return MyViewHolder(v)
    }

    override fun onBindViewHolder(holder: VideoListAdapter.MyViewHolder, position: Int) {
        holder.bindItems(videoList[position], btnPlayClicked )
    }

    override fun getItemCount(): Int {
        return videoList.size
    }

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(aFile: File, click -> (File) -> Unit) {           
            itemView.tvVideoFileName.text=aFile.name
            itemView.btnPlay.setOnClickListener {
                 click(aFile)
            }
        }
    }
}

and in your fragment buil the adapter like this

val aVideoListAdapter = VideoListAdapter(listFile) { myFile ->
    Navigation.findNavController(activity!!, R.id.fragment_container).navigate(
                    UIFragmentVideoListDirections.aVideoList2Video((myFile.absolutePath)) 
    )
}
Mohsen
  • 1,246
  • 9
  • 22
  • 1
    I like to make a `typealias` in the ViewHolder file to improve the readability: `typealias OnItemClickListener = (File) -> Unit)`. Then you can change the constructor param to `val itemClickListener: OnItemClickListener` – charles-allen Nov 08 '19 at 00:33