How to use a lambda instead of a kotlin interface - android

I have a recycler view adapter in android. Part of my adapter class looks like this:
private lateinit var itemLongClick: ItemLongClick
override fun onCreateViewHolder(parent: ViewGroup, a: Int): RecyclerAdapter.ViewHolder {
// Define And Initialize The Custom View And Its Holder//
val myView = LayoutInflater.from(parent.context).inflate(customLayout, parent, false)
val viewHolder = ViewHolder(myView)
// What Happens When A List Item Is Long Clicked//
myView.setOnLongClickListener { view ->
// Differ Action To Class Instance//
itemLongClick.longClicked(context, viewHolder.layoutPosition, view)
// End Function//
true
}
// Returns The Custom View//
return viewHolder
}
fun setItemLongClick(itemLongClick: ItemLongClick) {
// Sets The Value For this.itemLongClick//
this.itemLongClick = itemLongClick
}
I created an interface tat looks like this:
interface ItemLongClick {
// Function Declaration For When An Item Is Long Clicked//
fun longClicked(context: Context, position: Int, view: View)
}
Instead of writing my on long click code in the adapter class I want to differ it to the activity that is calling the adapter class. I know one way of doing this is to make a kotlin interface then call it in the other class like this
userAdapter.setItemLongClick(object: ItemLongClick {
override fun longClicked(context: Context, position: Int, view: View) {
}
})
But this looks messy. I know java interfaces work with SAM but I don't want to do that either. What I want is for the onLongClick to be a Lambda but I'm not sure how to set up a Kotlin lambda expression to make this work and I can't find a good example anywhere.
Thanks in advance

You have two options:
1.) replace interface with typealias
typealias ItemLongClick = (Context, Int, View) -> Unit
2.) add an extension function for setting the interface as a lambda instead of with anonymous object
inline fun UserAdapter.setItemLongClick(crossinline longClick: (Context, Int, View) -> Unit) {
setItemLongClick(object: ItemLongClick {
override fun longClicked(context: Context, position: Int, view: View) {
longClick(context, position, view)
}
})
}
Now you can call
userAdapter.setItemLongClick { context, position, view ->
...
}

I had an adapter that i need to change the data based on a switch and i did something like this:
ListAdapter(private val context: Context, private val switchListener: (Boolean) -> Unit)
Then where i binded the header of my sectioned list i had:
private fun bindHeader(holder: HeaderViewHolder) {
holder.switch.setOnCheckedChangeListener { _, isChecked ->
callbackSwitchListener(isChecked)
}
}
And in my fragment:
private fun setupRecyclerView() {
fabricationDataListAdapter =
FabricationDataListAdapter(context!!) { isChecked: Boolean -> switchControl(isChecked) }
val layoutManager = ListLayoutManager(context!!)
this.recycler_view_all.layoutManager = layoutManager
this.recycler_view_all.adapter = fabricationDataListAdapter
}
Where the fun switchControl changed the data based on the boolean.
I'm not sure if this is what you need, i'm in a bit of a hurry, but this is called high order functions in kotlin, if i'm not mistaken.

As the Kotlin documentation for the Kotlin 1.4 release points out:
Before Kotlin 1.4.0, you could apply SAM (Single Abstract Method) conversions only when working with Java methods and Java interfaces from Kotlin. From now on, you can use SAM conversions for Kotlin interfaces as well. To do so, mark a Kotlin interface explicitly as functional with the fun modifier.
fun interface Operation1 {
operator fun invoke(x: String): String
}
fun interface Operation2 {
fun doSomething(x: Int): String
}
val operation1 = Operation1 { "$it world!" }
val operation2 = Operation2 { "$it world!" }
fun main() {
// Usage: First sample.
println(operation1("Hello"))
println(operation2.doSomething(0))
// Usage: Second sample.
println(Operation1 { "$it world!" }("Hello"))
println(Operation2 { "$it!" }.doSomething(0))
}
You can read more about functional interfaces here.

