imageView is null in recyclerview viewholder - android

I want to set an onClick Listener to the imageView present in the recycler view. But whenever I pass the imageview from onViewCreated() method from the fragment, it is still null and throws a NullPointerException when I invoke setOnClickListener.
These are kotlin classes.
class ShowDuesFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_show, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!, imageView)
recycler_view.apply {
layoutManager = LinearLayoutManager(activity!!.applicationContext)
setHasFixedSize(true)
this.adapter = adapter
}
}
}
class Adapter(private val list: List<Due>, private val manager: FragmentManager, private val imageView: ImageView?) : RecyclerView.Adapter<ViewHolder> {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context,
manager,
imageView
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
}
class ViewHolder(itemView: View, context: Context, manager: FragmentManager, imageView: ImageView? = null) :
RecyclerView.ViewHolder(itemView), DatePickerDialog.OnDateSetListener {
lateinit var item: Due
init {
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'true'
imageView?.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePicker = DatePickerFragment()
datePicker.show(manager, "DatePickerFragment")
}
}
context.toast((item as Due).name)
true
}
popup.show()
}
}
fun putData(due: Due) {
…
item = due
}
…
}
The log message I get is true and when I click on the imageView, it does not respond to my clicks. How do I successfully implement an onClickListener to my imageView?

You are never initializing the imageView. As you are using ( ?.) while setting onClickListner to ImageView, you are not getting any crash due to safe call. One better way to do this is:
class ShowDuesFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_show, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!)
recycler_view.apply {
layoutManager = LinearLayoutManager(activity!!.applicationContext)
setHasFixedSize(true)
this.adapter = adapter
}
}
}
class Adapter(private val list: List, private val manager: FragmentManager, private val imageView: ImageView?) : RecyclerView.Adapter {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context,
manager
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
}
class ViewHolder(itemView: View, context: Context, manager: FragmentManager) :
RecyclerView.ViewHolder(itemView), DatePickerDialog.OnDateSetListener {
lateinit var item: Due
init {
val imageView = itemView //Cast this to image view if required you can use itemView.findViewById for other //views.
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'true'
imageView?.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePicker = DatePickerFragment()
datePicker.show(manager, "DatePickerFragment")
}
}
context.toast((item as Due).name)
true
}
popup.show()
}
}
fun putData(due: Due) {
…
item = due
}
…
}

should work if you change onViewCreated to:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!, view.imageView) // Specify which imageView you mean, as there might be a bunch of these fragments or none, system doesn't know.
or, do the whole view.findViewById() thingy if you're not using the kotlinx thingies

The ViewHolder class should be inside the Adapter as an inner class. By doing so, imageView is not null.
Something like this:-
class Adapter (
private val list: List<Due>,
private val manager: FragmentManager
) : RecyclerView.Adapter<Adapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
inner class ViewHolder(itemView: View, context: Context) :
RecyclerView.ViewHolder(itemView) {
private lateinit var item: Due
init {
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'false'
itemView.dropdown_menu.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePickerFragment = DatePickerFragment()
datePickerFragment.show(manager, "date picker")
}
}
true
}
popup.show()
}
}
fun putData(due: Due) {
…
}
}
}
And we instantiate the Adapter like:-
val adapter = Adapter(mutableListOf(…), fragmentmanager!!)

Related

How to open a "Details" Fragment onclick of Recyclerview in Kotlin

I have home fragment and I want it to go to another fragment which will show its "details", which passes the data of the clicked recyclerview item to that "details" fragment.
When I click the article it will move to detail article which passes the data.
As for the code, here's the adapter:
class ArticleAdapter(private val articleList: ArrayList<Article>) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ItemArticleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val data = articleList[position]
holder.bind(data)
}
class MyViewHolder(private val binding: ItemArticleBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: Article) {
Glide.with(binding.root.context)
.load(data.img)
.into(binding.articleImage)
binding.articleTitle.text = data.title
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
}
}
}
override fun getItemCount(): Int {
return articleList.size
}
}
Here's my detailFragment
class DetailArticleFragment : Fragment() {
private var _binding: FragmentDetailArticleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDetailArticleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val intent = Intent(binding.root.context, DetailArticleFragment::class.java)
val article = intent.getParcelableExtra<Article>(DETAIL_ARTICLE) as Article
Glide.with(this)
.load(article.img)
.into(_binding!!.articleImage)
_binding!!.articleTitle.text = article.title
_binding!!.articleDescription.text = article.content
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val DETAIL_ARTICLE = "detail_article"
}
}
You need to pass click listener interface to adapter, for example:
typealias OnArticleClick = (article: Article) -> Unit
class ArticleAdapter(
private val articleList: ArrayList<Article>
) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
var onArticleClick: OnArticleClick? = null
...
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
onArticleClick?.invoke(article)
}
...
}
And init onArticleClick in your home fragment with RecyclerView:
adapter.apply {
onArticleClick = {
// show details fragment
}
}

