How to implement implicit intent to RecyclerView on Kotlin - android

I want to add onClick on my RecyclerView item images for navigating to Browser but it requires context how can I access the context
class MovieAdapter(val movies: List<Data>, val activity: Activity) : RecyclerView.Adapter<MovieAdapter.ViewHolder>(){
class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
val txtTitle = itemView.findViewById<TextView>(R.id.txtName)
val txtYear = itemView.findViewById<TextView>(R.id.txtPrice)
val image = itemView.findViewById<ImageView>(R.id.image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_movie,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return movies.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val movie:Data=movies[position]
holder.txtTitle.setText(movie.animeName)
holder.txtYear.setText(movie.animeİd.toString())
Glide.with(activity).load(movie.animeİmg).into(holder.image)
holder.image.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW,Uri.parse("https://www.google.com/search?q="+holder.txtTitle))
startActivity(intent)
}
}
}

Pass Context instead of Activity.
class MovieAdapter(val movies: List<Data>, val context: Context) RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
{
So in my Fragment I could just call it like
MovieAdapter(requireActivity().baseContext)
I think passing Context is enough for Adapters.

You can add a listener to your adapter. You can listen to this listener from your adapter. I will put a code snippet below to give you an idea.
MovieAdapter(val movies: List<Data>, val fooListener: (Sting) -> Unit)
//When you create the adapter instance(Activity)
MovieAdapter(list){ url ->
val intent = Intent(Intent.ACTION_VIEW,Uri.parse(url))
startActivity(intent)
}

You already have context in form of activity. You can use that.
activity.startActivity(intent)
You can also get a Context from any View using view.context, for example holder.image.context

Related

Please tell me how to transfer the ID (position) of the view element on recyclerview to another class

Please tell me how to transfer the ID (position) of the view element on recyclerview to another class?
class CardAdapter : RecyclerView.Adapter<CardAdapter.CardViewHolder>(), View.OnClickListener {
private var cardList = ArrayList<Card>()
private lateinit var card: Card
class CardViewHolder(
val binding: FragmentCardBinding
) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = FragmentCardBinding.inflate(inflater, parent, false)
return CardViewHolder(binding)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
card = cardList[position]
..
}
override fun getItemCount(): Int = cardList.size
override fun onClick(v: View) {
when (v.id) {
R.id.root_card_template -> {
val intent = Intent(v.context, ProductActivity::class.java)
// need put id into ProductActivty
intent.putExtra("item", card.id)
v.context.startActivity(intent)
}
}
}
At the moment, it only passes the ID of the last generated element. For some reason there is almost no information on the Internet on this score
Just create an interface and implement that in the calling activity. While creating an instance of your adapter inside the activity, pass that interface along and on the click event of the view in the adapter class, call the interface's method with the data that you want to pass back to the activity.
interface OnItemClickListener{
fun onClick(pos: Int)
}
class YourActivity: AppCompatActivity(), OnItemClickListener {
override fun onStart() {
super.onStart()
//Create an instance of your adapter and pass the interface.
// Here #this context is being passed as Activity is implementing the interface.
val cardListAdapter = CardListAdapter(this)
}
override fun onClick(pos: Int) {
//Add your logic
}
}
// Then in your adapter class
class YourAdapter(private val itemClickListener: OnItemClickListener) :
RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {
//Your code
override fun onBindViewHolder(holder: CardListAdapter.CardViewHolder, position: Int) {
yourView.setOnClickListener {
itemClickListener.onClick(position)
}
}
}
first you need to creat an interface
interface OnItemListener {
fun onItemSelect(position: Int)
}
then in your class that calls the recyclerview adapter, pass it to your recyclerView Adapter like this
var cardAdapter = CardAdapter(object :
OnItemListener {
override fun onItemSelect(position: Int) {
// you can handle your data here
// your position that you passed comes here
}
})
var layoutManager = GridLayoutManager(context, 2)
yourRecyclerViewId.adapter = cardAdapter
yourRecyclerViewId.layoutManager = layoutManager
finally in your adapter do like this
class CardAdapter(
private val onItemListener: OnItemListener
)
: RecyclerView.Adapter<CardAdapter.CardViewHolder>(), View.OnClickListener {
private var cardList = ArrayList<Card>()
private lateinit var card: Card
and in your on click event in the adapter call it as below:
onItemListener.onItemSelect(yourPosition)

How to use viewmodel in recyclerview adapter

I am creating Recyclerview using MVVM and data binding. Now I need to perform some network operation in Recycler view adapter. So how can we create ViewModel and Live data for adapter. How can adapter observe live data.
I have create ViewModel using activity context and but not working proper
class CartAdapter(cartList: ArrayList<ProductData>, context: BaseActivity) :
RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
private val itemList = cartList
private val activity = context
private var viewModel: CartAdapterViewModel =
ViewModelProvider(context).get(CartAdapterViewModel::class.java)
init {
initObserver()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.cart_item_layout, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = itemList[position]
holder.setData(item)
}
override fun getItemCount(): Int {
return itemList.size
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binding: CartItemLayoutBinding? = DataBindingUtil.bind(itemView)
fun setData(model: ProductData) {
binding?.item = model
}
}
private fun initObserver() {
viewModel.statusResponse.observe(activity, {
activity.hideLoader()
})
viewModel.serverError.observe(activity, {
activity.hideLoader()
})
}
}
You should not create a separate ViewModel for adapter.
The Classic way:
The adapter should expose an interface whose implementation would later handle e.g. clicks on an item in the RecyclerView.
class CartAdapter(
cartList: ArrayList<ProductData>,
private val itemClickListener: ItemClickListener // This is the interface implementation
// that will be provided for an item click in this example
) : RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
interface ItemClickListener {
fun onItemClick(position: Int)
}
override fun onBindViewHolder(holder: ScanResultViewHolder, position: Int) {
holder.binding.root.setOnClickListener {
itemClickListener.onItemClick(position)
}
}
// this function would be useful for retrieving an item from the recyclerview
fun getItemAt(position: Int): ProductData = itemList[position]
...
}
Later on when instantiating the CartAdapter in Your Activity or Fragment You would have to provide that interface implementation:
private val cartAdapter: CartAdapter = CartAdapter(
cartList,
object : CartAdapter.ItemClickListener {
override fun onItemClick(position: Int) {
// this function will handle the item click on a provided position
doSomethingWithARecyclerViewItemFrom(position)
}
}
)
private fun doSomethingWithARecyclerViewItemFrom(position: Int) {
// get the adapter item from the position
val item = cartAdapter.getItemAt(position)
// later on You can use that item to make something usefull with Your ViewModel of an activity/fragment
...
}
This way the Adapter doesn't have to have any ViewModels - the corresponding actions on RecyclerView items can be handled by the Activity view model.
In my example this action is an item click but for a more specific action, You would have to update Your question with those details.
The more compact way:
You can implement the same functionality as above using even more compact and neat way by using function types:
class CartAdapter(
cartList: ArrayList<ProductData>,
private val itemClickListener: (productData: ProductData) -> Unit // notice here
) : RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
override fun onBindViewHolder(holder: ScanResultViewHolder, position: Int) {
holder.binding.root.setOnClickListener {
itemClickListener(position) // slight change here also
}
}
}
My suggestion is using constructor to pass viewModel instance.
Without concerns of unhandled instance scope problem anyway.
Have a happy day.