In below code I using filterable adapter to do search on list. Here I am using lambda as callback to notify to view model when no data is found for the search.
Instantiating Adapter in ViewModel. And passing lambda
var matterAdapter = MatterAdapter(matterList) {
//todo - got callback
}
Adapter
class MatterAdapter (var filteredList : MutableList<AndroidViewModel>, val funcNoSearchData : () -> Unit) : DataBindingRecyclerViewAdapter(filteredList), Filterable {
private var mViewModelMap: MutableMap<Class<*>, Int> = mutableMapOf()
private var originalList : MutableList<AndroidViewModel> = mutableListOf()
private val mFilter = ItemFilter()
init {
mViewModelMap.put(MatterRowViewModel::class.java, R.layout.row_matter)
}
override fun getViewModelLayoutMap(): MutableMap<Class<*>, Int> {
return mViewModelMap
}
override fun getFilter(): Filter {
return mFilter
}
private inner class ItemFilter : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults {
val filterString = constraint.toString().toLowerCase()
val results = FilterResults()
val list = originalList
val count = list.size
val nlist = ArrayList<AndroidViewModel>(count)
var filterableString: String
for (i in 0 until count) {
filterableString = (list.get(i) as MatterRowViewModel).matter.casestitle!!
if (filterableString.toLowerCase().contains(filterString)) {
nlist.add(list.get(i))
}
}
results.values = nlist
results.count = nlist.size
return results
}
override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) {
filteredList.clear()
filteredList.addAll(results.values as ArrayList<AndroidViewModel>)
// sends empty search callback to viewmodel
if(filteredList.size == 0) {
funcNoSearchData()
}
notifyDataSetChanged()
}
}
fun resetSearch() {
filteredList.clear()
filteredList.addAll(originalList)
notifyDataSetChanged()
}
fun refreshData() {
originalList = ArrayList(filteredList)
notifyDataSetChanged()
}
}

Related

Fetching Multiple Firestore Documents for RecyclerView

I'm trying to fetch multiple documents from my Firestore collection so I can populate my RecyclerView. However, I'm getting a mismatch error when I try to hook my categories ArrayList to the QuerySnapshot, it says it's looking for kotlin.collections.ArrayList<Category> but it found Category?. What can I do to make my RecyclerView populate my category collection in Firestore? Do I need to rewrite my val categories = ArrayList<Category>()? Thank you!
Category Collection
Category.kt
data class Category(var category: String?, val categoryImage: String?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(category)
parcel.writeString(categoryImage)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)
}
override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size)
}
}
}
CategoryAdapter.kt
class CategoryAdapter(val category: ArrayList<Category>) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
var selectedCategory = Category("", "")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindCategory(category[position])
holder.itemView.setOnClickListener { v ->
val context: Context = v.context
val intent = Intent(context, CategoryServiceActivity::class.java)
selectedCategory.category = category[position].category
intent.putExtra("category", selectedCategory)
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return category.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.categoryrecyclyerview, parent, false)
return ViewHolder(view)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val categoryName = itemView.findViewById<TextView>(R.id.categoryJobNameTextView)
val categoryImage = itemView.findViewById<ImageView>(R.id.categoryImageView)
fun bindCategory(category: Category) {
categoryName?.text = category.category
Picasso.get().load(category.categoryImage).into(categoryImage)
}
}
}
HomeFragment.kt
val categories = ArrayList<Category>()
val categoriesDatabaseRef = FirebaseFirestore.getInstance().collection(REF_JOB_CATEGORIES)
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
categories = querySnapshot.toObject(Category::class.java)
}
}
}
})
As I see in your code, the categories object is an ArrayList. So when you're using the following line of code:
categories = querySnapshot.toObject(Category::class.java)
It means that you're trying to convert the querySnapshot object, which is actually a DocumentSnapshot object, into an object of type Category, which works perfectly fine. However, you cannot assign that value to the categories object because between the ArrayList and Category classes, there is no inheritance relationship, hence that error.
So there are two ways in which you can solve this. The first solution would be to add an object of type Category, at each iteration of the for loop to the list:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
val category = querySnapshot.toObject(Category::class.java)
categories.add(category) //Add the object to the list.
}
}
}
})
The second solution, which is even simpler in my opinion, would be to convert the querySnapshot directly into a list, by removing the for loop like this:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
categories = p0.toObjects(Category::class.java)
}
}
})
Please see that I have used toObjects(Class clazz) method which:
Returns the contents of the documents in the QuerySnapshot, converted to the provided class, as a list.
So it's toObjects, see the s? And not toObject.
Besides that, don't forget that Firebase API is asynchronous. So you cannot simply use the value of categories outside the onSuccess() method. If you're new to asynchronous programming, I recommend you read the following resource:
How to read data from Cloud Firestore using get()?

