I am working with MVP for the first time and I believe I get the idea of it but I am not sure about the RecyclerView. As far as I can say, MVP is about making views as passive as possible so all business logic goes to the Presenter but how can this be achieved for the Recycler View?
Here is my code so far:
Contract
public interface PhotosContract {
// View
interface View {//: IBaseActivity {
fun showPhotos(photos: ArrayList<Photo>)
fun showText(message: String)
}
// Presenter
interface Presenter {//: IBasePresenter<View> {
fun getPhotos()
}
}
Presenter
public class PhotosPresenter(var view: PhotosContract.View) :PhotosContract.Presenter {
var dataList = ArrayList<Photo>()
override fun getPhotos() {
//call for endpoint
val call : Call<ArrayList<Photo>> = ApiClient.getClient.getPhotos()
call.enqueue(object: Callback<ArrayList<Photo>> {
override fun onFailure(call: Call<ArrayList<Photo>>, t: Throwable) {
Log.d("FAIL","FAILED")
}
override fun onResponse(
call: Call<ArrayList<Photo>>,
response: Response<ArrayList<Photo>>
)
{
Log.d("SUCCESS","SUCCESSED")
dataList.addAll(response!!.body()!!)
Log.d("SIZELIST",dataList.size.toString())
view.showPhotos(dataList)
view.showText("SUCCESS")
}
})
}
}
RecyclerViewAdapter
class PhotosAdapter(private var dataList: List<Photo>, private val context: Context) : RecyclerView.Adapter<PhotosAdapter.PhotosViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotosAdapter.PhotosViewHolder {
return PhotosViewHolder(LayoutInflater.from(this.context).inflate(R.layout.list_item_home, parent, false))
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: PhotosAdapter.PhotosViewHolder, position: Int) {
val dataModel = dataList[position]
holder.titleTextView.text = dataModel.title
}
class PhotosViewHolder(itemLayoutView: View) : RecyclerView.ViewHolder(itemLayoutView){
var titleTextView: TextView = itemLayoutView.tv_title
}
}
Activity
class PhotosActivity : AppCompatActivity(),PhotosContract.View {
private lateinit var presenter: PhotosPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photos)
presenter = PhotosPresenter(this)
presenter.getPhotos()
}
override fun showPhotos(photos: ArrayList<Photo>) {
photosRecyclerView.layoutManager = LinearLayoutManager(this)
photosRecyclerView.adapter = PhotosAdapter(photos,this)
photosRecyclerView.adapter?.notifyDataSetChanged()
}
override fun showText(message: String) {
Toast.makeText(this,message,Toast.LENGTH_LONG).show()
}
}
Here's how I did it when I used MVP. In your contract, define an additional view named ItemView. The way I do it, each item view holder is a MVP view. The view is dumb, so it just calls the presenter whenever something happens, and the presenter calls it back.
interface MyContract {
interface View {
fun setTitle(title: String)
}
// Add this interface here
interface ItemView {
fun bindItem(item: Item)
}
interface Presenter {
fun attach(view: View)
fun detach()
val itemCount: Int
fun onItemClicked(pos: Int)
fun onBindItemView(itemView: ItemView, pos: Int)
}
}
The adapter is also dumb. When it needs to bind an item view holder, it calls the presenter to do it.
class MyAdapter : RecyclerView.Adapter<ViewHolder>() {
// How many items do we have? We don't know, ask the presenter.
override fun getItemCount() = presenter?.itemCount ?: 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// How to bind the item if we only have position? We don't know, ask the presenter.
presenter?.onBindItemView(holder, position)
}
// ...
}
The ViewHolder implements the MyContract.ItemView interface. Again, it's just a view so it has no responsibility by itself. It just delegates to the presenter.
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), MyContract.ItemView {
private val txv: TextView = view.findViewById(R.id.text_view)
init {
view.setOnClickListener {
// What to do here, we only have the item's position? Call the presenter.
presenter?.onItemClicked(adapterPosition)
}
}
override fun bindItem(item: Item) {
txv.text = item.text
}
}
And finally the presenter:
class MyPresenter : MyContract.Presenter {
private var view: View? = null
private val items = mutableListOf<Item>()
override fun attach(view: View) {
this.view = view
// ...
}
override fun detach() {
view = null
}
override val itemCount: Int
get() = items.size
override fun onItemClicked(pos: Int) {
val item = items[pos]
// ...
}
override fun onBindItemView(itemView: ItemView, pos: Int) {
itemView.bindItem(items[pos])
}
// ...
}
The view for completeness, but nothing new here:
class MyView : Fragment(), MyContract.View {
private var presenter: Presenter? = null
override fun onViewCreated(view: View) {
// Attach presenter
presenter = MyPresenter()
presenter?.attach(this)
}
override fun onDestroyView() {
super.onDestroyView()
// Detach the presenter
presenter?.detach()
presenter = null
}
// ...
}
That's just one way to do it, I'm sure there are a lot of others. I just like this one because all the responsibility belongs to the presenter, there's no business logic anywhere else.
Eventually, you'll want to make changes to your list and notify the adapter. For this, add a couple methods in your View contract like notifyItemInserted(pos: Int) and call them when needed from the presenter. Or, better yet, use DiffUtil so you don't have to manage it yourself!
Once you have a good understanding of MVP though, I strongly suggest you move to MVVM as it is the official architecture promoted by Google. Most people also find it a lot more convenient than MVP.
If you have any questions don't hesitate.
Related
Scenario
Recently moved from synthetics to view binding and I'm still struggling (haven't done any coding in months, so I'm rusty as it gets).
MainActivity has two recyclerviews, one displaying totals, one displaying a list of transactions. Each transaction has a "OnLongClickListener" attached to it (attached in the adapter class). This listener calls for a MaterialAlertDialog with two options: edit or delete.
There's also a separate fragment for adding transactions.
Requirement
Upon addition, deletion or modification of these transactions, both recycleradapters need to refresh the data in order to reflect the correct information on screen.
Problem
I'm not able to get the adapters to receive the "notifydatasetchanged" as I am not sure how to get the adapters' reference.
Code being used
MainActivity
private val db = FirebaseFirestore.getInstance()
private val dbSettings = firestoreSettings { isPersistenceEnabled = true }
CoroutineScope(Dispatchers.Default).launch {
val adapterRV1 = Adapter_RV1(FSDB.Get_calculations, FSDB.Get_transactions(db))
val adapterRV2 = Adapter_RV2(FSDB.Get_transactions(db))
runOnUiThread {
binding.rvCalculations.adapter = adapterRV1
binding.rvTransactions.adapter = adapterRV2
}
}
Adapter_RV1
class Adapter_RV1(val calculations_list: List<calculation>,val transactions_list: ArrayList<Transactions>) : RecyclerView.Adapter<Adapter_RV1.CalculationViewHolder>() {
private lateinit var binding: RvCalculationsLayoutBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter_RV1.CalculationViewHolder {
val binding = RvCalculationsLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return CalculationViewHolder(binding)
}
override fun onBindViewHolder(holder: KPIViewHolder, position: Int) {
with(holder) {
with(calculations_list[position]) {
...
}
}
}
override fun getItemCount() = calculations_list.size
inner class CalculationViewHolder(val binding: RvCalculationsLayoutBinding) :
RecyclerView.ViewHolder(binding.root)
}
Adapter_RV2
class Adapter_RV2(var transactions_list: List<Transaction>):
RecyclerView.Adapter<Adapter_RV2.TransactionsViewHolder>() {
private lateinit var binding: RvTransactionsLayoutBinding
inner class TransactionsViewHolder(val binding: RvTransactionsLayoutBinding) : RecyclerView.ViewHolder(binding.root){
init {
binding.root.setOnLongClickListener {
val position = absoluteAdapterPosition
val item = transactions_list[position]
CoroutineScope(Dispatchers.Main).launch {
CreateDialog(item,position,binding)
}
true
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter_RV2.TransactionsViewHolder {
val binding = RvTransactionsLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TransactionsViewHolder(binding)
}
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
...
}
override fun getItemCount() = transactions_list.size
}
CreateDialog
class CreateDialog (transaction: Transaccion, position: Int, binding: RvVistaTransaccionesBinding){
private suspend fun DeleteTransaction(position: Int) {
...
traRef.update("transactions", FieldValue.arrayRemove(transaction)).await()
}
private val puBinding : PopupLayoutBinding = PopupLayoutBinding.inflate(LayoutInflater.from(binding.root.context))
init {
with(puBinding){
...
CoroutineScope(Dispatchers.Main).launch {
supervisorScope {
val task = async {
DeleteTransaction(position)
}
try{
task.await()
/// This is where, I guess, adapters should be notified of dataset changes
popup.dismiss()
}
catch (e: Throwable){
crashy.recordException(e)
Log.d("Transaction",e.message!!)
popup.dismiss()
}
}
}
}
}
popup.setView(puBinding.root)
popup.show()
}
What I've tested so far
I honestly have no clue how to proceed. I've tried a few things but none work and considering I'm super green in Dev in general, View Binding is a bit more confusing than usual.
I have replaced the line below
notifyDataSetChanged();
with
binding.recycler.getAdapter().notifyDataSetChanged();
in my own code.
Here, "recyler" is my RecyclerView, and "binding" is ActivityListBinding.
I didnt read your code, maybe my tip will help you, it worked for me.
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.
What's the objective
Im currently working on an app which has a RecyclerView for the Settings menu. This menu serves to load other fragments. So i needed to implement an OnItemClick function: for this, i followed this video.
What's the probelm
Following the given tutorial, Android Studio flags val adapter = adapterSettings(settingsList), saying No value passed for parameter 'listener'. I suppose that im missing something, since without the code written in the tutorial, the RecyclerView works.
So, am i missing something? Are there any ways to fix this in an easy and clean way?
Code:
activitySettings.kt
class ndActSettings : AppCompatActivity(), adapterSettings.OnItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ndactivity_settings)
topToolbarBack.setNavigationOnClickListener {
finish()
}
var settingsList = listOf(
dataItemsSettings(getString(R.string.look), getString(R.string.lookdescription), R.drawable.ic_colored_color_lens),
dataItemsSettings(getString(R.string.reproduction), getString(R.string.reproductiondescription), R.drawable.ic_colored_view_carousel),
dataItemsSettings(getString(R.string.images), getString(R.string.imagesdscription), R.drawable.ic_colored_image),
dataItemsSettings(getString(R.string.audio), getString(R.string.audiodescription), R.drawable.ic_colored_volume_up),
dataItemsSettings(getString(R.string.about), getString(R.string.aboutdescription), R.drawable.ic_colored_info)
)
val adapter = adapterSettings(settingsList) //ERROR HERE!
rvSettings.adapter = adapter
rvSettings.layoutManager = LinearLayoutManager(this)
}
override fun OnItemClick(position: Int) {
//TODO
}
}
adapterSettings.kt
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<adapterSettings.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
The constructor of class adapterSettings is expecting two parameters
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
)
However, you are instantiating the object with one parameter only:
val adapter = adapterSettings(settingsList)
So, you have to add a second parameter.. An object that implements OnItemClickListener. Since you activity already implements that interface, you can send the activity as second parameter:
val adapter = adapterSettings(settingsList, this)
I've done this successfully with a normal ViewAdapter but I can't seem to get it working with a ListAdapter.
Here is my Fragment that does most of the work:
class CrimeListFragment: Fragment() {
//Required interface for hosting activities
interface Callbacks {
fun onCrimeSelected(crimeId: UUID)
}
private var callbacks: Callbacks? = null
private lateinit var crimeRecyclerView: RecyclerView
private val crimeListViewModel: CrimeListViewModel by lazy {
ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
}
override fun onAttach(context: Context) {
super.onAttach(context)
callbacks = context as Callbacks?
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_crime_list, container, false)
crimeRecyclerView =
view.findViewById(R.id.crime_recycler_view) as RecyclerView
crimeRecyclerView.layoutManager = LinearLayoutManager(context)
crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
crimeListViewModel.crimeListLiveData.observe(
viewLifecycleOwner,
Observer { crimes ->
crimes?.let {
Log.i(TAG, "Got crimes ${crimes.size}")
updateUI(crimes)
}
}
)
}
override fun onDetach() {
super.onDetach()
callbacks = null
}
private fun updateUI(crimes: List<Crime>) {
crimeRecyclerView.adapter = CrimeListAdapter(crimes)
}
companion object {
fun newInstance(): CrimeListFragment {
return CrimeListFragment()
}
}
private inner class CrimeHolder(view: View)
: RecyclerView.ViewHolder(view), View.OnClickListener {
private lateinit var crime: Crime
private val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
private val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
private val solvedImageView = itemView.findViewById<ImageView>(R.id.crime_solved)
init {
itemView.setOnClickListener(this)
}
fun bind(crime: Crime) {
this.crime = crime
titleTextView.text = crime.title
dateTextView.text = crime.date.toString()
solvedImageView.visibility = if(crime.isSolved) {
View.VISIBLE
} else {
View.GONE
}
}
override fun onClick(v: View) {
callbacks?.onCrimeSelected(crime.id)
}
}
private inner class CrimeListAdapter(var crimes: List<Crime>)
: ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view =
layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(crimes[position])
}
}
private inner class DiffCallback: DiffUtil.ItemCallback<Crime>() {
override fun areItemsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem == newItem
}
}
}
And here is the fragment's viewmodel:
class CrimeListViewModel: ViewModel() {
private val crimeRepository = CrimeRepository.get()
val crimeListLiveData = crimeRepository.getCrimes() //returns LiveData<List<Crime>>
}
Android documentation has this regarding ListAdapter:
While using a LiveData is an easy way to provide data to the adapter, it isn't required - you can use submitList(List) when new lists are available.
I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But crimeRecyclerView.adapter has no .submitList() function. So how do I pass on the new list?
LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I observe this time? Or do I just add code to my existing Observer?
Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's functions get called. I'm not sure if this is a real problem or just the consequence of the above.
1.I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But
crimeRecyclerView.adapter has no .submitList() function. So how do I
pass on the new list?
crimeRecyclerView.adapter return RecyclerView.Adapter type
submitList() is a method of ListAdapter, a sub-class of RecyclerView.Adapter
You need to cast from super to sub class before calling that method, like this.
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
2.LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I
observe this time? Or do I just add code to my existing Observer?
Your code for this part is good, no need to do more.
3.Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's
functions get called. I'm not sure if this is a real problem or just
the consequence of the above.
The best part of using ListAdapter is you do not need to provide a list of data (crimes in your case) to constructor.
Back to your code, you need to change 3 things.
// crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
crimeRecyclerView.adapter = CrimeListAdapter()
and
// crimeRecyclerView.adapter = CrimeListAdapter(crimes)
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
and
//private inner class CrimeListAdapter(var crimes: List<Crime>) :
// ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
//
// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
// val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
// return CrimeHolder(view)
// }
//
// override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
// holder.bind(crimes[position])
// }
//}
private inner class CrimeListAdapter : ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(getItem(position))
}
}
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.