I have 3 similars adapters and I want to combine them in 1. The only thing that is different is the ArrayList type. I have 2 room entities for room, FavoriteArticle, and HistoryArticle, but they have the same fields as WikiPage.
class Adapter<T> (#LayoutRes private val layoutRes: Int) : RecyclerView.Adapter<Holder>() {
val currentResult: ArrayList<T> = ArrayList()
override fun getItemCount() = currentResult.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val cardItem = LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
return Holder(cardItem)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val page = currentResult[position]
holder.updateWithPage(page)
}
fun add(wiki: ArrayList<T>?) { {
currentResult.clear()
if (wiki != null) {
currentResult.addAll(wiki)
}
}
}
Holder class:
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val articleImageView: ImageView = itemView.findViewById(R.id.articleCardItemImage)
private val titleTextView: TextView = itemView.findViewById(R.id.articleCardItemTitle)
private lateinit var currentPage: WikiPage
init {
itemView.setOnClickListener {
itemView.context.startActivity(ArticleDetailActivity.createStartIntent(itemView.context, currentPage))
}
}
fun updateWithPage(page: WikiPage) {
currentPage = page
titleTextView.text = page.title
Picasso.get().load(page.thumbnail.source).into(articleImageView)
}
}
I can't figure out how to modify my updateWithPage function from onBindViewHolder using generics. How can I do this?
Some idea for your reference by using Interface:
interface Article {
getWikiPage(): WikiPage
}
class FavoriteArticle : Article {
getWikiPage() = wikiPage
}
class HistoryArticle: Article {
getWikiPage() = wikiPage
}
// Adapter
class Adapter<Article>
...
override fun onBindViewHolder(holder: Holder, position: Int) {
val page = currentResult[position].getWikiPage()
holder.updateWithPage(page)
}
...
You can make a generalized adapter that can accept any viewholder class. This will require pushing the viewholder creation logic inside the viewholder class itself. Using some kotlin extension functions can even be a lot cleaner for this use case.
Check out this link: https://thecommonwise.com/blogs/60f6ea9bea3d10001503eac3
Related
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.
I have an app which uses Room Database to show data in recycleview. It works fine when i load data seperately from different tables. But i want to show data from both tables in a single recycleview with multiple viewtypes, i know how to combine tables in room but it's not working. I get empty cards in recycleview when i load the data. Here is what i have tried so far.
My Adapter Class
class CategoriesAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val TYPE_CATEGORIES = 0
private const val TYPE_ARTICLES = 1
}
private val items: MutableList<Any> by lazy {
ArrayList<Any>()
}
fun setItems(list: List<Any>) {
items.addAll(list)
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (items[position] is Categories) TYPE_CATEGORIES else TYPE_ARTICLES
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_CATEGORIES -> CategoriesViewHolder.create(viewGroup)
else -> ArticlesViewHolder.create(viewGroup)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is CategoriesViewHolder -> {
if (items[position] is Categories)
holder.bind(items[position] as Categories)
}
is ArticlesViewHolder -> {
if (items[position] is Articles)
holder.bind(items[position] as Articles)
}
}
}
override fun getItemCount(): Int {
return items.size
}
}
class CategoriesViewHolder (parent: View) : RecyclerView.ViewHolder(parent) {
val textView: TextView = parent.findViewById(R.id.categories_textView)
fun bind(category: Categories) {
textView.text = category.categoryName
}
companion object {
fun create(parent: ViewGroup): CategoriesViewHolder {
return CategoriesViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.categories_item_layout, parent, false))
}
}
}
class ArticlesViewHolder (parent: View) : RecyclerView.ViewHolder(parent) {
val textView: TextView = parent.findViewById(R.id.titleText)
fun bind(articles : Articles) {
textView.text = articles.articleName
}
companion object {
fun create(parent: ViewGroup): ArticlesViewHolder {
return ArticlesViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.article_item_layout, parent, false))
}
}
}
this is how i set data from my activity
val db = AppDatabase.getDatabase(applicationContext)
dao = db.articleDao()
val recyclerView = findViewById<RecyclerView>(R.id.categories_recycle_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = CategoriesAdapter()
adapter.setItems(dao.getAllArticlesAndCategories())
Can anyone help.
P.s i'm new to kotlin
Instead of
adapter.setItems(dao.getAllArticlesAndCategories())
Use live data observer to avoid processing on main thread and debug in observe function of live data to confirm you are receiving correct data from DB.
calling code one line of code is missing
val db = AppDatabase.getDatabase(applicationContext)
dao = db.articleDao()
val recyclerView = findViewById<RecyclerView>(R.id.categories_recycle_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = CategoriesAdapter()
adapter.setItems(dao.getAllArticlesAndCategories())
it should be:
val db = AppDatabase.getDatabase(applicationContext)
dao = db.articleDao()
val recyclerView = findViewById<RecyclerView>(R.id.categories_recycle_view)
recyclerView.layoutManager = LinearLayoutManager(this)
adapter=CategoriesAdapter()
adapter.setItems(dao.getAllArticlesAndCategories())
recyclerView.adapter = adapter
I would like to thank for the question and the code
For some reason, the context in my RecyclerView adapter is not being read when trying to apply a click listener to my RecyclerView item to start a new activity. myContext.startActivity(intent) returns an 'unresolved reference' error. What should it be changed to in order for the context to be read? Do I need to use the context of the view holder or the TextView or something else?
Unresolved reference: myContext
class MyAdapter(
val myContext: Context,
var listCompany: MutableList<ItemCompany>,
private val mTwoPane: Boolean,
private val itemClickListener: AdapterView.OnItemClickListener
) : androidx.recyclerview.widget.RecyclerView.Adapter<MyAdapter
.CompanyViewHolder>() {
class CompanyViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView
.ViewHolder(itemView) {
var tvTitle: TextView = itemView.findViewById(R.id.tv_RVItem)
fun bind(company: ItemCompany, clickListener: AdapterView.OnItemClickListener)
{
tvTitle.text = company.companyName
itemView.setOnClickListener {v ->
val intent: Intent = when (company.companyName) {
v.resources.getString(R.string.bing) -> {
Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.bing.com/"))
}
v.resources.getString(R.string.google) -> {
Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.google.com/"))
}
else -> {
Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.yahoo.com/"))
}
}
myContext.startActivity(intent)
clickListener.onItemClick(company)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CompanyViewHolder {
val inflater = LayoutInflater.from(myContext)
val v = inflater.inflate(R.layout.rv_item, parent, false)
return CompanyViewHolder(v)
}
override fun onBindViewHolder(holder: CompanyViewHolder, position: Int) {
val product = listCompany[holder.adapterPosition]
holder.tvTitle.text = product.companyName
holder.bind(product, itemClickListener)
}
override fun getItemCount(): Int {
return listCompany.size
}
override fun getFilter(): Filter {
return companyFilter
}
}
In Kotlin, nested class is by default static, so its data member and member function can be accessed without creating an object of class. Nested class cannot be able to access the data member of outer class.
Since "myContext" is data member of Outer Class ~ "MyAdapter" that's why it's unresolved in Inner Class.
I want to create an android app with Kotlin. In this app, i use swagger also to get all the web service in a file.
I want to create an interface, the description is as follows:
A RecyclerView horizontal that contains all the list of categories
comes from a web service apiMobileProductCategoriesGetAllPost.
after that, when i click on a which category, a RecyclerView(Grid)
appear that contains all the product by category id.
I want to know how can i get the category id when i click on item,and how to use it in the activity
The following the RecyclerView category adapter:
class CategoryAdapter(private val categories: Array<ProductCategoryData>) :
RecyclerView.Adapter<CategoryAdapter.ViewHolder>(), View.OnClickListener {
private var onItemClickListener: OnItemClickListener? = null
override fun onClick(v: View?) {
if (v != null) {
onItemClickListener?.onItemClick(v, ProductCategoryData())
}
}
fun setOnItemClickListener(onItemClickListener: OnItemClickListener) {
this.onItemClickListener = onItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.category_item, parent, false)
view.setOnClickListener(this)
return ViewHolder(view)
}
override fun getItemCount() = categories.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemCategory: ProductCategoryData = categories[position]
holder.categoryId.text = itemCategory.id.toString()
println(holder.categoryId.text)
println(itemCategory.name?.get("En").toString())
holder.categoryName.text = itemCategory.name?.get("En").toString()
println(itemCategory.id)
if (itemCategory.logo != null) {
Picasso.get()
.load("..../T/${itemCategory.logo}")
.into(holder.categoryImage, object : com.squareup.picasso.Callback {
override fun onError(e: Exception?) {
holder.categoryImage.setImageResource(R.drawable.homecraftdefault)
}
override fun onSuccess() {
Picasso.get().load("....T/${itemCategory.logo}")
.into(holder.categoryImage)
}
})
holder.itemView.setOnClickListener {
onItemClickListener?.onItemClick(holder.itemView,itemCategory)
}
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val categoryName: TextView = itemView.categoryName
val categoryImage: ImageView = itemView.categoryImage
val categoryId: TextView = itemView.categoryId
override fun onClick(v: View?) {
if (v != null) {
onItemClickListener?.onItemClick(v, ProductCategoryData())
}
}
}
interface OnItemClickListener {
fun onItemClick(view : View, viewModel:ProductCategoryData)
}
}
The following code is relative to the activity:
class CategoryByProduct : AppCompatActivity(), CategoryAdapter.OnItemClickListener {
override fun onItemClick(view: View, viewModel: ProductCategoryData) {
var params = "CategoryProductID";"5cc057458c4d9823743736d2"
println(viewModel.id)
val products = mobileApi!!.apiMobileProductsGetAllPost(params, 0, 50, "", "")
recyclerViewProductByCategory.apply {
recyclerViewProductByCategory.layoutManager = GridLayoutManager(this#CategoryByProduct, 2)
recyclerViewProductByCategory.adapter = ProductAdapter(products)
} }
var mobileApi: MobileApi? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.list_product_by_category)
mobileApi = MobileApi()
val params = HashMap<String, String>()
GlobalScope.launch(Dispatchers.IO) {
val categories = mobileApi!!.apiMobileProductCategoriesGetAllPost(params, 0, 50, "", "")
withContext(Dispatchers.Main) {
recyclerViewCategories.apply {
recyclerViewCategories.layoutManager =
LinearLayoutManager(this#CategoryByProduct, OrientationHelper.HORIZONTAL, false)
recyclerViewCategories.adapter = CategoryAdapter(categories)
}
}
}
}
}
First of all , never put your onclick in onBindViewHolder That's not a good practice, after that i think you need to get the ID of the category i will give you simple example in all of the Adapter Class
class NewsAdapter (val context: Context, private val arrayList: ArrayList <NewsModel>):
RecyclerView.Adapter <NewsAdapter.Holder> () {
companion object {
// val TAG: String = OperationAdapter::class.java.simpleName
}
override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): Holder {
return Holder (LayoutInflater.from (parent.context ).inflate (R.layout.newslist , parent, false))
}
override fun getItemCount (): Int = arrayList. size
override fun onBindViewHolder (holder: Holder, position: Int) {
val mynews = arrayList[position]
holder.setData(mynews , position)
}
inner class Holder (itemView: View): RecyclerView.ViewHolder (itemView) {
private var currentnews: NewsModel? = null
private var currentPosition: Int = 0
init {
//The click listener
itemView.newscardview.setOnClickListener {
//do it here
Toast.makeText(this,currentnews!!.id,Toast.LENGTH_SHORT).show()
}
//the end of the init
}
//getting data from model and bind it into View
fun setData(news: NewsModel?, position: Int) {
news?.let {
itemView.newsid.text = news.id
itemView.newstitle.text = news.title
itemView.body.text = news.body
itemView.txtdate.text = news.ndate
}
this.currentnews = news
this.currentPosition = position
}
}
}
In this example you will get the news ID when you click newscardview, i hope to understand it
In your Activity
put this code in onCreate
//set up the recycleview
mRecyclerView.setHasFixedSize (true)
mRecyclerView. layoutManager = LinearLayoutManager(this)
mRecyclerView is my RecycleView
also call your Adapter class in anywhere you want
//adapter
val adapter = NewsAdapter (this,arrayList)
adapter.notifyDataSetChanged()
mRecyclerView.adapter = adapter
you get the position of the category inside a viewholder by calling adapterPosition and with this you can get the category from your list you provide to your adapter in the constructor (categories[adapterPosition])
In your case it is very simple.Try these:-
holder.tv_broadcast_title.text=broadList[position].name
where broadlist is my array list created in the adapter itself.In this list the json data is getting stored from api.
internal var broadList = ArrayList<Model>()
and .name is the name of key to fetch name from json data.
holder.categoryName.text = itemCategory.name?.get("En").toString()
in your case do something like this:-
itemCategory[position].name
To get data from adapter to activity, you can make an interface in the adapter or globally and from the activity you can pass that interface in adapter's constructor and use that to get data. I am giving you an example.
interface ProductCategoryListner {
fun getProductCategory(viewModel:ProductCategoryData)
}
Not in adapter's constructor add this interface.
class CategoryAdapter(private val categories: Array<ProductCategoryData>,private val productCategoryListner: ProductCategoryListner):
RecyclerView.Adapter<CategoryAdapter.ViewHolder>(), View.OnClickListener {
Now you can use this to pass data in the activity when you click on view.
productCategoryListner.getProductCategory(categories[adapterPosition])
I have looked for solutions to this problem but cannot find an answer.
I can get my onClickListener to work (Kotlin) from inside the onBindViewHolder of my Adapter but the onLongClickListener (Kotlin) does not respond, even though the code is not showing an error
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val ingredientdisplay=displayItems[position]
holder.setData(ingredientdisplay,position)
runningTotal=runningTotal+holder.itemView.tvcost.text.toString().toDouble()
println ("running total $runningTotal")
val intent = Intent("message_from_displayadapter")
intent.putExtra("runningtotal", runningTotal)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
holder.itemView.setOnLongClickListener {view->
println("longclick")
true
}
holder.itemView.setOnClickListener {
val intent = Intent(context, ChooseIngredientsActivity::class.java)
ContextCompat.startActivity(context, intent, null)
}
}
I am just trying to println or run a Toast but nothing happens.
I don't understand why? Your help would be appreciated.
First don't put your onClicklistiner in onBindViewHolder it's not good practice, use update your Adapter Class like this one also OnLongclicklistiner is working fine
class NewsAdapter (val context: Context, private val arrayList: ArrayList
<NewsModel>):
RecyclerView.Adapter <NewsAdapter.Holder> () {
companion object {
// val TAG: String = OperationAdapter::class.java.simpleName
}
override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): Holder {
return Holder (LayoutInflater.from (parent.context ).inflate (R.layout.newsitemlist , parent, false))
}
override fun getItemCount (): Int = arrayList. size
override fun onBindViewHolder (holder: Holder, position: Int) {
val mynews= arrayList[position]
holder.setData(mynews, position)
}
inner class Holder (itemView: View): RecyclerView.ViewHolder (itemView) {
private var currentnews: NewsModel? = null
private var currentPosition: Int = 0
init {
//The click listener
itemView.cardview.setOnClickListener {
val i = Intent(context,NewsReaderActivity::class.java)
i.putExtra("body",currentnews!!.body)
i.putExtra("title",currentnews!!.title)
context.startActivity(i)
}
itemView.cardview.setOnLongClickListener{
Toast.makeText(context,"Long Clicked",Toast.LENGTH_SHORT).show()
true
}
//the end of the init
}
//getting data from model and bind it into View
fun setData(news: NewsModel?, position: Int) {
news?.let {
itemView.newstitle.text = news.title
itemView.body.text = news.body
itemView.txtdate.text = news.ndate
}
this.currentnews = news
this.currentPosition = position
}
}
}
I have moved the listeners to the inner class the code for the complete adaper is below. Still the onLongClicListener is not responding.
class RecipiesDisplayAdapter(val context:Context, val displayItems:List):RecyclerView.Adapter(){
var runningTotal:Double=0.00
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view= LayoutInflater.from(context).inflate(R.layout.recipedisplay_list,parent,false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return displayItems.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val ingredientdisplay=displayItems[position]
holder.setData(ingredientdisplay,position)
runningTotal=runningTotal+holder.itemView.tvcost.text.toString().toDouble()
println ("running total $runningTotal")
val intent = Intent("message_from_displayadapter")
intent.putExtra("runningtotal", runningTotal)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
inner class MyViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
private var currentIngredientDisplay:IngredientDisplay?=null
private var currentPosition:Int=0
init {
itemView.setOnLongClickListener {
Log.i ("clicked ", "longclick")
context.showToast("longClicked")
true
}
itemView.setOnClickListener {
val intent = Intent(context, ChooseIngredientsActivity::class.java)
ContextCompat.startActivity(context, intent, null)
}
}
fun setData(ingredientdisplay: IngredientDisplay?, pos:Int){
val fromrate=getfromdb(ingredientdisplay!!.unitPurchased)
val torate =getfromdb(ingredientdisplay!!.unitPerRecipe)
val minunitcost= ingredientdisplay!!.costPurchased/ingredientdisplay!!.quantityPurchased/fromrate
val costperrecipe=minunitcost*ingredientdisplay!!.quantityPerRecipe*torate
val s = String.format( "%.2f", costperrecipe);
val pdu=ingredientdisplay!!.quantityPerRecipe.toString()+" "+ingredientdisplay!!.unitPerRecipe
ingredientdisplay?.let {
itemView.tvIngredientName.text = ingredientdisplay!!.ingredientName
itemView.tvcost.text = s
itemView.tvqty.text = pdu
}
this.currentPosition=pos
this.currentIngredientDisplay=ingredientdisplay
}
fun getfromdb (unit:String) :Double{
var sendback:String=""
context.database.use {
select("Units", "convertionrate")
.`whereSimple`("unitname = ?", unit)
.exec {
parseList(DoubleParser).forEach{
println("result $it")
val resu= it.toString()
sendback=resu
}
}
}
return sendback.toDouble()
}
}
}
I cannot figure out why!
The problem is solved. I made a rookie mistake. I added all the onclicklisteners to my adapters. Then later started to add onlongclicklisteners to them. Belive it or not. I was setting it in the wrong adapter. Appologies for your trouble however your answers where helpful.
context does not work sometimes or in the newer version
so you can use also
Toast.makeText(it.context, "Long Clicked", Toast.LENGTH_SHORT).show()