mvp recycler adapter not showing data

The problem I am facing with the RecyclerView is the data is coming from Server and the API response is getting printed correctly in the console.
but when I am trying to set data in the adapter what is wrong or something is not going correctly with the flow that the data is not being updated on UI.
//This is my adapter class
class DashboardAdapter(val context: Context) : RecyclerView.Adapter<DashBoardHolder>() {
private var transactionList = ArrayList<DashboardData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DashBoardHolder {
val inflator = LayoutInflater.from(parent.context)
val view = ActivityDashboardDataBinding.inflate(inflator, parent, false)
val viewHolder = DashBoardHolder(view)
return viewHolder
}
override fun onBindViewHolder(holder: DashBoardHolder, position: Int) {
val quickModel = transactionList[position]
holder.tvName.text = quickModel.bookingTitle
}
fun showListItems(dashboardlist: List<DashboardData>?, aboolean: Boolean) {
when {
aboolean -> transactionList.clear()
}
if (dashboardlist != null && !dashboardlist.isEmpty())
this.transactionList.addAll(dashboardlist)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return transactionList.size
}
}
MyHolderClass
class DashBoardHolder(val binding: ActivityDashboardDataBinding) :
RecyclerView.ViewHolder(binding.root) {
var tvName: TextView = binding.textViewGrandrukName
var tvTime: TextView = binding.tvGrandrukTripDetails
var tvPlace: ImageView = binding.btnGhandruk
var ivRectangle: ImageView = binding.imageView5
}
Similarly,I set the adapter in view section like this way:
//setting adapter in Presenter class
fun setAdapter() {
var layoutmanager: LinearLayoutManager? = LinearLayoutManager(appCompatActivity)
val firstVisiblePosition = layoutmanager!!.findFirstVisibleItemPosition()
binding!!.includesDashboardRecyclerview.rvBookingList.setHasFixedSize(true)
binding!!.includesDashboardRecyclerview.rvBookingList.layoutManager = layoutmanager
binding!!.includesDashboardRecyclerview.rvBookingList.adapter = dashboardAdapter
layoutmanager!!.scrollToPositionWithOffset(firstVisiblePosition, 0)
}
In Presenter Class, calling setAdapter class from presenter like this way
class DashboardPresenter(
private val dashboardView: DashboardView,
private val dashboardModel: DashboardModel
) {
fun onCreateView() {
onClick()
dashboardView.setAdapter()
getDashboardRequest()
}
//calling adpter function here
fun showList(termlist: List<DashboardData>?, aboolean: Boolean) {
(null as DashboardAdapter?)?.showListItems(termlist!!, aboolean)
}
}
I'm not able to understand what is getting wrong here.
(null as DashboardAdapter?)?.showListItems(termlist!!, aboolean)
You are calling showListItems() on the DashboardAdapter as a type not the instance dashboardAdapter. Assuming that dashboardAdapter is a local class field.
Also I guess this type casting is not necessary as you already using the optional ?
So, it can be simplified to:
dashboardAdapter?.showListItems(termlist!!, aboolean)
Assuming that this should be called whenever you retrieve the API response. So, showList() must be called when there's new API data.

Why do I get this error when creating an Intent and haw to pass data from Firebase that is in RecyclerView on second screen