Kotlin Recyclerview ItemClick to new Intent

I have a problem. I can print a toast message when I click on any item in Recyclerview. But I want to switch to a different activity when clicked. Could you please help me in a very specific way? It is a very important project for me. The code blocks I wrote are as follows. Thanks in advance .
CustomersAdapter
class CustomersAdapter(private val companynameArray:ArrayList,private val companyoffArray:ArrayList, private val companyPhoneArray:ArrayList):
RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
class CustomerHolder(view: View) : RecyclerView.ViewHolder(view) {
var recycleCompanyName: TextView?=null
var recycleOffName: TextView?=null
var recycleOffPhone: TextView?=null
init {
recycleCompanyName=view.findViewById(R.id.recycleCompanyName)
recycleOffName=view.findViewById(R.id.recycleOffName)
recycleOffPhone=view.findViewById(R.id.recycleOffPhone)
itemView.setOnClickListener {
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerHolder {
val inflater= LayoutInflater.from(parent.context)
val view= inflater.inflate(R.layout.recycler_view_customer_row,parent,false)
return CustomersAdapter.CustomerHolder(view)
}
override fun getItemCount(): Int {
return companynameArray.size
}
override fun onBindViewHolder(holder: CustomerHolder, position: Int) {
holder.recycleCompanyName?.text="Şirket Adı:"+companynameArray[position]
holder.recycleOffName?.text="Şirket Yetkilisi :"+companyoffArray[position]
holder.recycleOffPhone?.text="İrtibat No : "+companyPhoneArray[position]
}
}
I did not specify the activity I want to go to. I would appreciate it if you could help as a method.
If it helps, I write the codes of the client class here.
class Customers : AppCompatActivity()
{
private lateinit var auth: FirebaseAuth
private lateinit var db: FirebaseFirestore
var companynameFB:ArrayList<String> = ArrayList()
var companyoffFB:ArrayList<String> = ArrayList()
var companyphoneFB:ArrayList<String> = ArrayList()
var adapter :CustomersAdapter?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_customers)
auth= FirebaseAuth.getInstance()
db= FirebaseFirestore.getInstance()
getDataFromFirestore()
var layoutManager= LinearLayoutManager(this)
recyclerView.layoutManager=layoutManager
adapter= CustomersAdapter(companynameFB,companyoffFB,companyphoneFB)
recyclerView.adapter=adapter
}
fun getDataFromFirestore(){
db.collection("Customers")
.addSnapshotListener { snapshot, exception ->
if(exception!=null){
Toast.makeText(applicationContext,exception.localizedMessage.toString(),Toast.LENGTH_LONG).show()
}else{
if(snapshot!=null){
if (!snapshot.isEmpty){
companynameFB.clear()
companyoffFB.clear()
companyphoneFB.clear()
val documents=snapshot.documents
for (document in documents){
val compname=document.get("compname") as String
val compoff=document.get("compoff") as String
val compphone=document.get("compphone") as String
val user=document.get("user") as String
val timeStamp=document.get("date") as Timestamp
val date=timeStamp.toDate()
companynameFB.add(compname)
companyoffFB.add(compoff)
companyphoneFB.add(compphone)
adapter!!.notifyDataSetChanged()
}
}
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val menuInflater=getMenuInflater()
menuInflater.inflate(R.menu.add_customer,menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if(item.itemId==R.id.add){
val intent= Intent(this,AddCustomers::class.java)
intent.putExtra("info","new")
startActivity(intent)
}
return super.onOptionsItemSelected(item)
}
For starting a new Activity you need at least a context instance. And as is written in some other answers here, you could start the activity directly inside ViewHolder with View's context. But for better separations of responsibilities, I suggest you move your navigation to your Fragment/Activity, where all other navigations happen. That way you know at the look of Activity/Fragment, which navigations could be done from that screen, and you don't need to search for some hidden navigations that happen inside Adapter or ViewHolder. Also, if you need to perform startActivityForResult, then an instance of Context is not enough to do that. You need an Activity or Fragment instance for that and we have another reason to put all navigations there.
So you can do this like that:
ADD LAMBDA TO ADAPTER CONSTRUCTOR:
class CustomersAdapter(private val companynameArray: ArrayList,
private val companyoffArray: ArrayList,
private val companyPhoneArray: ArrayList,
private val onItemClick: () -> Unit) : RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
NOTE: put lambda as the last constructor parameter.
ADD LAMBDA TO VIEWHOLDER CONSTRUCTOR:
class CustomerHolder(view: View,
private val onItemClick: () -> Unit) : RecyclerView.ViewHolder(view) {
MODIFY VIEWHOLDER CONSTRUCTOR CALL:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.recycler_view_customer_row,parent,false)
return CustomersAdapter.CustomerHolder(view, onItemClick) //Add onItemClick lambda instance from adapter here
}
INVOKE LAMBDA ON CLICK:
itemView.setOnClickListener {
onItemClick()
}
FIX ADAPTER CONSTRUCTOR CALL:
val adapter = CustomersAdapter(companynameArray, companyoffArray, companyPhoneArray) {
startActivity() // do your navigation here
}
Why did we place lambda as the last parameter of a constructor in our adapter? If the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses(SOURCE). And we did exactly that with the last junk of code above where we handle our navigation.
put your click listener inside onBindViewHolder method
override fun onBindViewHolder(holder: CustomerHolder, position: Int) {
holder.recycleCompanyName?.text="Şirket Adı:"+companynameArray[position]
holder.recycleOffName?.text="Şirket Yetkilisi :"+companyoffArray[position]
holder.recycleOffPhone?.text="İrtibat No : "+companyPhoneArray[position]
holder.itemView.setOnClickListener {
// do what ever you want here
holder.itemView.context.startActivity( /*your intent here*/)
}
}
}
Step 1 : pass context here
class CustomersAdapter(val context:Context,private val
companynameArray:ArrayList,private val
companyoffArray:ArrayList, private val companyPhoneArray:ArrayList):
RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
step 2:
itemView.setOnClickListener {
val intent=Intent(context,yourActivity::java.class)
startActivity(intent)
}
you need a context for intent so you can get it form the activity or fragment that are associated with this adapter and pass it to your intent or you can get it from any view in your inflated layout like this
val context=holder.title.context
val intent = Intent( context, EventDetail::class.java)
context.startActivity(intent)

How to send RecyclerView data to a BottomSheetDialog

I am developing a Recyclerview that pressing a button should open a BottomSheetDialog. I can open BottomSheet but I can't pass data to it. I tried using an interface earlier but it didn't work.
class MyAdapter(private val listaItens: List<Itens>, private val context: Context,
private val fragmentManager: FragmentManager) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val POST_TXT = 0
private val POST_IMG = 1
//some code ...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) {
POST_TXT -> {
val item = LayoutInflater.from(parent.context).inflate(R.layout.text, parent, false)
return ViewHolderTexto(item)
}
POST_IMG -> {
val item = LayoutInflater.from(parent.context).inflate(R.layout.image, parent, false)
return ViewHolderImage(item)
}
//some code ...
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = listaItens[position]
val usuarioLogado = UsuarioFirebase.getDadosUsuarioLogado()
when (holder.itemViewType) {
POST_TXT -> {
val viewHolderTexto = holder as ViewHolderTexto
viewHolderTexto.setIsRecyclable(false)
//some code ...
}
POST_IMG -> {
val viewHolderImage = holder as ViewHolderImage
viewHolderImage.setIsRecyclable(false)
//some code ...
holder.imageComentarioPostagemImage.setOnClickListener {
val comentariosBottomSheet = ComentariosBottomSheet()//open bottom sheet
comentariosBottomSheet.show(fragmentManager, comentariosBottomSheet.tag)
}
//some code ...
}
}
}
How to send RecyclerView data to a BottomSheetDialog?
Why don't you simply pass the data by constructor param of the Bottom sheet
val comentariosBottomSheet = ComentariosBottomSheet(data)//open bottom sheet
comentariosBottomSheet.show(fragmentManager, comentariosBottomSheet.tag)
How about his approach:
function/interface passed into the adapter
Note function is passed as last parameter
class MyAdapter( .... private val onClickCallBack:(item :Itens) ... {
....
}
Register clicklistener on the holder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int){
val item = listaItens[position]
val usuarioLogado = UsuarioFirebase.getDadosUsuarioLogado()
....
holder.itemView.setOnClickListener{
onClickCallBack(item)
}
Add callback to the adapter where you want to show the bottom (sheet ex Activity)
As noted above the function was passed as last parameter to the Aconstructor so we can call it after like this:
val myAdapter:MyAdapter = MyAdapter(){
//ShowBottomScree
// 'it' is refenced as the passed data from adapter.
}

Can't assign new value to a simple Singleton using an onClickeListener for a RecyclerView

I am trying to build a Slack-like chat app following a tutorial in a course I am taking online.
In the tutorial the instructor is using a ListView and the OnItemClickListener method, but I am trying to do it with recycler view, and I am stuck with the onClickListener in the adapter.
I have tried to find answers in other questions but couldn't find one that solves my problem. The closest ones were this and this
My two problems are:
1. The app's main activity has on the top of the screen a title that states what channel is currently active. I have created a singleton that holds the "current channel" and the title's text is being pulled from that singleton.
I am having a hard time changing the value of that singleton on click.
The main activity also has all the channels in a list view in a drawer.
I am trying to close the drawer when a channel is clicked but that isn't happening either.
This is my current adapter:
class ChannelsAdapter(val context: Context, val channels: ArrayList<Channel>) :
RecyclerView.Adapter<ChannelsAdapter.Holder>() {
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val singleChannel = itemView.findViewById<TextView>(R.id.single_channel)
val mainLayout = LayoutInflater.from(context).inflate(R.layout.activity_main, null)
fun bindText(textVar: String, context: Context) {
singleChannel.text = textVar
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindText(channels[position].toString(), context)
holder.itemView.setOnClickListener {
ChannelName.activeChannel = channels[position]
holder.mainLayout.drawer_layout.closeDrawer(GravityCompat.START)
}
}
override fun getItemCount(): Int {
return channels.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelsAdapter.Holder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.channel_list_layout, parent, false)
return Holder(view)
}
}
This is the singleton
object ChannelName {
var activeChannel : Channel? = null
}
You can rewrite the setter for activeChanell variable and call a listener that has been added before to notify your Activity:
object ChannelName {
private val listeners = ArrayList<(Channel?) -> Unit>()
fun addChannelNameChangedListener(listener: (Channel?) -> Unit) {
listeners.add(listener)
}
fun removeChannelNameChangedListener(listener: (Channel?) -> Unit) {
listeners.remove(listener)
}
var activeChannel: Channel? = null
set(value) {
field = value
listeners.forEach { it.invoke(value) }
}
}
And inside the Activity add a listener like this:
ChannelName.addChannelNameChangedListener {
// Do your operation
}
The alternative solution is to use Observable utils like LiveData, so you shouldn't worry about the Android life cycle any more:
object ChannelName {
val activeChannel: MutableLiveData<ChannelName> = MutableLiveData()
}
To change the value inside your adapter simply call:
ChannelName.activeChannel.value = channels[position]
And inside your activity Observe to the variable by calling:
ChannelName.activeChannel.observe(this, Observer {
// Do your operation
})
class ChannelsAdapter(val context: Context, val channels: ArrayList<Channel>) :
RecyclerView.Adapter<ChannelsAdapter.Holder>() {
private var itemClickListener: OnItemClickListener? = null
fun setItemClickListener(itemClickListener: OnItemClickListener) {
this.itemClickListener = itemClickListener
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val singleChannel = itemView.findViewById<TextView>(R.id.single_channel)
val mainLayout = LayoutInflater.from(context).inflate(R.layout.activity_main, null)
fun bindText(textVar: String, context: Context) {
singleChannel.text = textVar
}
override fun onClick(v: View?) {
val position = adapterPosition
itemClickListener?.let {
if (position != RecyclerView.NO_POSITION) {
it.onItemClick(position)
}
}
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindText(channels[position].toString(), context)
}
override fun getItemCount(): Int {
return channels.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelsAdapter.Holder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.channel_list_layout, parent, false)
return Holder(view)
}
}
This way you can setItemClickListener to the adapter in your activity and get callback from your recyclerView.
You should not set listener in onBind() method since it will be called more than your items' count.

Categories

Resources