I want each row of my RecyclerView to display all the details of one document of the collection.
I've used this exact same adapter code, albeit with a different class to serialize into. And it works well. But in this instance, it's simply not working.
But the code just doesn't get into populating the views.
My database is like:
reviews--Orange--vault--|
|-firstReview
|-secondReview
|-sjdeifhaih5aseoi
...
My query and adapter from the fragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ReviewViewModel::class.java)
val reviewQuery = FirebaseFirestore.getInstance().collection("reviews").document("Orange").collection("vault")
val reviewBurnOptions = FirestoreRecyclerOptions.Builder<Review>()
.setQuery(reviewQuery, object : SnapshotParser<Review> {
override fun parseSnapshot(snapshot: DocumentSnapshot): Review {
return snapshot.toObject(Review::class.java)!!.also {
it.id = snapshot.id
}
}
}).setLifecycleOwner(this)
reviewRecycler.adapter=ReviewBurnAdapter(reviewBurnOptions.build())}
class ReviewBurnAdapter(options: FirestoreRecyclerOptions<Review>) :
FirestoreRecyclerAdapter<Review, ReviewBurnAdapter.ViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//I never reach this point
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_review, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, item: Review) {
holder.apply {
holder.itemView.rowAuthor.text = item.author
}
}
inner class ViewHolder(override val containerView: View) :
RecyclerView.ViewHolder(containerView), LayoutContainer
}
Class to serialize into:
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.PropertyName
import java.util.*
class Review(
#get:Exclude var id: String = "DEVIL",
#JvmField #PropertyName(AUTHOR) var author: String = "",
#JvmField #PropertyName(WRITEUP) var writeup: String = "",
//#JvmField #PropertyName(MOMENT) var moment:Date=Date(1997,12,1),
#JvmField #PropertyName(RATING) var rating: Int = 0
) {
companion object {
const val AUTHOR = "author"
const val WRITEUP = "writeup"
const val RATING = "rating"
//const val MOMENT="moment"
}
}
Also, there's no errors, it just never reaches the code that would generate and populate with viewHolders.
Alright, the fix was ultra simple, as #Prashant Jha pointed out, I hadn't specified a layout manager for my RecyclerView -_-
To be crystal clear, I added app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
to my xml, and everything worked.
Related
When I run the app, the fragments content is blank. Even though the log statements show, the list is populated. I tried implementing a favorite post feature. You can add/remove a favorite post to your list. This works fine.
The goal:
I want to display the favorite posts in FavoritePostsOverViewFragment. Using a recyclerView.
I'm also trying to follow MVVM architecture. Using a Room database. (no API at this point)
The problem(s):
Working with the 2 different objects seems a bit weird the way I do it right now. But it is populated at the moment
Please refer to the part "How I am getting the posts based on if they have been favorite by a user" Is there a less complex way of writing this?
The Binding Adapter is null / empty, not displaying the posts.
I am using the Adapter already in another fragment, it works fine there. I can see a list of posts and use the click listeners. So In my thoughts, I eliminated the adapter as a problem for this case.
The two data classes used:
data class Post(
var Id: Long = 0L,
var Text: String = "",
var Picture: Bitmap? = null,
var Link: String = "",
var UserId: String = "",
var UserEmail: String = ""
)
data class Favorite(
var Id: Long = 0L,
var UserId: String = "",
var PostId: Long = 0L
)
The Adapter
lass PostAdapter(val clickListener: PostListener, val favoriteListener: FavoriteListener) :
ListAdapter<Post, ViewHolder>(PostDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(clickListener, favoriteListener, item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
}
class ViewHolder(val binding: PostListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(clickListener: PostListener, favoriteListener: FavoriteListener, item: Post) {
binding.post = item
binding.clickListener = clickListener
binding.favoriteListener = favoriteListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
println(layoutInflater.toString())
val binding = PostListItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem.Id == newItem.Id
}
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem == newItem
}
}
class PostListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
class FavoriteListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
How I am getting the posts based on if they have been favorite by a user.
class PostRepository(private val faithDatabase: FaithDatabase) {
suspend fun getUserFavs(): List<Long> {
return withContext(Dispatchers.IO) {
faithDatabase.favoriteDatabaseDao.getUserFavorites(CredentialsManager.cachedUserProfile?.getId()!!)
}
}
suspend fun getFavos(): LiveData<List<Post>> {
val _items: MutableLiveData<List<Post>> = MutableLiveData(listOf())
val items: LiveData<List<Post>> = _items
val postIds: List<Long>
var dbPost: DatabasePost
withContext(Dispatchers.IO) {
postIds = getUserFavs()
}
for (id in postIds) {
withContext(Dispatchers.IO) {
dbPost = faithDatabase.postDatabaseDao.get(id)
}
val post = Post(
Text = dbPost.Text,
UserId = dbPost.UserId,
UserEmail = dbPost.UserEmail,
Link = dbPost.Link,
Picture = dbPost.Picture,
Id = dbPost.Id
)
_items.value = _items.value?.plus(post) ?: listOf(post)
}
Timber.i("items= " + items.value!!.size)
/*this logs=
I/PostRepository: items= 2*/
return items
}
My FavoritePostOverViewModel
class FavoritePostsOverviewViewModel(val database: PostDatabaseDao, app: Application) :
AndroidViewModel(app) {
private val db = FaithDatabase.getInstance(app.applicationContext)
private val postRepository = PostRepository(db)
var posts: LiveData<List<Post>>? = null
init {
viewModelScope.launch {
posts = repository.getFavos()
Timber.i(posts!!.value.toString())
/* this logs=
I/FavoritePostsOverviewViewModel: [Post(Id=1, Text=Name, Picture=android.graphics.Bitmap#ef3b553, Link=Add your link here, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com), Post(Id=4, Text=test, Picture=android.graphics.Bitmap#35ae90, Link=www.google.com, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com)]*/
}
}
my FavoritePostsOverViewFragment
class FavoritePostsOverViewFragment : Fragment() {
lateinit var binding: FragmentFavoritePostsBinding
private lateinit var favoritePostsOverviewViewModel: FavoritePostsOverviewViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// setup the db connection
val application = requireNotNull(this.activity).application
val dataSource = FaithDatabase.getInstance(application).postDatabaseDao
// create the factory + viewmodel
val viewModelFactory = FavoritePostsOverviewViewModelFactory(dataSource, application)
favoritePostsOverviewViewModel =
ViewModelProvider(this, viewModelFactory)[FavoritePostsOverviewViewModel::class.java]
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_favorite_posts, container, false)
// Giving the binding access to the favoritePostsOverviewViewModel
binding.favoritePostsOverviewViewModel = favoritePostsOverviewViewModel
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
binding.lifecycleOwner = this
// Sets the adapter of the PostAdapter RecyclerView with clickHandler lambda that
// tells the viewModel when our property is clicked
binding.postList.adapter = PostAdapter(PostListener {
favoritePostsOverviewViewModel.displayPropertyDetails(it)
}, FavoriteListener {
favoritePostsOverviewViewModel.FavoriteClick(it)
})
return binding.root
}
I have a Binding Adapter
#BindingAdapter("listData")
fun bindRecyclerViewPost(recyclerView: RecyclerView, data: List<Post>?) {
if (data.isNullOrEmpty()) {
return
}
val adapter = recyclerView.adapter as PostAdapter
adapter.submitList(data)
}
Used in the XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="favoritePostsOverviewViewModel"
type="com.example.ep3_devops_faith.ui.post.favorites.FavoritePostsOverviewViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:listData="#{favoritePostsOverviewViewModel.posts}"
tools:listitem="#layout/post_list_item"
tools:itemCount="16"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
referenced articles:
Android BindingAdapter order of execution?
LiveData Observer in BindingAdapter
https://developer.android.com/topic/architecture
https://developer.android.com/topic/libraries/data-binding/binding-adapters
https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/01_Introduction
try changing this line
app:listData="#{favoritePostsOverviewViewModel.posts}"
to
app:listData="#{favoritePostsOverviewViewModel.posts.value}"
I guess, you are binding list of posts in your binding adapter and you are passing LiveData<List>
I am creating one Android app and trying to set the data in Recyclerview, I am using MVVM architecture pattern with kotlin, I can see data in logcat but when app loads I am not seeing any data in my recyclerview. Following is my code.
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var productViewModel: ProductViewModel
private lateinit var binding: ActivityMainBinding
val adapter = ProductAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val productService = RetrofitHelper.getInstance().create(ProductService::class.java)
val productRepository = ProductRepository(productService)
productViewModel = ViewModelProvider(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
binding.recyclerview.adapter = adapter
productViewModel.products.observe(this,{
Log.d("TEST",it.toString())
adapter.notifyDataSetChanged()
})
}
}
ProductAdapter
class ProductAdapter : RecyclerView.Adapter<ProductViewHolder>() {
var movies = mutableListOf<MobileList>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterLayoutBinding.inflate(inflater, parent, false)
return ProductViewHolder(binding)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val movie = movies[position]
holder.binding.name.text = movie.products.get(position).name
Glide.with(holder.itemView.context).load(movie.products.get(position).image_url).into(holder.binding.imageview)
}
override fun getItemCount(): Int {
return movies.size
}
}
class ProductViewHolder(val binding: AdapterLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
}
Repository class
class ProductRepository (private val productService: ProductService) {
private val productLiveData = MutableLiveData<MobileList>()
val products:LiveData<MobileList>
get() = productLiveData
suspend fun getProducts(){
val products = productService.getQuotes()
if(products?.body()!=null)
{
productLiveData.postValue(products.body())
}
}
}
ViewModel
class ProductViewModel (private val productRepository: ProductRepository ) :ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO){
productRepository.getProducts()
}
}
val products : LiveData<MobileList>
get() = productRepository.products
}
Factory
class ProductViewModelFactory (private val productRepository: ProductRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ProductViewModel (productRepository) as T
}
}
Model
data class MobileList(
val products: List<Product>
)
data class Product(
val image_url: String,
val name: String,
val price: String,
val rating: Int
)
JSON Response
{
"products": [
{
"name": "test",
"price": "34999",
"image_url": "url",
"rating": 4
},
{
"name": "test2",
"price": "999",
"image_url": "url",
"rating": 4
},]}
First of all make sure you have layoutManager set on the RecyclerView.
The problem here is Your ProductAdapter never had the data . notifyDataSetChanged is not a magic stick to notify the adapter you modify/add/update the dataset and then You will call notifyDataSetChanged . that's how it works .
In your case You have movies list your adapter but you never assigned anything to it its always empty .
There are several ways to it. Just to make it work You can have a method to add the data in your adapter and then notify it.
fun addData(data:List<MobileList>){
movies.addAll(data)
notifyDataSetChanged()
}
Now when you get the data inside on change you call this method .
productViewModel.products.observe(this,{
it?.let{ items ->
adapter.addData(items)
}
})
This should work .
Update on type fix - Seems like your type is messed up . Why is your repo returns a object of MobileList? While you are using a list of MobileList in adapter . Your adapter should hold var movies = mutableListOf<Products>().
productViewModel.products.observe(this,{
it?.let{ item ->
adapter.addData(item.products)
}
})
I have a problem in my app where I need to display an image with recyclerview but I'm getting a null object.
class AllSubscribersAdapter(val context: Context, private val subsribedChannels: SubscribedChannels): RecyclerView.Adapter<AllSubscribersAdapter.ViewHolder>() {
private var channels: List<Channels> = listOf()
init {
channels = subsribedChannels.channels
}
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.subscribers_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return channels.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvChannelName.text = channels[position].name
Glide.with(holder.channel_img)
.load(channels[position].avatar)
.transform(CircleCrop())
.into(holder.channel_img)
/*Picasso.get()
.load(channels[position].avatar)
.fit()
.into(holder.channel_img)*/
}
fun setChannelsList(channels: List<Channels>){
this.channels = channels
notifyDataSetChanged()
}
class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
var tvChannelName: TextView = itemView!!.findViewById(R.id.channel_name)
var channel_img: ImageView = itemView!!.findViewById(R.id.channel_image)
}
}
channel_name is working but channel_image is null, any help is appreciated. Here is my model class.
class SubscribedChannels (
var channels: List<Channels>
): Serializable
data class Channels(
var id: String,
var name: String,
var slug: String,
var avatar: String,
var created_by: String,
var subscribers: String
): Serializable
I don't know if there is a problem here.
First thing you cannot initialize variable in ViewHolder class.
You should do it in constructor just like this.
var tvChannelName: TextView?=null
var channel_img: ImageView?=null
init{
tvChannelName = itemView!!.findViewById(R.id.channel_name)
channel_img = itemView!!.findViewById(R.id.channel_image)
}
And if you are getting empty string in your retrofit response then you have check string then you have to call glide.
At the bottom holder.tvChannelName.text you can log chanels. Avatar if is not empty you can check the value have the valid image url
I got the above-mentioned error when I try to create a custom adapter class in Kotlin
Source code
MainActivity.kt
var adapterC:CustomAdapter = CustomAdapter(this,Statearray)
spinnerState.adapter=adapterC
CustomAdapter.kt
class CustomAdapter(val activity: Activity,val array:JSONArray) : BaseAdapter(), ListAdapter
{
lateinit var ItemName: TextView
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
var view=convertView
if (view == null)
view = activity.layoutInflater.inflate(R.layout.spinnerlayout, null)
try {
ItemName = view?.findViewById(R.id.ItemName) as TextView
val obj = array.getJSONObject(position)
ItemName.setText(obj.getString("Name"))
view.setTag(obj.getString("Id"))
} catch ( e:JSONException) {
Log.e("At Custom Class",e.toString())
}
return view
}
override fun getItem(position: Int): JSONObject {
return array.optJSONObject(position)
}
override fun getItemId(position: Int): Long {
var jsonObject=getItem(position)
return jsonObject.optLong("id")
}
override fun getCount(): Int {
return array.length()
}
}
Need Help I don't know what I did wrong.
I finally figure it out.. the problem was, iwas using a spinner library which only support ArrayAdapter
the library that i used was
com.toptoche.searchablespinner:searchablespinnerlibrary:1.3.1
it only support arrayadapter
ClassCastException
Thrown to indicate that the code has attempted to cast an object to a
subclass of which it is not an instance.
FYI
You added Multiple Adapter. Remove 2nd One.
Don't
class CustomAdapter(val activity: Activity,val array:JSONArray) : BaseAdapter(), ListAdapter
Do
class CustomAdapter(context: Context,var arrayLIST: ArrayList<Response>) : BaseAdapter() {
DEMO
var arrayLIST: ArrayList<Response>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
arrayLIST=ArrayList()
val jsonObj = ("[{\"Id\":\"35\",\"Name\":\"Kerala\"},{\"Id\":\"36\",\"Name\":\"Tamilnadu\"}]")
val jo = JSONArray(jsonObj)
val num = 0 until jo.length()
for (i in num) {
val loanObj = jo.getJSONObject(i)
val Id = loanObj.getString("Id")
val Name = loanObj.getString("Name")
arrayLIST!!.add(Response(Id,Name))
}
var adapterC:CustomAdapter = CustomAdapter(this#MainActivity,arrayLIST)
Response.kt
data class Response
(
#SerializedName("id") val id : String,
#SerializedName("name") val name : String
)
NOTE
Make sure add,
implementation "com.google.code.gson:gson:2.3.0"
implementation "com.squareup.retrofit2:converter-gson:2.3.0"
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" -> {}
}
}