android kotlin property does not change with passed method to adapter

i have defined a val in my fragment like so
private var selectionMode: Boolean = false
and a property to change it
fun enableSelectionMode() {
selectionMode = true
}
and i passed both to Adapter and want to call enableSelectionMode after clicking on each recycler item but nothing happen
here is my fragment:
class PostsFragment : Fragment() {
private val model: PostsViewModel by activityViewModels()
private var selectionMode: Boolean = false
fun enableSelectionMode() {
selectionMode = true
}
private lateinit var recyclerView: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_posts, container, false)
recyclerView = view.findViewById(R.id.posts_recycler)
recyclerView.layoutManager = LinearLayoutManager(view.context)
model.posts.observe(viewLifecycleOwner, { items ->
recyclerView.adapter = PostsAdapter(items, model, selectionMode,
::enableSelectionMode)
})
return view
}
}
and my adapter
class PostsAdapter(
private val posts: List<Post>,
private val model: PostsViewModel,
private val selectionMode: Boolean,
private val enableIt: () -> Unit
) :
RecyclerView.Adapter<PostsAdapter.ViewHolder>() {
companion object {
private const val TYPE_FROM = 0
private const val TYPE_TO = 1
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var postContent: TextView = itemView.findViewById(R.id.post_content)
var selectPostCheckBox: ImageView = itemView.findViewById(R.id.post_select_checkbox)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(
if (viewType == TYPE_TO) R.layout.to_post else R.layout.from_post,
parent,
false
)
return ViewHolder(view)
}
#SuppressLint("CheckResult")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.selectPostCheckBox.visibility = if (selectionMode) View.VISIBLE else View.GONE
holder.postContent.text = selectionMode.toString()
holder.selectPostCheckBox.setImageResource(R.drawable.cursor_color)
holder.itemView.setOnClickListener() {
enableIt()
notifyDataSetChanged()
}
}
override fun getItemCount() = posts.size
override fun getItemViewType(position: Int): Int {
return posts[position].selfPost
}
}

How to create custom arrayAdapter without context attribute

For a school project, I made a custom arrayadapter with a context, resources and items attribute. Today I received feedback from my teacher and he wants me to find a solution where I don't have a Context attribute because he doesnt like that I always need to specify the context.
This is my code:
class TalentListAdapter(
var mCtx: Context,
var resources: Int,
var items: MutableList<Talent>
) : ArrayAdapter<Talent>(mCtx, resources, items) {
lateinit var mItem: Talent
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater: LayoutInflater = LayoutInflater.from(mCtx)
val view: View = layoutInflater.inflate(resources, null)
mItem = items[position]
//set name of talent
val talentTextView: TextView = view.findViewById(R.id.talentName)
talentTextView.text = mItem.toString()
return view
}
}
He wants to get rid of the mCtx: Context attribute, but I don't find a solution for it. Any suggestions?
The adapter is created like this atm:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val listView: ListView = binding.talentList
// set custom adapter for talent_list
val adapter = TalentListAdapter(view.context, R.layout.talentlayout, binding.talentViewModel?.getTalents()?.value as MutableList<Talent>)
listView.adapter = adapter
}
Can you try this
class YourAdapter() : RecyclerView.Adapter<YourAdapter.ViewHolder>() {
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
var Data: List<YourResponse> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.your_layout, parent, false
)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return Data.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val getYourModel = Data[position]
val Binding = holder.view
Binding.yourTextView.apply{
text = getYourModel.yourField
}
.....
}
}
and pass Data from your Fragment or Activity (my example uses Retrofit2)
private var theList: List<YourResponse> = emptyList()
private val theAdapter =yourAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onUpdateData()
}
private fun onUpdateData() {
...
override fun onResponse(call: Call<YourResponse>, response: Response<YourResponse>) {
val body = response.body()?.results
if (response.isSuccessful && body != null) {
onUpdateDataSuccess(body)
} else {
// Something
}
}
...
}
private fun onUpdateDataSuccess(data: List<YourResponse>) {
theList = data
theAdapter.Data = data
}
...
You only need context while inflating so you should use parent.context instead of explicit context.
Parent.Context represent the Activity/Fragment where this recycler view is implemented.

Sharing recyclerview item ID with another fragment

