I have an adapter in which the items each have 3 buttons, that generate a dialog that then performs an action. I have a sense that this should be removed from the adapter (I have view models available), but it works and I am wondering: Should I move logic to the fragment, to the view model, do I need to move it at all (is the code below bad practice and if so why)? Any help/input would be greatly appreciated.
Here is the adapter code:
class ViewRecipesAdapter(val context: Context, private val recipes: List<Recipe>, private val parentFragment: Fragment) :
RecyclerView.Adapter<ViewRecipesAdapter.RecipeViewHolder>()
{
private var listToUse: List<Recipe> = recipes
private lateinit var recipesViewModel: RecipesViewModel
private var isView = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ViewRecipesItemBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.view_recipes_item, parent, false)
return RecipeViewHolder(binding, context)
}
override fun getItemCount() = listToUse.size
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int)
{
val recipe = listToUse[position]
// to delete and edit items
val dao = RecipesDatabase.getInstance(context).recipeDao()
val repository = RecipeRepository(dao)
recipesViewModel = RecipesViewModel(repository)
//display data on list item
holder.bind(recipe)
Glide.with(context).load(recipe.imageOne)
.into(holder.binding.imageViewItemImage)
//tried to handle clicks here through the viewModel but I could not get it working from fragment
//the function call after viewModel calls is what works and it seems to work well
holder.binding.imageButtonItemdelete.setOnClickListener {
recipesViewModel.setIsDelete(true)
recipesViewModel.setPositionFromAdapter(position)
startDeleteDialog(position)
}
holder.binding.imageButtonItemedit.setOnClickListener {
recipesViewModel.setIsView(false)
recipesViewModel.setPositionFromAdapter(position)
isView = false
startEditOrViewDialog(position)
}
holder.binding.imageButtonItemview.setOnClickListener {
recipesViewModel.setIsView(true)
recipesViewModel.setPositionFromAdapter(position)
isView = true
startEditOrViewDialog(position)
}
}
fun setList(newList: List<Recipe>)
{
listToUse = newList
}
//dialog functions for the edit, delete, and view buttons on each item
private fun startDeleteDialog(position: Int)
{
AlertDialog.Builder(context)
.setTitle("Delete recipe?")
.setPositiveButton("Yes") { _, _ ->
recipesViewModel.deleteRecipe(recipes[position])
notifyItemRemoved(position)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
private fun startEditOrViewDialog(position: Int)
{
when (isView)
{
true ->
{
AlertDialog.Builder(context).setTitle("View recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
//create a dialog that shows this data in an inflated layout
val viewDialog = AlertDialog.Builder(context)
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.fragment_edit_or_view, null)
view.editText_editrecipe_directions.setText(recipe.directions)
view.editText_editrecipe_ingredients.setText(recipe.ingredients)
view.editText_editrecipe_notes.setText(recipe.notes)
view.editText_editrecipe_title.setText(recipe.title)
view.textView_date_edit.text = recipe.date
view.editText_editrecipe_title.keyListener = null
view.editText_editrecipe_directions.keyListener = null
view.editText_editrecipe_ingredients.keyListener = null
view.editText_editrecipe_notes.keyListener = null
if (recipe.rating != null)
{
view.ratingBar_edit.rating = recipe.rating
}
Glide.with(context)
.load(recipe.imageOne)
.into(view.imageView_addphoto_edit)
viewDialog.setView(view).show()
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
false ->
{
AlertDialog.Builder(context).setTitle("Edit recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
val idString = recipe.id.toString()
recipesViewModel.setId(idString)
recipesViewModel.getRecipeById2(idString)
notifyDataSetChanged()
val controller = parentFragment.findNavController()
controller.navigate(
ViewRecipesFragmentDirections.actionNavViewrecipesToNavAddrecipe(
recipe.id.toString()
)
)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
}
}
override fun getItemId(position: Int): Long
{
return position.toLong()
}
override fun getItemViewType(position: Int): Int
{
return position
}
class RecipeViewHolder(val binding: ViewRecipesItemBinding, val context: Context) :
RecyclerView.ViewHolder(binding.root)
{
fun bind(recipe: Recipe)
{
if (recipe.isLeftover == true)
{
binding.tvIsLeftovers.visibility = View.VISIBLE
}
binding.textViewItemTitle.text = recipe.title
if (recipe.date != null)
{
binding.textViewItemDate.text = recipe.date
}
if (recipe.rating != null)
{
binding.ratingBar2.rating = recipe.rating
}
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
}
}
This is the view model, with live data variables set up that I could not get working in the fragment that this RecyclerView is in:
class RecipesViewModel(private val repository: RecipeRepository) : ViewModel()
{
val recipesList = repository.getAllRecipes()
private val _isView = MutableLiveData<Boolean>()
val isView: MutableLiveData<Boolean> = _isView
private val _isEdit = MutableLiveData<Boolean>()
val isEdit: MutableLiveData<Boolean> = _isEdit
private val _positionFromAdapter = MutableLiveData<Int>()
val positionFromAdapter: MutableLiveData<Int> = _positionFromAdapter
private val _isDelete = MutableLiveData<Boolean>()
val isDelete: MutableLiveData<Boolean> = _isDelete
private val _recipesListFromSearch = MutableLiveData<List<Recipe>>()
val recipesListFromSearch: LiveData<List<Recipe>> = _recipesListFromSearch
private val _recipe = MutableLiveData<Recipe>()
val recipe: LiveData<Recipe> = _recipe
lateinit var searchString: String
val savedId = MutableLiveData<String>()
fun setPositionFromAdapter(position: Int)
{
_positionFromAdapter.value = position
}
fun setIsView(isView: Boolean)
{
_isView.value = isView
}
fun setIsDelete(isDelete: Boolean)
{
_isView.value = isDelete
}
fun setIsEdit(isEdit: Boolean)
{
_isEdit.value = isEdit
}
fun setId(id: String)
{
savedId.value = id
}
fun insertRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.insertRecipe(recipe)
}
}
fun getRecipesFromQuery(query: String)
{
CoroutineScope(Dispatchers.IO).launch {
val list = repository.getRecipesSearch(query)
MainScope().launch { _recipesListFromSearch.value = list }
}
}
fun saveUserRecipeToDb(
title: String?,
ingredients: String?,
directions: String?,
notes: String?,
uriToSave: String?,
rating: Float?,
date: String?,
isLeftover: Boolean,
loadedId: String
): Boolean
{
val recipeToSave = Recipe(
title,
ingredients,
directions,
notes,
uriToSave,
null,
null,
rating,
date,
isLeftover
)
if (loadedId != "666")
{
recipeToSave.id = loadedId.toInt()
}
insertRecipe(recipeToSave)
return false
}
fun getRecipeById2(id: String) = repository.getRecipeByIdLive(id)
fun deleteRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.deleteRecipe(recipe)
}
}
}
How to implement onClick in the RecyclerView. Let's assume that in Your Recycler every view is a visualization of some item and when You click on it You want to do something with that item:
Create class: ClickListener:
class ClickListener(
val clickListener: (itemId: Int) -> Unit,
)
{
fun onClick(item: ItemClass) = clickListener(item.id)
}
Now in Your RecylerViewAdapter pass as an argument this Listener:
class RecylerViewAdapter(
private val clickListener: ClickListener
)
In onBindViewHolder pass this Listenner as argument
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
{
return ViewHolder.from(
parent
)
}
In Your ViewHolder class:
class ViewHolder private constructor(private val binding: ItemRecyclerBinding) :
RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun from(parent: ViewGroup): ViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemRecyclerBinding.inflate(layoutInflater, parent, false)
return ViewHolder(
binding
)
}
}
fun bind(
item : Item,
clickListener: ClickListener
)
{
binding.item = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
}
In Your item layout (which has to be converted to data binding layout) add this:
<data>
<variable
name="item"
type="com.example.sth.database.Item" /> // path to `Item`
<variable
name="clickListener"
type="com.example.sth.ui.adapter.ClickListener" /> // Path to `ClickListener`
</data>
Now You can add onClick method to Button:
android:onClick="#{() -> clickListener.onClick(item)}"
When You create Adapter in fragment or Activity You have to pass clickListenner as a parameter. In this way You can handle everything from fragment and RecyclerView doesn't care about what You do in this function.
val clickListenner = ClickListenner(
{ id -> viewModel.clickItemWithid(id) }, // click. This function from ViewModel will be executed when You click on item in recycler View
)
val adapter = RecylerViewAdapter (
clickListenner
)
This method is based on Google developers codelabs on Udacity.
Here You can check whole codelabs. It is free.
And here is just one video with implementing click listenner
These are the changes that are working for me now:
class ClickListener(val clickListener: (itemId: Int, itemPosition: Int, dialogInt: Int) -> Unit) {
fun onClickDelete(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickEdit(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickView(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
}
In the adapter:
class RecipeViewHolder private constructor(val binding: ViewRecipesItemBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup): RecipeViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ViewRecipesItemBinding.inflate(layoutInflater, parent, false)
return RecipeViewHolder(binding)
}
}
fun bind(recipe: Recipe, clickListener: ClickListener) {
binding.recipe = recipe
binding.imageButtonItemdelete.setOnClickListener {
clickListener.onClickDelete(recipe, adapterPosition, 1)
}
binding.imageButtonItemedit.setOnClickListener {
clickListener.onClickEdit(recipe, adapterPosition,2)
}
binding.imageButtonItemview.setOnClickListener {
clickListener.onClickView(recipe, adapterPosition,3)
}
binding.executePendingBindings()
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
In the fragment holding the RecyclerView :
private fun initRecyclerView() {
recipesViewModel.recipesList.observe(viewLifecycleOwner, Observer {
//update recyclerview
val list = it
listForFragment = it
clickListener = ClickListener { id, position, dialogInt ->
recipesViewModel.apply {
setPositionFromAdapter(position)
setDialogRecipe(id)
}
when (dialogInt) {
1 -> startDeleteDialog(position)
2 -> startEditDialog(position)
3 -> startViewDialog(position)
}
}
rv_viewrecipes.adapter = ViewRecipesAdapter(requireContext(), list, this, clickListener)
rv_viewrecipes.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
})
}
I couldn't figure out how to pass the position and int for the when statement through the xml onClick, but for now at least the adapter class has the view logic removed.
Related
I have viewModel class which name UserListViewModel And on that class there is function which named sumUserIncrease and I want to get that functions value and send that to adapter to show in recyclerview I hope you got what I mean if not, take look at this:
here is my userListViewModel:
class UserListViewModel(
val mUserInfoDAO: UserDAO,
val mTransactionDAO: TransactionsDAO,
val mLoanDAO: LoanDAO,
val mBankDAO: BankDAO,
application: Application
) :
AndroidViewModel(application) {
var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun sumAllIncrease(id: Long): Long {
return mTransactionDAO.sumUserIncrease(id)
}
fun sumAllDecrease(id: Long): Long {
return mTransactionDAO.sumUserDecrease(id)
}
}
my Adapter:
package com.example.holyquran.ui.userList
class UserAdapter() : ListAdapter<UserInfo, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val item = getItem(position)
holder.bind(item, clickListener)
}
is EmptyViewHolder -> {
holder.bind()
}
}
}
lateinit var clickListener: AdapterListener
fun setOnclickListener(listener: AdapterListener) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return if (itemCount > 0)
ITEM_VIEW_TYPE_ITEM
else
ITEM_VIEW_TYPE_EMPTY
}
class ViewHolder private constructor(val binding: ItemUserListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: UserInfo, adapterListener: AdapterListener) {
if (item.gender == "مرد") {
binding.img.setImageResource(R.drawable.user_avata_male);
}else{
binding.img.setImageResource(R.drawable.user_avatar_female);
}
binding.userInfo = item
binding.clickListener = adapterListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<UserInfo>() {
override fun areItemsTheSame(oldItem: UserInfo, newItem: UserInfo): Boolean {
return oldItem.userId == newItem.userId
}
override fun areContentsTheSame(
oldItem: UserInfo,
newItem: UserInfo
): Boolean {
return oldItem == newItem
}
}
class AdapterListener(
val clickListener: (id: Long) -> Unit,
val deleteListener: (userInfo: UserInfo) -> Unit,
private val longClickListener: (id: Long) -> Unit
) {
fun onclick(userInfo: UserInfo) = clickListener(userInfo.userId)
fun onDeleteClick(userInfo: UserInfo) = deleteListener(userInfo)
fun onLongClick(userInfo: UserInfo) = longClickListener(userInfo.userId)
}
Have your UserListViewModel contain LiveData that your Fragment/Activity observes. Once it gets an update it will send it to the Adapter.
In ViewModel
private val currentSum : MutableLiveData<Int> = MutableLiveData(0)
fun sumAllIncrease(id: Long): Long {
var sum = mTransactionDAO.sumUserIncrease(id)
currentSum.value = sum
return sum
}
fun sumAllDecrease(id: Long): Long {
var sum = mTransactionDAO.sumUserDecrease(id)
currentSum.value = sum
return sum
}
fun getCurrentSum(): LiveData<Long> {
return currentSum
}
In Fragment/Activity
viewModel.getCurrentSum().observe(this, Observer {
adapter.setSum(it)
})
In Adapter
fun setSum(sum : Long){
//The sum is now in you adapter.
//Use it how you need too.
}
But this is kinda unusual. What you really want to do is pass a new UserInfo with the changed sum. And let the DiffCallback change the one that is different. What you probably want to do is implement Paging https://proandroiddev.com/paging-3-easier-way-to-pagination-part-1-584cad1f4f61 This will allow you to change stuff in the database anywhere in the app. When that happens the data will be updated in the Adapter. Its kinda complex but once you get it working you want regret it.
I am trying to show multiple type of views in a recyclerview with a header and 5 rows followed by another header and 5 rows but only 4 rows are appearing.
Here is the log output from the adapter in the onBindViewHolder
Here is the code from the adapter
class DailyFragAdapter(
private val activityData: (DetailActivityData) -> Unit,
private val dates: List<Date>,
private val list : ArrayList<TabType1>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
/*
ListAdapter<TabType1, RecyclerView.ViewHolder>(Diff())*/ {
private lateinit var context: Context
private val HEADER = 1
private val ROW = 2
/*private class Diff : DiffUtil.ItemCallback<TabType1>() {
override fun areItemsTheSame(oldItem: TabType1, newItem: TabType1): Boolean {
return oldItem.header == newItem.header && oldItem.cps.cps[0].id == newItem.cps.cps[0].id
}
override fun areContentsTheSame(
oldItem: TabType1,
newItem: TabType1
): Boolean {
return oldItem.hashCode() == newItem.hashCode()
}
}*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
context = parent.context
val layoutInflater = LayoutInflater.from(context)
return if (viewType == 2) {
DVH(DailyFragRecyclerItemBinding.inflate(layoutInflater, parent, false))
} else {
TitleHolder(DailyTableHeaderBinding.inflate(layoutInflater, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data: TabType1 = list[position]
if (holder is DVH) {
data.cps.cps.forEach {
Timber.i("is: a row")
holder.bind(it)
}
}else{
Timber.i("is: header")
holder as TitleHolder
holder.bind(data.header)
}
}
override fun getItemViewType(position: Int): Int {
val tabType1 = list[position]
return if (tabType1.header.title == "") {
ROW
} else {
HEADER
}
}
inner class TitleHolder(binding: DailyTableHeaderBinding) :
RecyclerView.ViewHolder(binding.root) {
private val groupHeader: TextView = binding.title
fun bind(title: Header) {
groupHeader.text = title.title
}
}
inner class DVH(binding: DailyFragRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
private var cpname: TextView = binding.cpName
private var day1: FrameLayout = binding.frameLayout
private var day2: FrameLayout = binding.frameLayout2
private var day3: FrameLayout = binding.frameLayout3
private var day4: FrameLayout = binding.frameLayout4
private var dayContainer: ArrayList<FrameLayout> = ArrayList()
fun bind(cpVo: CheckingPointVo) {
dayContainer.addAll(arrayListOf(day1, day2, day3, day4))
cpname.apply {
text = cpVo.name
setOnClickListener {
activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID))
}
}
val map = cpVo.chartEntriesMap
for (i in dates.indices) {
val dateID = BkpDateUtil.getDateId(dates[i])
Timber.i("dateID $dateID")
var ceVo: List<ChartEntryVo>? = map[dateID]
if (ceVo == null) {
ceVo = ArrayList<ChartEntryVo>()
ceVo.add(ChartEntryVo(0, dateID, 0L, 0L, "", false))
}
val entryValue = ceVo[0].value
when (cpVo.cpType) {
CheckingPointType.YES_NO -> {
val fl: FrameLayout = dayContainer[i]
fl.setOnClickListener {
openYesNoDialog(cpVo, ceVo[0])
}
if (entryValue == 1L) {
setIconInChartEntry(fl, R.drawable.ic_tick, cpVo)
} else {
setIconInChartEntry(fl, R.drawable.ic_no_entry, cpVo)
}
}
CheckingPointType.QUANTIFIABLE -> {
}
CheckingPointType.COUNTABLE -> {
}
CheckingPointType.CATEGORY -> {
}
CheckingPointType.TEXT -> {
}
}
}
dayContainer.clear()
}
}
private fun setIconInChartEntry(fl: FrameLayout, resID: Int, cpVo: CheckingPointVo) {
fl.getChildAt(0).visibility = GONE
val image: ImageView = fl.getChildAt(1) as ImageView
image.visibility = VISIBLE
image.setImageResource(resID)
/*image.setOnClickListener {
activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID))
}*/
}
private fun openYesNoDialog(cpVo: CheckingPointVo, ce: ChartEntryVo) {
val builder = AlertDialog.Builder(context)
val dataSourceArray = BkpUiUtil.getYesNoArray()
val date = getDateFromId(ce.dateKey)
val title: String = getDayText(date, 1) + " - " + cpVo.name
builder.setTitle(title)
builder.setSingleChoiceItems(
dataSourceArray, BkpUiUtil.getYesNoIndex(ce.value.toString())
) { dialog, which ->
ce.value = BkpUiUtil.getYesNoId(which)
activityData.invoke(
DetailActivityData(
cpvo = cpVo,
cevo = ce,updateChart = true
))
dialog.dismiss()
}
val d = builder.create()
d.show()
}
override fun getItemCount() = list.size
}
data class DetailActivityData(var cpID: Long = 0, var pcID: Long = 0, var cpvo:CheckingPointVo = CheckingPointVo(), var cevo:ChartEntryVo= ChartEntryVo(), var updateChart : Boolean = false)
data class TabType1(
val header: Header = Header(""), val cps: CheckingPointVoList = CheckingPointVoList(
cps = listOf(
CheckingPointVo()
)
)
)
This is how the adapter is given the data.
val data: ArrayList<TabType1> = arrayListOf(
TabType1(
header = Header("Group One")
),
TabType1(
cps = CheckingPointVoList(it)
),
TabType1(
header = Header("Group Two")
),
TabType1(
cps = CheckingPointVoList(it)
)
)
if(it1.updateChart){
vm.updateChartEntry(it1.cpvo,it1.cevo)
}else{
Intent(requireContext(), CpDetailActivity::class.java).apply {
putExtra("cprId", it1.cpID)
putExtra("pcId", it1.pcID)
requireActivity().startActivity(this)
}
}
}, arraylist,dayslist)
This is the output that I get.
What am I missing?
An Adapter should adapt ONE list of data, not multiple lists nested within others. Meaning you need an Adapter and RecyclerView for each list.
If you want to do that you need to add an Adapter/RecyclerView to DVH and display your CheckingPoint list in there.
look here ->
override fun getItemCount() = list.size
Its only going to bind 4 times because you only gave it 4 items.
And here you are just rebinding the same ViewHolder, you need to pass in the data to the ViewHolder and have it display the list of "cps"
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data: TabType1 = list[position]
if (holder is DVH) {
holder.bind(data) //HERE
}else{
Timber.i("is: header")
holder as TitleHolder
holder.bind(data.header)
}
}
inner class DVH(binding: DailyFragRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
private var checkingPointVoRecyclerView: RecyclerView = binding.recyclerVew
private var checkingPointVoAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder> = CheckingPointVoAdapter(dates)
checkingPointVoRecyclerView.adapter = checkingPointVoAdapter
fun bind(data: TabType1) {
checkingPointVoAdapter.list = data.cps
checkingPointVoAdapter.notifyDataSetChanged()
}
}
class CheckingPointVoAdapter(
private val dates: List<Date>,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
public var list: List<CheckingPointVo> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
context = parent.context
val layoutInflater = LayoutInflater.from(context)
return ViewHolder(CheckingPointRecyclerItemBinding.inflate(layoutInflater, parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val cpv: CheckingPointVo = list[position]
holder as ViewHolder
holder.bind(cpv)
}
inner class ViewHolder(binding: CheckingPointRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
private var cpname: TextView = binding.cpName
private var day1: FrameLayout = binding.frameLayout
private var day2: FrameLayout = binding.frameLayout2
private var day3: FrameLayout = binding.frameLayout3
private var day4: FrameLayout = binding.frameLayout4
private var dayContainer: ArrayList<FrameLayout> = ArrayList()
fun bind(cpVo: CheckingPointVo) {
dayContainer.addAll(arrayListOf(day1, day2, day3, day4))
cpname.apply {
text = cpVo.name
setOnClickListener {
activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID))
}
}
val map = cpVo.chartEntriesMap
for (i in dates.indices) {
val dateID = BkpDateUtil.getDateId(dates[i])
Timber.i("dateID $dateID")
var ceVo: List<ChartEntryVo>? = map[dateID]
if (ceVo == null) {
ceVo = ArrayList<ChartEntryVo>()
ceVo.add(ChartEntryVo(0, dateID, 0L, 0L, "", false))
}
val entryValue = ceVo[0].value
when (cpVo.cpType) {
CheckingPointType.YES_NO -> {
val fl: FrameLayout = dayContainer[i]
fl.setOnClickListener {
openYesNoDialog(cpVo, ceVo[0])
}
if (entryValue == 1L) {
setIconInChartEntry(fl, R.drawable.ic_tick, cpVo)
} else {
setIconInChartEntry(fl, R.drawable.ic_no_entry, cpVo)
}
}
CheckingPointType.QUANTIFIABLE -> {
}
CheckingPointType.COUNTABLE -> {
}
CheckingPointType.CATEGORY -> {
}
CheckingPointType.TEXT -> {
}
}
}
dayContainer.clear()
}
}
}
I did not test this but this is like 90% of what you need.
OnBindViewHolder gives you a view or you can imagine it as a row
, then you bind data to that row (setting text, etc..)
let's look at what you actually did in OnBindViewHolder
data.cps.cps.forEach { // you bind the data to the same row multiple times
Timber.i("is: a row")
holder.bind(it)
}
But there is only one row.
To solve this you need to pass a list of header items and row items only
sealed class DataItem {
abstract val id: Long
data class RowItem(val row: Row) : DataItem() {
override val id = row.id
}
data class HeaderItem(val header: Header) : DataItem() {
override val id = header.id
}
}
The Transform your row and header data to the DataItem Like this
val rowItems: List<DataItem> = rowList.map { DataItem.RowItem(it) }
val headertems: List<DataItem> = headerList.map { DataItem.BankItem(it) }
val items = rowItems+ headertems
//then pass the items to the adapter
So your adapter now will create a new ViewHoler to each item in the list and you can check for it as
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM_ROW -> DVH(DailyFragRecyclerItemBinding.inflate(layoutInflater, parent, false))
ITEM_VIEW_TYPE_ITEM_HEADER -> TitleHolder(DailyTableHeaderBinding.inflate(layoutInflater, parent, false))
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
then bind data to each view as
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is DVH-> {
val item = getItem(position) as DataItem.RowItem
holder.bind(item)
}
is TitleHolder -> holder.bind(item)
}
}
Finally you need to adjust getItemViewType
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.RowItem -> ITEM_VIEW_TYPE_ITEM_ROW
is DataItem.HeaderItem -> ITEM_VIEW_TYPE_ITEM_HEADER
}
}
I am new android developer.
I have activity with radio buttons and I can choose color,put it into sharedPreferences and set background color to item in recycleview.
Here is it my code:
class ListAdapter(private val context: RecyclerView, private val ideasList: ArrayList<DataItem>) :
RecyclerView.Adapter<ListAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.title_idea
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return ideasList.count()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val idea: DataItem = ideasList[position]
holder.title.text = idea.title
}
}
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val sharedPreferences = getSharedPreferences(CHANGE_COLOR_SETTING, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
radio_group.setOnCheckedChangeListener { _, id ->
when (id) {
R.id.set_green_color_item -> editor.putBoolean(CHANGE_TO_GREEN_COLOR, true)
R.id.set_pink_color_item -> editor.putBoolean(CHANGE_TO_PINK_COLOR, true)
R.id.set_blue_color_item -> editor.putBoolean(CHANGE_TO_BLUE_COLOR, true)
}
editor.apply()
}
}
companion object {
private const val CHANGE_COLOR_SETTING = "change_color_setting"
private const val CHANGE_TO_GREEN_COLOR = "change_to_green_color"
private const val CHANGE_TO_PINK_COLOR = "change_to_pink_color"
private const val CHANGE_TO_BLUE_COLOR = "change_to_blue_color"
}
}
Thank you for answers!
I don't understand your question. Do you mean the color of each item inside the RecyclerView or the background color of the RecyclerView?
For each item you would have to do it inside your adapter with some logic. An option would be changing your model data(your ideaList), and call notifyDataSetChanged() to your adapter. Then inside your adapter you would have the logic to change the background color of each view.
If you want to change the background color of your whole RecyclerView then RecyclerView is like any other View and you can use code like this:
yourRecyclerView.setBackgroundColor(Color.BLUE)
My solution is:
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val sharedPreferences = getSharedPreferences(CHANGE_COLOR_SETTING, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
radio_group.setOnCheckedChangeListener { _, id ->
when (id) {
R.id.set_green_color_item -> {
editor.putBoolean(CHANGE_TO_GREEN_COLOR, true)
cancelChangePinkColor(editor)
cancelChangeColorToBlue(editor)
}
R.id.set_pink_color_item -> {
editor.putBoolean(CHANGE_TO_PINK_COLOR, true)
cancelChangeColorToBlue(editor)
cancelChangeColorGreen(editor)
}
R.id.set_blue_color_item -> {
editor.putBoolean(CHANGE_TO_BLUE_COLOR, true)
cancelChangeColorGreen(editor)
cancelChangePinkColor(editor)
}
}
editor.apply()
}
}
private fun cancelChangeColorToBlue(editor: SharedPreferences.Editor) {
editor.putBoolean(CHANGE_TO_BLUE_COLOR, false)
}
private fun cancelChangeColorGreen(editor: SharedPreferences.Editor) {
editor.putBoolean(CHANGE_TO_GREEN_COLOR, false)
}
private fun cancelChangePinkColor(editor: SharedPreferences.Editor) {
editor.putBoolean(CHANGE_TO_PINK_COLOR, false)
}
companion object {
internal const val CHANGE_COLOR_SETTING = "change_color_setting"
internal const val CHANGE_TO_GREEN_COLOR = "change_to_green_color"
internal const val CHANGE_TO_PINK_COLOR = "change_to_pink_color"
internal const val CHANGE_TO_BLUE_COLOR = "change_to_blue_color"
}
}
And
class ListAdapter(private val context: RecyclerView, private val ideasList: ArrayList<DataItem>) :
RecyclerView.Adapter<ListAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.title_idea
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return ideasList.count()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val idea: DataItem = ideasList[position]
holder.title.text = idea.title
val sharedPreferences = context.context.getSharedPreferences(
SettingsActivity.CHANGE_COLOR_SETTING,
Context.MODE_PRIVATE
)
val (changeToGreen, changeToPink, changeToBlue) = getSharedPreferences(sharedPreferences)
when (true) {
changeToGreen -> changeItemColor(
holder,
context.context.resources.getColor(R.color.colorGreen)
)
changeToPink -> changeItemColor(
holder,
context.context.resources.getColor(R.color.colorPink)
)
changeToBlue -> changeItemColor(
holder,
context.context.resources.getColor(R.color.colorPrimaryDark)
)
}
}
private fun getSharedPreferences(sharedPreferences: SharedPreferences): Triple<Boolean, Boolean, Boolean> {
val changeToGreen =
sharedPreferences.getBoolean(SettingsActivity.CHANGE_TO_GREEN_COLOR, false)
val changeToPink =
sharedPreferences.getBoolean(SettingsActivity.CHANGE_TO_PINK_COLOR, false)
val changeToBlue =
sharedPreferences.getBoolean(SettingsActivity.CHANGE_TO_BLUE_COLOR, false)
return Triple(changeToGreen, changeToPink, changeToBlue)
}
private fun changeItemColor(
holder: ViewHolder,
color: Int
) {
holder.itemView.setBackgroundColor(color)
}
}
In my app I have some fragments and I switch between them with this nice animation:
activity?.supportFragmentManager?.beginTransaction()
?.setCustomAnimations(
R.anim.slide_in_from_right,
R.anim.slide_out_to_left,
R.anim.slide_in_from_left,
R.anim.slide_out_to_right)
?.replace(R.id.fragmentContainer, articleContentFragment)
?.addToBackStack(null)
?.commit()
Inside of every fragment I have a RecyclerView with several types of items e.g. TextViews or ImageViews.
And it works fine, but
when I added a WebView as one of the types of my RecyclerView items, nice animation of fragment transaction started to freeze the first time I open the fragment. The cause is inflating of WebView.
How can I inflate view in background thread or maybe there are any better solutions?
(It still works fine in RecyclerViews, which do not inflate WebView as an item)
My Adapter:
class ArticleContentAdapter(
context: Context
) : RecyclerView.Adapter<ArticleContentAdapter.BaseViewHolder<*>>() {
companion object {
private const val headerType = 1
private const val paragraphType = 2
private const val codeSnippetType = 3
private const val imageType = 4
}
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var articlePieces = ArrayList<ArticlePiece>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
return when (viewType) {
headerType -> ArticleHeaderViewHolder(
inflater.inflate(
R.layout.article_header_item,
parent,
false
)
)
paragraphType -> ArticleParagraphViewHolder(
inflater.inflate(
R.layout.article_paragraph_item,
parent,
false
)
)
codeSnippetType -> ArticleCodeSnippetViewHolder(
inflater.inflate(
R.layout.article_code_snippet_item,
parent,
false
)
)
imageType -> ArticleImageViewHolder(
inflater.inflate(
R.layout.article_image_item,
parent,
false
)
)
else -> throw IllegalArgumentException("Invalid view type.")
}
}
override fun getItemViewType(position: Int): Int {
return when (articlePieces[position]) {
is ArticleHeader -> headerType
is ArticleParagraph -> paragraphType
is ArticleCodeSnippet -> codeSnippetType
is ArticleImage -> imageType
else -> throw IllegalArgumentException("Invalid type of data $position.")
}
}
override fun getItemCount() = articlePieces.size
internal fun addArticlePieces(articlePieces: List<ArticlePiece>) {
this.articlePieces = getAllSortedPieces(this.articlePieces, articlePieces)
notifyDataSetChanged()
}
private fun getAllSortedPieces(
formerPieces: List<ArticlePiece>,
newPieces: List<ArticlePiece>
): ArrayList<ArticlePiece> {
val allSortedPieces = ArrayList<ArticlePiece>()
allSortedPieces.addAll(formerPieces)
allSortedPieces.addAll(newPieces)
allSortedPieces.sortBy { A -> A.positionInArticle }
return allSortedPieces
}
abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}
class ArticleHeaderViewHolder(itemView: View) :
BaseViewHolder<ArticleHeader>(itemView) {
private val articleHeader: TextView = itemView.header
override fun bind(item: ArticleHeader) {
articleHeader.text = item.getEssentialDataOfPiece()
}
}
class ArticleParagraphViewHolder(itemView: View) :
BaseViewHolder<ArticleParagraph>(itemView) {
private val articleParagraph: TextView = itemView.paragraph
override fun bind(item: ArticleParagraph) {
val text = "\t ${item.getEssentialDataOfPiece()}"
articleParagraph.text = text
}
}
class ArticleCodeSnippetViewHolder(itemView: View) :
BaseViewHolder<ArticleCodeSnippet>(itemView) {
private val webView: WebView = itemView.webView
private val loadingPlaceholder: LinearLayout = itemView.loadingPlaceholder
private val noInternetConnectionPlaceholder: LinearLayout =
itemView.noInternetConnectionPlaceholder
override fun bind(item: ArticleCodeSnippet) {
val codeSnippetController = CodeSnippetController(
item,
webView,
loadingPlaceholder,
noInternetConnectionPlaceholder
)
if (item.viewHeight != 0 || item.viewHeight != 1) {
webView.layoutParams.height = item.viewHeight
loadingPlaceholder.layoutParams.height = item.viewHeight
noInternetConnectionPlaceholder.layoutParams.height = item.viewHeight
}
codeSnippetController.loadCodeSnippet()
}
}
class ArticleImageViewHolder(itemView: View) :
BaseViewHolder<ArticleImage>(itemView) {
private val imageView: ImageView = itemView.imageView
// private val loadingPlaceholder: LinearLayout = itemView.loadingPlaceholder
// private val noInternetConnectionPlaceholder: LinearLayout =
// itemView.noInternetConnectionPlaceholder
override fun bind(item: ArticleImage) {
Picasso.get().load(item.url).into(imageView)
}
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val piece = articlePieces[position]
when (holder) {
is ArticleHeaderViewHolder -> holder.bind(piece as ArticleHeader)
is ArticleParagraphViewHolder -> holder.bind(piece as ArticleParagraph)
is ArticleCodeSnippetViewHolder -> holder.bind(piece as ArticleCodeSnippet)
is ArticleImageViewHolder -> holder.bind(piece as ArticleImage)
else -> throw IllegalArgumentException()
}
}
}
I have an adapter that has a view holder that handles the data binding. I want to be able to redirect the user to other page(do a fragment transiction to the error fragment) if the array is empty.
This is my adapter:
class UserCardsRecyclerViewAdapter(val listener: (Card) -> Unit) : RecyclerView.Adapter<UserCardsRecyclerViewAdapter.ViewHolder>() {
private var cardsList = mutableListOf<Card>()
private var views = mutableListOf<ViewHolder>()
fun addItems(items: List<Card>){
cardsList.addAll(items)
notifyDataSetChanged()
}
fun clear(){
cardsList = mutableListOf()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_user_card, parent, false)
return ViewHolder(view).also { views.add(it) }
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val card = cardsList[position]
holder.bind(card, listener)
}
override fun getItemCount() = cardsList.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(card: Card, listener: (Card) -> Unit) {
if(card.name.isEmpty()){
Toast.makeText(itemView.context,"Empty",Toast.LENGTH_LONG).show()
}
itemView.card_name.text = card.name
itemView.card_number.text = card.number.toString()
card.expiration?.let {
itemView.valid_title.visibility =View.VISIBLE
itemView.valid_date.visibility = View.VISIBLE
itemView.valid_date.text = it.ParseMonthYear()
}
GlideApp.with(itemView.context).load(card.mosaicImageUrl)
.fitCenter()
.apply(RequestOptions.bitmapTransform(
RoundedCornersTransformation(50, 0, RoundedCornersTransformation.CornerType.ALL)))
.into(itemView.card_image)
itemView.setOnClickListener { listener(card) }
}
}
}
This is my fragment where I call the adapter:
override fun doOnCreated() {
initCardsRecyclerView()
viewModel.loadCards()
}
private fun initCardsRecyclerView(){
dataBinding.rvCards.setHasFixedSize(true)
dataBinding.rvCards.setItemViewCacheSize(1)
dataBinding.rvCards.isNestedScrollingEnabled = false
dataBinding.rvCards.layoutManager = LinearLayoutManager(context)
userCardsRecyclerViewAdapter = UserCardsRecyclerViewAdapter(::handleCardClickListener)
dataBinding.rvCards.adapter = userCardsRecyclerViewAdapter
viewModel.retrieveCards().observe(this, Observer<List<Card>> {
userCardsRecyclerViewAdapter.apply {
clear()
addItems(it)
}
})
}
This is my viewModel that contains methods that communicate with repository and API
class CardsViewModel #Inject constructor(
private val cardsRepository: CardsRepository
) : ViewModel() {
private val loading = MutableLiveData<Boolean>()
private val cards = MutableLiveData<List<Card>>()
fun retrieveCards(): LiveData<List<Card>> = cards
fun retrieveLoading(): LiveData<Boolean> = loading
fun loadCards(){
loading.postValue(true)
cardsRepository.getUserCards(
{
cards.postValue(it)
loading.postValue(false)
},
{
loading.postValue(false)
})
}
}
This is my repository
class CardsRepository #Inject constructor(
private val cardsDao: CardsDao,
private val apiManager: ApiManager
){
fun getUserCards(onSuccess: (List<Card>)->Unit, onError: (Exception)->Unit) =
cardsDao.loadAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ composeCardInfo(
it.filterNot {
it.cardState == CardState.Blocked ||
it.cardState == CardState.Deleted
},
onSuccess, onError) },
{onError(Exception(it.message))}
)