I marked the parts that were added (added to code) after the moment
when the application was working, the data was successfully downloaded
from the database. I may be mistakenly trying to pass this information
to another screen. I tried to find a video that connects to the
database and forwards that data of recicler on another screen, but
without success, or they are in Java, which I understand less.
MySecondActivity
class BookDescription : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book_description)
var books = intent.getSerializableExtra("noti") as Book //added to code
Glide.with(this).load(books.imageUrl).into(bookImg2)// added to code
nameTxt2.text = books.name //added to code
autorTxt2.text = books.writer //added to code
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var adapter : Adapter
private val viewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setUpRecyclerView()
}
private fun setUpRecyclerView(){
adapter = Adapter(this){
startBookDescription()
}
recycle.layoutManager = GridLayoutManager(this, 2)
recycle.adapter = adapter
observerData()
}
fun observerData(){
viewModel.fetchUserData().observe(this,Observer{
adapter.setListdata(it)
adapter.notifyDataSetChanged()
})
}
private fun startBookDescription(){
val intent = Intent (this, BookDescription::class.java )
startActivity(intent)
}
}
Class Adapter with inner class Holder
class Adapter(private val context: Context,
private val onItemCliked: () -> Unit ) : RecyclerView.Adapter<Adapter.Holder>() {
private var datalist = mutableListOf<Book>()
fun setListdata(data: MutableList<Book>){
datalist = data
}
inner class Holder(itemView : View) : RecyclerView.ViewHolder(itemView){
fun bindView(book: Book, onItemClicked: () -> Unit){
Glide.with(context).load(book.imageUrl).into(itemView.bookImg)
itemView.nameTxt.text = book.name
itemView.autorTxt.text= book.writer
itemView.setOnClickListener { onItemClicked.invoke() }
itemView.bookImg.setOnClickListener(View.OnClickListener { //added
val intent = Intent(context, BookDescription::class.java)//added to code
intent.putExtra("noti", book)//added to code
context.startActivity(intent)//added to code
})
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.book_format, parent,
false )
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val book = datalist[position]
holder.bindView(book, onItemCliked)
}
override fun getItemCount(): Int {
return if (datalist.size> 0){
datalist.size
}else{
0
}
}
}
The problem is here:
intent.putExtra("noti", book)
The book variable is of type Book, which is apparently neither a Parcelable or Serializable class. You must implement one of these two interfaces in the Book class in order to add it to an Intent or Bundle.
Assuming Book is made up of simple data types (String, Int, etc), then you can use the #Parcelize annotation to easily implement Parcelable. More here: https://developer.android.com/kotlin/parcelize
In your bindView() method, you have this block of code:
val intent = Intent(context, BookDescription::class.java)//added to code
intent.putExtra("noti", book)//added to code
context.startActivity(intent)//added to code
})
However, you don't actually do anything with this Intent; you start your activity from another place:
private fun startBookDescription(){
val intent = Intent (this, BookDescription::class.java )
startActivity(intent)
}
You will have to pass the Book instance to this method (via invoke(book)). This will require a corresponding type change to the click listener parameter of your adapter.

Trying to get RecyclerView to use a diffferent dataset