Currently I have a recyclerview adapter that gets data from the database and displays it in the dashboard fragment. Once the item is clicked, I want to pass the item id to the details fragment to get the correct items information on the detailed view. How can I pass this ID to the detailed fragment?
Dashboard Fragment
class DashboardFragment : Fragment() {
private lateinit var viewModel: DashboardViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: DashboardFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.dashboard_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = NumberDatabase.getInstance(application).numberDatabaseDao
val viewModelFactory = DashboardViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val dashboardViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(DashboardViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.dashboardViewModel = dashboardViewModel
val adapter = CounterAdapter(CounterListener { nightId ->
dashboardViewModel.onCountClicked(nightId)
})
binding.counterList.adapter = adapter
dashboardViewModel.counts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
}
Adapter
class CounterAdapter (val clickListener: CounterListener): ListAdapter<Number, CounterAdapter.ViewHolder>(NightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemCounterBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: Number, clickListener: CounterListener) {
binding.night = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemCounterBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class CounterListener(val clickListener: (nightId: Long) -> Unit) {
fun onClick(night: Number) = clickListener(night.nightId)
}
In your recyclerview
class RecipeRecyclerAdapter(
mFragmentCommunication: FragmentCommunication
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mFragmentCommunication: FragmentCommunication? = null
init {
this.mFragmentCommunication =mFragmentCommunication
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
RecyclerView.ViewHolder {
view = LayoutInflater.from(parent.context)
.inflate(
com.broadcast.recipeslistapp.R.layout.layout_recipe_list_item,
parent,
false
)
return RecipeViewHolder(view)
}
}
override fun getItemCount(): Int {
if (mRecipes.isNullOrEmpty()) {
return 0
} else {
return mRecipes!!.size
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
mFragmentCommunication!!.PassNightId(nightId = 1) //value of nightId
}
}
interface FragmentCommunication {
fun PassNightId(nightId: Int)
}
Fragment A from where you want to send data :
class RecipeListActivity : BaseActivity(),
RecipeRecyclerAdapter.FragmentCommunication {
private lateinit var layoutManager: LinearLayoutManager
}
private fun initRecyclerView() {
mAdapter = RecipeRecyclerAdapter( this)
val itemDecorator = VerticalSpacingItemDecorator(30);
recipe_list.addItemDecoration(itemDecorator);
recipe_list.adapter = mAdapter
layoutManager = LinearLayoutManager(this);
recipe_list.layoutManager = layoutManager
}
override fun PassNightId(nightId: Int) {
val testFragment = TestFragment()
val bundle = Bundle()
bundle.putString("nightId", "$nightId")
testFragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.containerId,testFragment).commit()
}
}
Fragment B where you want to getData :
class TestFragment : Fragment()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nightId =arguments?.getString("nightId")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.test_fragment,container,false)
}
}
This is how you can send data from on fragment to another fragment.

ViewPager2 | View.ClickListener not called

I use new android widget ViewPager2 version 1.0.0-alpha03 and when I set click listener on it method onClick() not called.
My Actvity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.add(R.id.fragmentContent, SecondFragment.newInstance(), SecondFragment.TAG)
.addToBackStack(SecondFragment.TAG)
.commit()
}
}
My Fragment:
class SecondFragment : Fragment() {
companion object {
val TAG = SecondFragment::class.java.canonicalName
fun newInstance(): SecondFragment = SecondFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewPager2.setOnClickListener {
Log.d("logi", "click : ")
}
}
}
My layout xml file:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Any assumption or workaround?
I found a solution! ViewPager2 contains RecyclerView and we must work with it as RecyclerView. I created RecyclerView.Adapter and set the ClickListener on itemView into the constructor RecyclerView.ViewHolder and VOILA!!!
in Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewPager2.adapter = ViewPager2Adapter {
Log.d("logi", "clicked at : $it")
}
}
RecyclerView adapter:
class ViewPager2Adapter(private val itemClickListener: (Int) -> (Unit)) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val items = mutableListOf<Any>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// bind your items
}
private inner class ItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
Log.d("logi", "Click!") // WORKS!!!
itemClickListener(adapterPosition)
}
}
}
}
Another approach can be like below:
class MyAdapter(
private val items: List<Any>,
private val onItemClickListener: OnItemClickListener
) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = parent.inflate(R.layout.item_details_view_pager)
return MyViewHolder(itemView)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.imageView.apply {
setOnClickListener {
onItemClickListener.onItemClick(position)
}
...
}
}
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.some_image_view)
}
}
The constructor parameter onItemClickListener could be a lambda as well, but it's a simple interface for me:
interface OnItemClickListener {
fun onItemClick(position: Int)
}

Categories

Resources