Reputation: 374
I have two fragments: (1) Library Fragment, (2) Book Fragment
The library fragments displays all available books via a RecyclerView. The user can tab on each of the RecyclerView Items, which will set the LiveData to the corresponding book. At the same time the Book Fragment will be opened and the contents of that book will be shown.
I set up an onClickListener in the ViewHolder class inside the RecyclerView.Adapter of the Library Fragment. So, when an item is clicked, the livedata is being set and then navigated to the Book fragment via Navigation Component. The Book Fragment has an observer on the Live Data and shows it.
As you can see in the following code, I am passing viewmodel instance to the adapter, which is not correct....? Or is it? How should this be done?
Library Fragment Code Snippet
class LibraryFragment : Fragment() {
[...]
private val model: SharedViewModel by activityViewModels()
private lateinit var gridlayoutManager: GridLayoutManager
private lateinit var thumbnailAdapter: ThumbnailAdapter
private lateinit var thumbnailRecyclerView: RecyclerView
private lateinit var selectedFolder: Uri
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = layoutInflater.inflate(R.layout.fragment_thumbnail_viewer, container, false)
initRecyclerView(view)
view.allFolderPicker.setOnClickListener {
pickFolder()
}
view.switchFragment.setOnClickListener {
findNavController().navigate(R.id.action_allFoldersFragment_to_oneFolderFragment)
}
return view
}
private fun initRecyclerView(view: View) {
thumbnailRecyclerView = view.findViewById(R.id.thumbnail_recycler_view)
gridlayoutManager =
GridLayoutManager(requireContext(), 3, LinearLayoutManager.VERTICAL, false)
thumbnailRecyclerView.layoutManager = gridlayoutManager
thumbnailAdapter = ThumbnailAdapter(model)
thumbnailAdapter.setThumbnailList(listOf())
thumbnailRecyclerView.adapter = thumbnailAdapter
}
[...]
}
Adapter Class Code Snippet
class ThumbnailAdapter(model: SharedViewModel) :
RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {
private var thumbnailList = listOf<Pair<String, File>>()
private val myModel = model
inner class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val thumbnail = view.thumbnail
private val title = view.title
fun bind(item: Pair<String, File>) {
val titleToSet = item.first
val bitmapToSet = Util.uriToBitmap(item.second)
val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
thumbnail.setImageBitmap(resizedBitmapToSet)
title.text = titleToSet
itemView.setOnClickListener {
val folderWithImages = thumbnailList[adapterPosition].second.parentFile
folderWithImages?.let {
myModel.setLibraryFolderList(it)
itemView.findNavController()
.navigate(R.id.action_allFoldersFragment_to_oneFolderFragment)
}
}
}
}
[...]
}
Book Fragment Code Snippet
[...]
class BookViewFragment : Fragment() {
private lateinit var viewPager: ViewPager2
private lateinit var viewPagerAdapter: ViewPager2Adapter
private val model: SharedViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_image_viewer, container, false)
initRecyclerView(view)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selectedBookFile.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
viewPagerAdapter.setImageList(getImageList(it))
viewPagerAdapter.notifyDataSetChanged()
})
}
private fun initRecyclerView(view: View) {
viewPager = view.findViewById(R.id.main_image)
viewPagerAdapter = ViewPager2Adapter()
viewPagerAdapter.setImageList(listOf())
viewPager.adapter = viewPagerAdapter
}
[...]
}
Upvotes: 3
Views: 11539
Reputation: 2417
I know it's too late to answer. But I hope it will help other developers searching for a resolution on a similar issue.
Take a look at LiveAdapter.
You just need to add the latest dependency in Gradle.
dependencies {
implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.4'
// kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects
}
and bind adapter with your RecyclerView
// Kotlin sample
LiveAdapter(
data = liveListOfItems,
lifecycleOwner = this@MainActivity,
variable = BR.item )
.map<Header, ItemHeaderBinding>(R.layout.item_header) {
onBind{
}
onClick{
}
areContentsTheSame { old: Header, new: Header ->
return@areContentsTheSame old.text == new.text
}
areItemSame { old: Header, new: Header ->
return@areContentsTheSame old.text == new.text
}
}
.map<Point, ItemPointBinding>(R.layout.item_point) {
onBind{
}
onClick{
}
areContentsTheSame { old: Point, new: Point ->
return@areContentsTheSame old.id == new.id
}
areItemSame { old: Header, new: Header ->
return@areContentsTheSame old.text == new.text
}
}
.into(recyclerview)
That's it. No need to write extra code for adapter implementation, observe LiveData and notify the adapter.
Also, have DiffUtil implemented so only changed items will be updated on RecyclerView instead of all.
Upvotes: -1
Reputation: 2990
Simply use your Activity
reference inside Adapter class instead of viewLifecycleOwner
and it will work
class ThumbnailAdapter(
private val activity: FragmentActivity
) : RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {
// ... Other functions
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selectedBookFile.observe(activity, androidx.lifecycle.Observer {
// Your code goes here
})
}
// ... Other functions
}
Upvotes: 0
Reputation: 5247
You shouldn't do it that way, here is a course about RecyclerView that goes from displaying to handle clickListener.
Here is how they define it:
While the
ViewHolder
is a great place to listen for clicks, it's not usually the right place to handle them. You should usually handle clicks in theViewModel
, because theViewModel
has access to the data and logic for determining what needs to happen in response to the click.
So you should be doing this:
Adapter:
class ThumbnailAdapter(model: SharedViewModel, val clickListener: ThumbnailListener) :
RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {
private var thumbnailList = listOf<Pair<String, File>>()
private val myModel = model
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val item = getItem(position)
holder.itemView.setOnClickListener {
listener.onClick(item)
}
holder.bind(marsProperty)
}
inner class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val thumbnail = view.thumbnail
private val title = view.title
fun bind(item: Pair<String, File>) {
val titleToSet = item.first
val bitmapToSet = Util.uriToBitmap(item.second)
val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
thumbnail.setImageBitmap(resizedBitmapToSet)
title.text = titleToSet
}
}
class ThumbnailListener(val clickListener: (libraryId: Long) -> Unit) {
fun onClick(val library: Library) = clickListener(library.id)
}
[...]
}
ViewModel:
private val _navigateToBook = MutableLiveData<Long>()
val navigateToBook: LiveData<Long>()
get() = _navigateToBook
fun onLibraryClicked(libraryId: Long) {
// your stuff
_navigateToBook.value = libraryId
}
Fragment:
thumbnailAdapter = ThumbnailAdapter(model, ThumbnailListener { libraryId ->
viewModel.onLibraryClicked(libraryId)
})
viewModel.navigateToBook.observe(viewLifecycleOwner, Observer { book ->
book?.let {
this.findNavController().navigate(YOUR_DESTINATION)
}
})
xml item file:
<data>
<variable
name="listener"
type="Your.Package.To.ThumbnailListener" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp"
android:onClick="@{() -> listener.onClick(property)}">
...
</FrameLayout>
Adapter:
fun bind(item: Pair<String, File>, listener: ThumbnailListener) {
val titleToSet = item.first
val bitmapToSet = Util.uriToBitmap(item.second)
val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
thumbnail.setImageBitmap(resizedBitmapToSet)
title.text = titleToSet
itemView.clickListener = listener
}
Upvotes: 4