I've been stuck trying to figure out how to update the list that my RecyclerView is showing.
What I'm trying to do is show a subset of a shown list when a spinner is changed. I have a collection of animals in my database and some have their pet attribute set as true and others have it set as false.
Using Room Database with repositories and viewModels, and what I've been trying to piece together is that it's good to have three different lists that I can tune into, so in m
Repository:
class AnimalRepository(private val animalDao: AnimalDao) {
val allAnimals: Flow<List<Animal>> = animalDao.getAnimalsByCategory()
val pets: Flow<List<Animal>> = animalDao.getAnimalsByPetStatus(true)
val nonPets: Flow<List<Animal>> = animalDao.getAnimalsByPetStatus(false)
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(animal: Animal) {
animalDao.insert(animal)
}
#WorkerThread
suspend fun get(id: Int): Animal {
return animalDao.get(id)
}
#WorkerThread
suspend fun delete(id: Int) {
animalDao.delete(id)
}
}
ViewModel
class AnimalViewModel(private val repository: AnimalRepository) : ViewModel() {
var allAnimals: LiveData<List<Animal>> = repository.allAnimals.asLiveData()
val pets: LiveData<List<Animal>> = repository.pets.asLiveData()
val nonPets: LiveData<List<Animal>> = repository.nonPets.asLiveData()
var result: MutableLiveData<Animal> = MutableLiveData<Animal>()
var mode: VIEW_MODES = VIEW_MODES.BOTH
/*
* Launching a new coroutine to insert the data in a non-blocking way
* */
fun insert(animal: Animal) = viewModelScope.launch {
repository.insert(animal)
}
/*
* Launching a new coroutine to get the data in a non-blocking way
* */
fun get(id: Int) = viewModelScope.launch {
result.value = repository.get(id)
}
fun delete(id: Int) = viewModelScope.launch {
repository.delete(id)
}
}
class AnimalViewModelFactory(private val repository: AnimalRepository) : ViewModelProvider.Factory {
override fun <T: ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AnimalViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return AnimalViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
In my MainActivity I have it set up where I have an observer on these three lists and depending on which view mode is active (the spinner sets the view mode), that list is fed into the my RecyclerView's ListAdapter's submitList
animalViewModel.allAnimals.observe(this) { animals ->
if (viewMode == VIEW_MODES.BOTH) {
animals.let {
adapter.submitList(it)
// recyclerView.adapter = adapter
}
}
}
animalViewModel.pets.observe(this) { animals ->
if (viewMode == VIEW_MODES.PETS) {
animals.let {
adapter.submitList(it)
// recyclerView.adapter = adapter
}
}
}
animalViewModel.nonPets.observe(this) { animals ->
if (viewMode == VIEW_MODES.NON_PETS) {
animals.let {
adapter.submitList(it)
}
}
}
I am changing the mode with my spinner doing
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (position) {
0 -> {
viewMode = VIEW_MODES.BOTH
}
1 -> {
viewMode = VIEW_MODES.PETS
}
2 -> {
viewMode = VIEW_MODES.NON_PETS
}
}
adapter.notifyDataSetChanged()
}
This works fine if add or remove an animal after changing the view mode since the observers fire and the correct one is allowed to populate the adapter, but the notifyDataSetChanged() isn't doing anything and I've been stuck on getting the adapter to update without having to add or remove from the lists
I also tried resetting the adapter in the observer but that didn't do anything either
I am extremely new to kotlin and android programming, and I'm sure that I'm going about this the wrong way, but is there a way force a list refresh?
Update:
I think I may have found a found a solution but I worry that it's hacky. In my ViewModel I am replacing the contents of my allAnimals with the filtered lists
fun showBoth() {
allAnimals = repository.allAnimals.asLiveData()
}
fun showPets() {
allAnimals = repository.pets.asLiveData()
}
fun showNonPets() {
allAnimals = repository.nonPets.asLiveData()
}
and then in my main activity I changed my logic on when handling the spinner change to tell the view model to do its thing and then to remove the observer and slap it back on
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (position) {
0 -> {
animalViewModel.showBoth()
}
1 -> {
animalViewModel.showPets()
}
2 -> {
animalViewModel.showNonPets()
}
}
refreshObserver()
}
private fun refreshObserver() {
animalViewModel.allAnimals.removeObservers(this)
animalViewModel.allAnimals.observe(this) { animals ->
animals.let {
adapter.submitList(it)
}
}
}
this seems to work to get the recycler view to update, but is it hacky?
As far as I can see it makes perfect sense that notifyDataSetChanged isn't doing anything, you don't submit any new data before that call. However I think what you're trying to do is to get the adapter to react to a change in viewMode.
If this is the case, I would recommend also having your viewMode as a LiveData object and then expose a single list for your adapter to observe, which changes depending on the viewMode selected.
The Transformations.switchMap(LiveData<X>, Function<X, LiveData<Y>>) method (or its equivalent Kotlin extension function) would probably do most of the work for you here. In summary it maps the values of one LiveData to another. So in your example, you could map your viewMode to one of the allAnimals, pets and nonPets.
Here is a simple pseudocode overview for some clarity:
AnimalViewModel {
val allAnimals: LiveData<List<Animal>>
val pets: LiveData<List<Animal>>
val nonPets: LiveData<List<Animal>>
val modes: MutableLiveData<VIEW_MODES>
val listAnimals = modes.switchMap {
when (it) {
VIEW_MODES.BOTH -> allAnimals
...
}
}
}
fun onItemSelected {
viewModel.onModeChanged(position)
}
viewModel.listAnimals.observe {
adapter.submitList(it)
}

