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)
}
Related
I make an api call to get a list, in the logcat I can see all the groups that came back from the api but the Recylerview doesn't show my items.
ViewModel
class MyGroupScreenViewMode(private val context: Context, private val codagramApi: CodagramApi) : ViewModel() {
#ExperimentalCoroutinesApi
private val myGroups = MutableLiveData<List<Group>>()
#ExperimentalCoroutinesApi
fun getMyGroups(): LiveData<List<Group>> = myGroups
init {
viewModelScope.launch(Dispatchers.IO){
val response = codagramApi.getAllGroups()
updateUi(response.groupList)
}
}
#ExperimentalCoroutinesApi
private fun updateUi(update: List<Group>){
viewModelScope.launch(Dispatchers.Main){
myGroups.value = update
}
}
and the fragment
class MyGroupScreen : Fragment() {
private val myGroupsAdapter: GroupAdapter by inject()
private lateinit var bindingGroup: FragmentThirdBinding
#ExperimentalCoroutinesApi
private val viewModel: MyGroupScreenViewMode by inject()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
bindingGroup = FragmentThirdBinding.inflate(layoutInflater, container, false)
return bindingGroup.root
}
#ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindingGroup.myGroups.apply {
adapter = this#MyGroupScreen.myGroupsAdapter
layoutManager = LinearLayoutManager(context)
}
myGroupsAdapter.currentList
bindingGroup.fabAdd.setOnClickListener {
it.findNavController().navigate(MyGroupScreenDirections.actionFirstViewToGroupscreen())
}
bindgetmyGroupToLiveData()
}
#ExperimentalCoroutinesApi
private fun bindgetmyGroupToLiveData() {
viewModel.getMyGroups().observe(viewLifecycleOwner, androidx.lifecycle.Observer {
myGroupsAdapter.submitList(it)
})
}
and the adapter
class GroupAdapter : ListAdapter<Group,GroupScreenViewHolder>(object: DiffUtil.ItemCallback<Group>(){
override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean = oldItem.creator?.id == newItem.creator?.id
override fun areContentsTheSame(oldItem: Group, newItem: Group): Boolean =
oldItem == newItem
}) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupScreenViewHolder {
return GroupScreenViewHolder(
GroupscreenMygroupsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onBindViewHolder(holder: GroupScreenViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
class GroupScreenViewHolder(private val binding: GroupscreenMygroupsBinding) :
RecyclerView.ViewHolder(binding.root) {
#RequiresApi(Build.VERSION_CODES.N)
fun bind(postData: Group) {
binding.textView.text = postData.id.toString()
}
}
Check if data is in the list, then assign the list to adapter and call adapter.notifyDataSetChanged(),
I have a Data Repository that saved data & fetches data, this is passed into a ViewModel as constructor. I have methods in a ViewModel that fetches and saves data.
I have a button that I click for each row (Item in a RecyclerView list) this saves data using the ViewModel.
I have found that I can directly call a ViewModel initialised it into the constructor, I checked the Google Android examples & this part is not covered.
Something like this below:
Copied from: Databinding Recyclerview and onClick
private ExampleViewModel exampleViewModel;
public ExampleListAdapter(Context context, List<Model> models) {
this.context = context;
this.models = models;
// ...
exampleViewModel = ViewModelProviders.of((FragmentActivity) context).get(ExampleViewModel.class);
}
But then, I could also call a ViewModel by passing a ViewModel object from the Activity alongside with the context.
So what the proper way of calling a ViewModel?
This is your pojo class
data class Item(val id: Int)
This is your adapter.
class Adapter : RecyclerView.Adapter<Adapter.ViewHolder>() {
var items: List<Item> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
var callback: Callback? = null
override fun getItemCount(): Int {
return items.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.simple_textview, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.itemView.setOnClickListener {
callback?.onItemClicked(item)
}
holder.bindItem(item)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItem(item: Item) {
// Fill layout
}
}
interface Callback {
fun onItemClicked(item: Item)
}
}
This is your viewModel class.
class MyViewModel : ViewModel(), Adapter.Callback {
override fun onItemClicked(item: Item) {
}
}
And this is your fragment.
class MyFragment : Fragment() {
private val adapter = Adapter()
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = ViewModelProviders.of(activity!!).get(MyViewModel::class.java)
adapter.callback = myViewModel
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter.items = listOf(
Item(1),
Item(2)
)
//Setup recyclerView etc.
}
}
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.
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!!)
I have a fragment with RecyclerAdapter inside it. I want to initialize the adapter in the onCreateView method but it throws the error of "Type mismatch. Required : Context , Found : FragmentActivity" in this statement
I have no idea why the first one showed this error and the second one did not contains compile time error.
Error shown
recyclerView!!.adapter = RestaurantMenuAdapter(activity)
No Error shown
recyclerView!!.layoutManager = LinearLayoutManager(activity)
Fragment.kt
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_restaurant_menu, container, false)
recyclerView = view.findViewById(R.id.restaurant_container)
recyclerView!!.adapter = RestaurantMenuAdapter(activity)
recyclerView!!.layoutManager = LinearLayoutManager(activity)
RecyclerAdapter.kt
class RestaurantMenuAdapter (val context : Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.item_menu1, parent, false)) {
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 10
}
}
Change this to-:
recyclerView!!.adapter = RestaurantMenuAdapter(activity)
To-:
recyclerView!!.adapter = RestaurantMenuAdapter(activity.applicationContext)
recyclerView!!.adapter = RestaurantMenuAdapter(this)
To
recyclerView!!.adapter = RestaurantMenuAdapter(this.requireActivity())
Keep the adapter as it and just use "activity!!" where you are initializing adapter.
recyclerView.adapter = RestaurantMenuAdapter(activity!!)
Your adapter will remain same.
class RestaurantMenuAdapter (val context : Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.item_menu1, parent, false)) {
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 10
}
}
Change in Recycler Adapter
from Context to Activity.
class RestaurantMenuAdapter (val context : Activity) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.item_menu1, parent, false)) {
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 10
}
}