In my application I want create chat application and for show messages list I used PagingLibrary (version 3) for RecyclerView.
I can show messages and add new item, but when added new item replace this new item with previous item. For example, this is messages list:
Hi
How are you?
I'm android developer.
You?
When send new message for example "My job is ...", replace this item instead of "You?" Such as below:
Hi
How are you?
I'm android developer.
My job is ...
But should show such as below:
Hi
How are you?
I'm android developer.
You?
My job is ...
My adapter codes:
class ChatAdapter #Inject constructor() : PagingDataAdapter<Result, ChatAdapter.ViewHolder>(differCallback) {
private lateinit var binding: ItemChatBinding
private val userToken by lazy { GoodPrefs.getInstance().getString(USER_TOKEN, "") }
private lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false)
context = parent.context
return ViewHolder()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!)
//holder.setIsRecyclable(false)
}
inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
val root = itemView.findViewById(R.id.root) as ConstraintLayout
private val meTxt = itemView.findViewById(R.id.meTxt) as TextView
private val doctorTxt = itemView.findViewById(R.id.doctorTxt) as TextView
#SuppressLint("SetTextI18n")
fun bind(item: Result) {
//Message
if (item.message != null) {
meTxt.text = item.message
meTxt.isVisible = true
doctorTxt.text = item.message
doctorTxt.isVisible = true
} else {
meTxt.isVisible = false
doctorTxt.isVisible = false
}
}
}
fun addNewItem(position: Int, item: Result) {
snapshot()[position]?.message = item.message
notifyItemChanged(position)
}
companion object {
private val differCallback = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Result, newItem: Result): Boolean {
return oldItem == newItem
}
}
}
}
Fragment codes:
//RecyclerView
chatLayoutManager.reverseLayout = true
chatList.apply {
layoutManager = chatLayoutManager
setHasFixedSize(true)
itemAnimator = null
adapter = chatAdapter.withLoadStateHeaderAndFooter(
header = LoadMoreAdapter { chatAdapter.retry() },
footer = LoadMoreAdapter { chatAdapter.retry() }
)
}
pusherChannel.bind(PUSHER_EVENT_NAME, object : PrivateChannelEventListener {
#SuppressLint("NotifyDataSetChanged")
override fun onEvent(event: PusherEvent) {
val newChat = ResponseChatList.Data.Result(
message = pusherGsonData.message, deliveredAt = pusherGsonData.deliveredAt,
toUserId = toUserData, chatFile = chatFile
)
//Update UI
runOnUiThread {
chatAdapter.addNewItem(chatDataList.indices.first, newChat)
chatList.smoothScrollToPosition(chatDataList.indices.first)
}
How can I fix this problem?
Related
binding adapter position is returning -1 and i cannot figure out why, anyone has an idea of what i could be doing wrong ?
holder.bindingAdapterPosition in onCreateViewHolder.
Adapter and viewHolder
class CommentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var likeClick: LikeClick? = null
private var disLikeClick: DisLikeClick? = null
inner class CommentHolder(view: View) : RecyclerView.ViewHolder(view) {
val nicknameTextView: TextView = itemView.findViewById(R.id.nickname_text_view)
val commentTextView: TextView = itemView.findViewById(R.id.comment_text_view)
val likeCount: TextView = itemView.findViewById(R.id.like_count_text_view)
val disLikeCount: TextView = itemView.findViewById(R.id.dislike_count_text_view)
val likeBtn: ImageButton = itemView.findViewById(R.id.like_image_btn)
val disLikeBtn: ImageButton = itemView.findViewById(R.id.disLike_Image_btn)
}
fun setLikeClickListener(clickListener: LikeClick) {
likeClick = clickListener
}
fun setDisLikeClickListener(clickListener: DisLikeClick) {
disLikeClick = clickListener
}
private val diffCallback = object : DiffUtil.ItemCallback<Comment>() {
override fun areItemsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Comment, newItem: Comment): Boolean {
return oldItem.id == newItem.id
}
}
private val diff = AsyncListDiffer(this, diffCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_comment, parent, false)
val holder: RecyclerView.ViewHolder = CommentHolder(view)
//set on click listener for like and dislike
Log.d("TESTLOG",diff.currentList.toString())
//TODO FIX
val commentItem = diff.currentList[holder.bindingAdapterPosition]
val likeBtn=view.findViewById<ImageButton>(R.id.like_image_btn)
val dislikeBtn=view.findViewById<ImageButton>(R.id.disLike_Image_btn)
likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
return holder
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val commentItem = diff.currentList[position]
val videoHolder = holder as CommentHolder
videoHolder.apply {
commentTextView.text = commentItem.comment
nicknameTextView.text = commentItem.nickname
likeCount.text = commentItem.commentlike.toString()
disLikeCount.text = commentItem.dislike.toString()
//check if user already liked/disliked this comment and change icon based on that
Utils.likes.forEach {
if(it == commentItem.id){
likeBtn.isSelected=true
}
}
Utils.disLikes.forEach {
if(it==commentItem.id){
disLikeBtn.isSelected=true
}
}
}
}
override fun getItemCount(): Int {
return diff.currentList.size
}
fun submitList(list: List<Comment>) {
diff.submitList(list)
}
interface LikeClick {
fun onLikeClick(commentId: Int, likeBtn:ImageButton)
}
interface DisLikeClick {
fun onDisLikeClick(commentId: Int, disLikeBtn:ImageButton)
}
}
Log.d in oncreateViewHolder
Shows what diff.currentlist contains.
D/TESTLOG: [Comment(comment=firstcomment
, commentlike=0, createdAt=2022-07-06T04:35:43.000Z, dislike=0, id=4, nickname=Alpac, videoId=170c3d0c-331f-4da9-974b-39703940f819)]
Where i set the adapter
i submit a list of comments to adapter, set like and dislike listeners then apply adapter to recyclerView
ChikiCommentsFetcher().fetchCommentsOfAVideo(videoId).observe(viewLifecycleOwner){
commentsRecyclerView.apply {
commentAdapter.submitList(it)
commentAdapter.setLikeClickListener(this#CommentsFragment)
commentAdapter.setDisLikeClickListener(this#CommentsFragment)
adapter=commentAdapter
}
}
Holder positions should be handled inside the onBindViewHolder method.
Also, move your clicks to the onBindViewHolder method.
holder.likeBtn.setOnClickListener {
likeClick?.onLikeClick(commentItem.id,likeBtn)
}
holder.dislikeBtn.setOnClickListener {
disLikeClick?.onDisLikeClick(commentItem.id,dislikeBtn)
}
check out this RecyclerView Tutorial
I am fetching JSON data from API and passing it in recycler view but if I want to fetch new data and display it in recycler view then I have to clear the list and then add new data in that list and notify the adapter that the data is changed but it is not updated what should I do?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recipeViewModel: RecipeViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var recipeAdapter: RecipeAdapter
private lateinit var recipeItemList: ArrayList<Hit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeItemList.addAll(recipeItems.hits)
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
private fun searchRecipeName() {
mainBinding.searchRecipeFabBtn.setOnClickListener {
val view = layoutInflater.inflate(R.layout.recipe_search_layout, null)
val searchRecipeET = view.findViewById<EditText>(R.id.searchRecipeET)
val searchRecipeBtn = view.findViewById<Button>(R.id.searchRecipeBtn)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.apply {
this.setContentView(view)
this.show()
}
searchRecipeBtn.setOnClickListener {
val recipeName = searchRecipeET.text.toString()
searchRecipeName(recipeName, searchRecipeET, bottomSheetDialog)
}
}
}
private fun searchRecipeName(
recipeName: String,
searchRecipeET: EditText,
bottomSheetDialog: BottomSheetDialog
) {
if (recipeName.isEmpty()) {
searchRecipeET.error = "Please enter recipe name"
} else {
recipeViewModel.getRecipes(recipeName)
bottomSheetDialog.dismiss()
}
}
}
RecipeAdapter.kt
class RecipeAdapter(val context: Context) : RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder>() {
private val recipesList: ArrayList<Hit> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = recipesList[position]
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
override fun getItemCount(): Int {
return recipesList.size
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList.clear()
Log.d("RECIPE SIZE", "${recipesList.size}")
recipesList.addAll(newRecipesList)
notifyDataSetChanged()
}
}
This may be helpful.
Be careful of this :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
// You're adding items here but never clear the list
// list will be bigger every time you'll be notified
// recipeItemList.addAll(recipeItems.hits
// recipeAdapter.updateRecipes(recipeItemList)
// Do this instead
recipeItemList = recipeItems.hits
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
Also, here: It's a little better to do this (https://stackoverflow.com/a/10298038/4221943)
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList = newRecipesList
Log.d("RECIPE SIZE", "${recipesList.size}")
notifyDataSetChanged()
}
BTW it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort. It is also good practice to use notifyItemInserted(mItems.size() - 1) for "easier" solution.
You could convert the RecyclerView.Adapter into a ListAdapter:
class RecipeAdapter(val context: Context) : ListAdapter<Hit, RecipeAdapter.RecipeViewHolder>(RecipeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = getItem(position)
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
}
class RecipeDiffCallback : DiffUtil.ItemCallback<Hit>() {
// Change this condition based on the attribute of `Hit` that will change
override fun areItemsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem == newItem
}
Then update its content with the submitList method.
Every item not satisfying the RecipeDiffCallback conditions will be automatically updated:
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeAdapter.submitList(recipeItems.hits)
})
I'm making a chat application using Firebase and I have a listener for my documents so that every time there's a new message I'll set the new lists to the value of my LiveData<List<T>> that is being observe in my Fragment and calls submistList() to my ListAdapter<>
My problem is everytime I add a new message, I can see a small flash on the recyclerview and after further debugging, I found out my DiffUtil.ItemCallback is being twice despite calling submitList() only once.
I really have no idea what I missed, thank you for any help.
DiffUtil.ItemCallback
class ChatsDiffCallBack : DiffUtil.ItemCallback<Chat>() {
override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean {
Log.d("Adapter", "Old: ${oldItem.id} New: ${newItem.id}")
Log.d("Adapter", "Equal: ${oldItem.id == newItem.id}")
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem == newItem
}
}
Adapter
class ChatAdapter : ListAdapter<Chat, ChatAdapter.ViewHolder>(ChatsDiffCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent, viewType)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getItemViewType(position: Int): Int {
if (getItem(position).chatType == ChatType.mine) return R.layout.my_chat_item
return R.layout.their_chat_item
}
class ViewHolder private constructor(val binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup, layoutId: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ViewDataBinding =
DataBindingUtil.inflate(layoutInflater, layoutId, parent, false)
return ViewHolder(binding)
}
}
fun bind(chat: Chat) {
when (binding) {
is MyChatItemBinding -> binding.chat = chat
is TheirChatItemBinding -> binding.chat = chat
}
binding.executePendingBindings()
}
}
}
ViewModel
class ChatViewModel #Inject constructor(private val repository: ChatRepository) : BaseViewModel() {
lateinit var userName: String
val message = MutableLiveData<String>()
private val _chats = MutableLiveData<List<Chat>>().apply { value = emptyList() }
val chats: LiveData<List<Chat>> = _chats
fun setup(userName: String?) {
if (userName == null) return
Log.d("Chat", "Username is: $userName")
this.userName = userName
getChats()
}
fun onSendClick() {
if (userName.isEmpty() && message.value.toString().isEmpty()) return
val chat = Chat(userName = userName, message = message.value.toString())
repository.addMessage(chat).addOnSuccessListener {
Log.d("Chat", "Added new message!")
}.addOnFailureListener {
Log.e("Chat", "Failed adding message")
}
}
private fun getChats() {
repository.getMessages().orderBy("timestamp", Query.Direction.ASCENDING)
.addSnapshotListener { value, e ->
if (value?.documents?.size == chats.value?.size) return#addSnapshotListener
if (e != null) {
Log.e("Chat", "Listen Failed!", e)
return#addSnapshotListener
}
val tempChats = mutableListOf<Chat>()
for (doc in value?.documents!!) {
val chat = Chat.fromDocumentSnapshot(doc)
chat.chatType = if (this.userName == chat.userName) ChatType.mine
else ChatType.theirs
tempChats.add(chat)
}
_chats.value = tempChats
}
}
}
Fragment
private fun initRecyclerView() {
val adapter = ChatAdapter()
binding.rv.adapter = adapter
viewModel.chats.observe(viewLifecycleOwner, Observer {
Log.d("ChatFragment", "*************")
adapter.submitList(it.toList())
})
}
I was facing the same issue, DiffUtil.ItemCallback was being called twice, and the RecyclerView was blinking on deleting an item, to fix this i had to override getItemId like this.
override fun getItemId(adapterPosition: Int): Long {
return getItem(adapterPosition).getId()
}
The data in the RecyclerView is called the first time without issues. However when i refresh the data, for some reason all the items goes blank.
The MainActivity is this
class BusinessActivity : AppCompatActivity() {
private val businessViewModel: BusinessViewModel by viewModel()
private val imageLoader: ImageLoader by inject()
private lateinit var staggeredGridLayoutManager: StaggeredGridLayoutManager
private lateinit var skeleton: Skeleton
private val adapter: BusinessAdapter by lazy { BusinessAdapter(imageLoader, businessViewModel) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_business)
initToolbar()
skeleton = findViewById<SkeletonLayout>(R.id.skeletonLayout)
staggeredGridLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
staggeredGridLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
recycler_view.apply {
layoutManager = staggeredGridLayoutManager
adapter = this#BusinessActivity.adapter
setHasFixedSize(true)
}
setupSkeleton()
initializeObserverBusiness()
refreshBusiness.setOnRefreshListener {
refreshBusiness.isRefreshing = true
skeleton.showSkeleton()
businessViewModel.retrieveBusiness()
}
}
private fun initToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.title = getString(R.string.app_name)
this.setSystemBarColor(this)
}
private fun setupSkeleton(){
skeleton = recycler_view.applySkeleton(R.layout.business_card, 6)
skeleton.showSkeleton()
}
private fun initializeObserverBusiness(){
businessViewModel.uiState.observe(this, Observer {
val dataState = it ?: return#Observer
if (!dataState.showProgress){
refreshBusiness.isRefreshing = false
skeleton.showOriginal()
}
if (dataState.business != null && !dataState.business.consumed){
dataState.business.consume()?.let { business ->
adapter.submitList(business)
}
}
if (dataState.error != null && !dataState.error.consumed){
dataState.error.consume()?.let { error ->
Toast.makeText(this, resources.getString(error), Toast.LENGTH_LONG).show()
}
}
})
}
}
and the Adapter for the RecyclerView, im currently using DiffCallback and ListAdapter due to a better performance.
class BusinessAdapter(var imageLoader: ImageLoader, var viewModel: BusinessViewModel) : ListAdapter<Business, BusinessViewHolder>(DIFF_CALLBACK){
companion object{
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Business>() {
override fun areItemsTheSame(oldItem: Business, newItem: Business) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Business, newItem: Business) = oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = BusinessViewHolder.create(parent)
override fun onBindViewHolder(holder: BusinessViewHolder, position: Int) {
holder.bind(getItem(position), imageLoader, viewModel)
}
}
and the ViewHolder for the Adapter
class BusinessViewHolder constructor(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(business: Business, imageLoader: ImageLoader, viewModel: BusinessViewModel) {
businessImage?.let { imageLoader.load("${BuildConfig.MY_URL}/gallery/${business.images[0]}", it) }
ownerBusiness.text = business.owner
businessName.text = business.name
cardBusiness.setOnClickListener {
viewModel.callDetailBusiness(business.id)
}
}
companion object {
fun create(parent: ViewGroup): BusinessViewHolder {
return BusinessViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.business_card, parent, false))
}
}
}
and the ViewModel
class BusinessViewModel (private val businessRepository: BusinessRepository): ViewModel() {
private val _uiState = MutableLiveData<BusinessDataState>()
val uiState: LiveData<BusinessDataState> get() = _uiState
val _showDetailBusiness = MutableLiveData<Int?>()
val showDetailBusiness: LiveData<Int?> get() = _showDetailBusiness
init {
retrieveBusiness()
}
fun retrieveBusiness(){
viewModelScope.launch {
runCatching {
emitUiState(showProgress = true)
businessRepository.retrieveBusiness()
}.onSuccess {
emitUiState(business = Event(it))
}.onFailure {
emitUiState(error = Event(R.string.internet_failure_error))
}
}
}
fun callDetailBusiness(businessId: Int) {
_showDetailBusiness.value = businessId
}
private fun emitUiState(showProgress: Boolean = false, business: Event<List<Business>>? = null, error: Event<Int>? = null){
val dataState = BusinessDataState(showProgress, business, error)
_uiState.value = dataState
}
data class BusinessDataState(val showProgress: Boolean, val business: Event<List<Business>>?, val error: Event<Int>?)
}
When the data is loaded for the first time i see this.
however when i apply the SwipeRefresh. I receive the data.
D/OkHttp: [{"id":18,"name":"Whatsup","owner":"Mi
Soledad","category":"ToDo",
but the RecyclerView won't attach the new information...
I have been trying for few days to find a solution , but I can not find the hot potato.
I have about 200 users in attendance list for a sports club.
The checkbox should store which users are present.
The problem is that I can only manage only one user.
I try a lot but no result
Anybody have an idea how to solve this problem please?
UPDATE:
Thanks to
#Sanlok Lee
Problem successfully resolved.
Below the new code:
class User:
class User(
val uid: String?,
val username: String?,
val profileImageUrl: String?,
val isSelected: Boolean?
) {
constructor() : this("", "", "", false)
}
class Home:
class Home : AppCompatActivity() {
val db = FirebaseFirestore.getInstance()
val adapter = PerformListAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_perform_anwesenheitsliste)
verifyUserIsLogin()
_recyclerView_show_anwesenheitsliste.adapter = adapter
showUsersAndPerformChekBoxes()
}
fun showUsersAndPerformChekBoxes() {
db.collection("users")
.get()
.addOnSuccessListener { documents ->
val users = documents.map { doc ->
doc.toObject(User::class.java)
}
adapter.submitUsers(users)
}
}
private fun verifyUserIsLogin() {
val uid = FirebaseAuth.getInstance().uid
if (uid == null) {
val intent = Intent(this, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_home -> {}
R.id.menu_sign_out -> {
FirebaseAuth.getInstance().signOut()
val intent = Intent(this, RegisterActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.nav_menu_home, menu)
return super.onCreateOptionsMenu(menu)
}
}
class PerformListAdapter:
class PerformListAdapter : RecyclerView.Adapter<PerformListAdapter.CustomViewHolder>() {
val db = FirebaseFirestore.getInstance()
var users: List<User> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val inflater = LayoutInflater.from(parent.context)
val itemuViewRow = inflater.inflate(R.layout.user_row, parent, false)
return CustomViewHolder(itemuViewRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
if (users.size > position) {
holder.bindTo(users[position])
} else {
holder.clear()
}
}
override fun getItemCount(): Int {
return users.size
}
fun submitUsers(users: List<User>) {
this.users = users
notifyDataSetChanged()
}
inner class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val checkBox_select: CheckBox = view.findViewById(R.id._checkBox_select)
private val textView_anwesenheitsliste_username =
view.findViewById<TextView>(R.id._textView_anwesenheitsliste_username)
private val imageView_show_anwesenheitsliste_row =
view.findViewById<CircleImageView>(R.id._imageView_show_anwesenheitsliste_row)
private val checkedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
val uid = users[adapterPosition].uid
if (uid != null) {
Log.d(TAG, uid)
if (isChecked) {
db.collection("users")
.document(uid)
.update("selected", true)
} else {
db.collection("users")
.document(uid)
.update("selected", false)
}
}
}
fun bindTo(user: User) {
if (!user.profileImageUrl!!.isEmpty()) {
Picasso.get().load(user.profileImageUrl)
.into(imageView_show_anwesenheitsliste_row)
}
textView_anwesenheitsliste_username?.text = user.username
checkBox_select.setOnCheckedChangeListener(null)
checkBox_select.isChecked = (user.isSelected == true)
checkBox_select.setOnCheckedChangeListener(checkedChangeListener)
}
fun clear() {
textView_anwesenheitsliste_username.text = ""
checkBox_select.setOnCheckedChangeListener(null)
}
}
}
The first thing to think about is whether you need a click listener for this. The click event should be absorbed by the CheckBox and therefore click listener is probably not needed. Also, setOnCheckedChangeListener should be set once per CheckBox not per click nor per User items.
Since you haven't showed us GroupAdapter nor ViewHolder implementation, so I just came up with one possible implementation:
GroupAdapter.kt
class GroupAdapter: RecyclerView.Adapter<GroupAdapter.ViewHolder>() {
private val db = FirebaseFirestore.getInstance()
private var users: List<User> = ArrayList() // Store user list here
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val itemView = inflater.inflate(R.layout.yourViewHolderLayout, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (users.size > position)
holder.bindTo(users[position])
else
holder.clear()
}
override fun getItemCount() = users.size
// Update the user list
fun submitUsers(users: List<User>) {
this.users = users
notifyDataSetChanged()
}
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
private val _checkBox_select: CheckBox = view.findViewById(R.id.yourCheckBoxId)
private val _textView_anwesenheitsliste_username = view.findViewById(R.id.yourTextBoxId)
private val checkedChangeListener =
CompoundButton.OnCheckedChangeListener { view, isChecked ->
val uid = users[adapterPosition].uid
if (uid != null) {
if (isChecked) {
db.collection("users").document(uid)
.update("isSelected", true)
} else {
db.collection("users").document(uid)
.update("isSelected", false)
}
}
}
// bind to user here
fun bindTo(user: User) {
if (!user.prfileImageUrl!!.isEmpty()) {
Picasso.get().load(user.prfileImageUrl)
.into(_imageView_show_anwesenheitsliste_row)
}
_textView_anwesenheitsliste_username.text = user.username
_checkBox_select.setOnCheckedChangeListener(null)
_checkBox_select.isChecked = (user.isSelected == true)
_checkBox_select.setOnCheckedChangeListener(checkedChangeListener)
}
// clear
fun clear() {
_textView_anwesenheitsliste_username.text = ""
_checkBox_select.setOnCheckedChangeListener(null)
}
}
}
Then you can use this adapter in your activity like this:
val db = FirebaseFirestore.getInstance()
val adapter = GroupAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_perform_anwesenheitsliste)
_recyclerView_show_anwesenheitsliste.adapter = adapter
showUsersAndPerformChekBoxes()
}
private fun showUsersAndPerformChekBoxes() {
db.collection("users")
.get()
.addOnSuccessListener { documents ->
val users = documents.map { doc -> doc.toObejct(User::Class.java) }
adapter.submitUsers(users)
}
}
Things might not work straight off since I haven't tested the code.
Further improvements
If you are interested, think about these scenarios: What if there are too many users that the app cannot get the entire list all at once? What if there are multiple phones that run the same app and try to update the list all at the same time? What if there is a network problem? What happens if the user spams the checkbox x9999 times?
The solutions for these problems are not trivial so I am not answering here, but it is a good practice to think about edge cases and how the app should react to those scenarios.