Generic RecyclerView adapter

I want to have generic RecyclerView to be able to reuse it. In my case I have 2 models: CategoryImages and Category. While trying to add constructor() it brings the following errors. I know the second one is because it understands like both primary and secondary constructor are same.
Is it possible to do such kind of thing? If yes, then how? if no - thank you.
Here is CategoryImage:
class CategoryImage {
#SerializedName("url")
private var url: String? = null
fun getUrl(): String? {
return url
}
}
And here is Category:
class Category {
#SerializedName("_id")
var id: String? = null
#SerializedName("name")
var name: String? = null
#SerializedName("__v")
var v: Int? = null
#SerializedName("thumbnail")
var thumbnail: String? = null
}
Here is the part of RecyclerViewAdapter's constructor:
class RecyclerViewAdapter(var arrayList: ArrayList<CategoryImage>?, var fragment: Int): RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
constructor(arrayList: ArrayList<Category>, fragment: Int): this(arrayList, fragment)
}
I want to have generic RecyclerView to be able to reuse it.
That's nice intention, then why you haven't made your adapter generic?
I think you can adopt the approach outlined by Arman Chatikyan in this blog post. After applying some Kotlin magic you'll only need following lines of code in order to setup your RecyclerView:
recyclerView.setUp(users, R.layout.item_layout, {
nameText.text = it.name
surNameText.text = it.surname
})
And if you need to handle clicks on RecyclerView items:
recyclerView.setUp(users, R.layout.item_layout, {
nameText.text = it.name
surNameText.text = it.surname
}, {
toast("Clicked $name")
})
Now the adapter of the RecyclerView is generic and you are able to pass list of any models inside setup() method's first argument.
In this section I will copy-paste sources from the blog post, in order to be evade from external sources deprecation.
fun <ITEM> RecyclerView.setUp(items: List<ITEM>,
layoutResId: Int,
bindHolder: View.(ITEM) -> Unit,
itemClick: ITEM.() -> Unit = {},
manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context)): Kadapter<ITEM> {
return Kadapter(items, layoutResId, {
bindHolder(it)
}, {
itemClick()
}).apply {
layoutManager = manager
adapter = this
}
}
class Kadapter<ITEM>(items: List<ITEM>,
layoutResId: Int,
private val bindHolder: View.(ITEM) -> Unit)
: AbstractAdapter<ITEM>(items, layoutResId) {
private var itemClick: ITEM.() -> Unit = {}
constructor(items: List<ITEM>,
layoutResId: Int,
bindHolder: View.(ITEM) -> Unit,
itemClick: ITEM.() -> Unit = {}) : this(items, layoutResId, bindHolder) {
this.itemClick = itemClick
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.bindHolder(itemList[position])
}
override fun onItemClick(itemView: View, position: Int) {
itemList[position].itemClick()
}
}
abstract class AbstractAdapter<ITEM> constructor(
protected var itemList: List<ITEM>,
private val layoutResId: Int)
: RecyclerView.Adapter<AbstractAdapter.Holder>() {
override fun getItemCount() = itemList.size
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val item = itemList[position]
holder.itemView.bind(item)
}
protected abstract fun onItemClick(itemView: View, position: Int)
protected open fun View.bind(item: ITEM) {
}
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
Assuming CategoryImage means a Category with image.
You can express this relationship with inheritance:
open class Category(
val name: String
)
class CategoryImage(
name: String,
val image: String
) : Category(name)
class RecyclerViewAdapter(
val arr: List<Category>,
val fragment: Int
) {
fun bind(i: Int) {
val item = arr[i]
val name: String = item.name
val image: String? = (item as? CategoryImage)?.image
}
}
Another options it to have a common interface (which removes that ugly cast):
interface CategoryLike {
val name: String
val image: String?
}
class Category(
override val name: String
) : CategoryLike {
override val image: String? = null
}
class CategoryImage(
override val name: String,
override val image: String
) : CategoryLike
class RecyclerViewAdapter(private var arr: List<CategoryLike>, var fragment: Int) {
fun bind(i: Int) {
val item = arr[i]
val name: String = item.name
val image: String? = item.image
}
}
In both cases the following works (just to see that it can be compiled):
fun testCreation() {
val cats: List<Category> = listOf()
val catImages: List<CategoryImage> = listOf()
RecyclerViewAdapter(cats, 0)
RecyclerViewAdapter(catImages, 0)
}
Tip: don't use ArrayList, List (listOf(...)) or MutableList (mutableListOf(...)) should be enough for all your needs.
Tip: try to use val as much as you can, it helps prevent mistakes.
Wish: Next time please also include some relevant parts of your code in a copy-able form (not screenshot), so we don't have to re-type it and have more context. See https://stackoverflow.com/help/mcve
One "terrible" way of doing it is to simply have 1 constructor taking an ArrayList of Objects and perform an instanceof on the objects.
Both methods have the same signature, because type parameters are not considered as different types (for Java Virtual Machine both are just ArrayLists). You also need to be aware of type erasure.
Check this repository https://github.com/shashank1800/RecyclerGenericAdapter
lateinit var adapter: RecyclerGenericAdapter<AdapterItemBinding, TestModel>
...
val clickListener = ArrayList<CallBackModel<AdapterItemBinding, TestModel>>()
clickListener.add(CallBackModel(R.id.show) { model, position, binding ->
Toast.makeText(context, "Show button clicked at $position", Toast.LENGTH_SHORT)
.show()
})
adapter = RecyclerGenericAdapter(
R.layout.adapter_item, // layout for adapter
BR.testModel, // model variable name which is in xml
clickListener // adding click listeners is optional
)
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
adapter.submitList(viewModel.testModelList)
Recycler adapter item R.layout.adapter_item XML.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="testModel"
type="com.packagename.model.TestModel" />
</data>
...
VERY IMPORTANT NOTE: I'm using same layout for all my screens.
//********Adapter*********
// include a template parameter T which allows Any datatype
class MainAdapter<T : Any>(var data: List<T>) : RecyclerView.Adapter<MainViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
val view = parent.inflateLayout()
return MainViewHolder(view)
}
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
override fun getItemCount(): Int = data.size
class MainViewHolder(private val binding: MainItemsListBinding) :
RecyclerView.ViewHolder(binding.root) {
// do the same for for bind function on Viewholder
fun <T : Any> bind(item: T) {
// Extension function see code below
binding.appInfo.mySpannedString(item)
}
}
}
//Cast Item to type
fun <T : Any> TextView.mySpannedString(item: T) {
when (item.javaClass.simpleName) {
"AaProgram" -> {
item as AaProgram
this.text = buildSpannedString {
appInfo(item.numero, item.principio)
}
}
"AppContent" -> {
item as AppContent
this.text = buildSpannedString {
appInfo(item.title, item.coment, item.footnote)
}
}
"AutoDiagnostic" -> {
item as AppContent
this.text = buildSpannedString {
appInfo(item.title, item.coment, item.footnote)
}
}
"GroupDirectory" -> {}
"ReflexionsBook" -> {}
"County" -> {}
"States" -> {}
"Towns" -> {}
}
}

Categories

Resources