How to populate Recyclerview with images retrieved from firebase storage? - android

How to populate a Recyclerview with images retrieved from Firebase storage?
I'm trying to use a pattern I learned in one of the courses, but it doesn't seem to apply
Here's the adapter:
class RecyclerViewAdapter (
private val onClickListener: OnClickListener) :
ListAdapter<GiftData, RecyclerViewAdapter.DataViewHolder>(DiffCallback)
{
class DataViewHolder(private var binding: GridViewItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(GiftData: GiftData) {
binding.data = GiftData
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<GiftData>() {
override fun areItemsTheSame(oldItem: GiftData, newItem: GiftData): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: GiftData, newItem: GiftData): Boolean {
return oldItem.imgSrcUrl == newItem.imgSrcUrl
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): DataViewHolder {
return DataViewHolder(GridViewItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: DataViewHolder, position: Int) {
val data = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(data)
}
holder.bind(data)
}
fun interface OnClickListener {
fun onClick(data: GiftData)
}
}
The Binding adapter:
#BindingAdapter("giftData")
fun bindGiftRecyclerView(recyclerView: RecyclerView, data: List<GiftData>?) {
val adapter = recyclerView.adapter as RecyclerViewAdapter
adapter.submitList(data)
}
the Recyclerview xml:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/gift_photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:giftData="#{viewModel.data}"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="#layout/grid_view_item" />
The call to firebase which returns string urls for the images:
fun getImagesUrl(callback: (String)-> Unit){
var url: String
val storageRef = Firebase.storage.reference.child("images")
storageRef.listAll().addOnSuccessListener { listResult ->
listResult.items.forEach { result ->
result.downloadUrl.addOnSuccessListener {
url = it.toString()
callback.invoke(url)
}
}
}
}
The viewmodel: Here I'm trying to set the value of [Data] a mutablelivedata list of the data class that's holding the images url which then I'm going to load into the view item, but the returned is a list of strings while the required type is a list of the data class holding the url
class OverviewViewModel : ViewModel() {
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
private val _data = MutableLiveData<List<GiftData>>()
val data: LiveData<List<GiftData>>
get() = _data
init {
getData()
}
private fun getData(){
viewModelScope.launch {
_status.value = NetworkStatus.LOADING
try {
getImagesUrl {
_data.value = listOf(it)
_status.value = NetworkStatus.DONE
}
}catch (e: Exception){
Log.e("failed", e.toString())
_data.value = ArrayList()
_status.value = NetworkStatus.ERROR
}
}
}
}

From the question what I understand is that you need to fetch the image in the recycler view item.
So to this kindly follow the steps below:
// add the dependency of Glide in build.gradle file (app level)
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
and in the onBindViewHolder you can simply pass the link to the glide to load it in the item's imageView
Glide.with(this)
.load(MEDIA_LINK)
.into(YOUR_IMAGE_VIEW);
and further you can also attach listeners to it if you need.
Feel Free to ask if something is unclear and if it helps you kindly mark it as the correct answer.

Related

How can I select multiple items in RecyclerView and add them to an ArrayList(KOTLIN)?

I asked this question before but I could not get any answer. So I am asking it again but this time more detailed so you guys can understand my problem. I have a RecyclerView that gets items from my Firebase database. I want to select multiple items and add highlighted items to an arraylist of strings. FYI I am using a library called Groupie. I am not using custom adapter.
Hobbies Class
class Hobbies : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hobbies)
val database = FirebaseDatabase.getInstance()
tophobbies.layoutManager =
object : LinearLayoutManager(this, HORIZONTAL, false) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
// force height of viewHolder here, this will override layout_height from xml
lp.height = recyclerlinear.height
return true
}
}
val adapter = GroupAdapter<GroupieViewHolder>()
val reference = database.getReference("Hobbies")
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snap in snapshot.children) {
val hobbiesItem = snap.getValue(HobbiesClass::class.java)
if (hobbiesItem != null) {
adapter.add(HobbiesAdapter(hobbiesItem))
}
}
tophobbies.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
HobbiesAdapter
class HobbiesAdapter(val hobbyItem: HobbiesClass) : Item<GroupieViewHolder>() {
var list: ArrayList<String> = ArrayList()
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.hobbynameTV.text = hobbyItem.hobbyName
Picasso.get().load(hobbyItem.imageUrl).into(viewHolder.itemView.hobbyImageView)
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
}
}
}
override fun getLayout(): Int {
return R.layout.row
}
}
HobbiesClass
#Parcelize
class HobbiesClass(val hobbyName: String, val imageUrl: String,var isSelected:Boolean) : Parcelable {
constructor() : this("", "",false)
}
My item row xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="164dp"
android:layout_height="70dp"
android:layout_marginStart="5dp"
android:background="#android:color/transparent"
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="#+id/frameHobby"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/hobbiesbackground">
<TextView
android:id="#+id/hobbynameTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Camping"
android:textColor="#000000"
android:fontFamily="#font/extralight"
android:layout_gravity="center|right"
android:layout_marginEnd="15dp"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/hobbyImageView"
android:layout_width="54dp"
android:layout_height="47dp"
android:layout_gravity="center|left"
android:layout_marginStart="12dp"/>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I can change items background with
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
}
}
What I want to do is: Add the selected elements names to my arraylist. If I use the code provided under it adds the name string to the array but when I click to another one the previous one gets deleted.(It looks like the array is not saved, it resets everytime I press an item) Also if I click on multiple items rapidly then it adds them. But if I click on an item split second later all previous items from the arraylist get deleted. How can I fix this? I have looked on multiple videos but still could not get any answers.
viewHolder.itemView.setOnClickListener {
list.add(viewHolder.itemView.hobbynameTV.text.toString())
}
Thank you in advance!
I have found the solution. I knew the problem that was making my arraylist reset everytime was that the bind method runs everytime it is clicked. So I made a mutable list called selectedList in my Hobbies class where the recyclerview is stored. Then I past it as a parameter in my HobbiesAdapter. Then in My Hobbies class I changed the adapter from adapter.add(HobbiesAdapter(hobbiesItem))
to adapter.add(HobbiesAdapter(hobbiesItem,selectedItems))
Here is how my code looks like now. I hope it will help others in the future
Hobbies class
class Hobbies : AppCompatActivity(){
private val selectedItems = mutableListOf<String>()
#SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hobbies)
val database = FirebaseDatabase.getInstance()
tophobbies.layoutManager =
object : LinearLayoutManager(this, HORIZONTAL, false) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
// force height of viewHolder here, this will override layout_height from xml
lp.height = recyclerlinear.height
return true
}
}
val adapter = GroupAdapter<GroupieViewHolder>()
val reference = database.getReference("Hobbies")
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snap in snapshot.children) {
val hobbiesItem = snap.getValue(HobbiesClass::class.java)
if (hobbiesItem != null) {
adapter.add(HobbiesAdapter(hobbiesItem,selectedItems))
}
}
tophobbies.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
letsgobtn.setOnClickListener {
Toast.makeText(this,"Selected item is ${selectedItems.toString()}",Toast.LENGTH_SHORT).show()
}
}
}
HobbiesAdapter class
class HobbiesAdapter(val hobbyItem: HobbiesClass,val selectedItems: MutableList<String>) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.hobbynameTV.text = hobbyItem.hobbyName
Picasso.get().load(hobbyItem.imageUrl).into(viewHolder.itemView.hobbyImageView)
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
selectedItems.remove(viewHolder.itemView.hobbynameTV.text.toString())
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
selectedItems.add(viewHolder.itemView.hobbynameTV.text.toString())
}
}
}
override fun getLayout(): Int {
return R.layout.row
}
}
HobbiesClass class
#Parcelize
class HobbiesClass(val hobbyName: String, val imageUrl: String,var isSelected:Boolean) : Parcelable {
constructor() : this("", "",false)
}

If data in a recycler view item is null then don't display the view holder

I have an API which give me the list of doctors. On it's last page only 1 item is there and other items are null like this:
After this i have used paging library for pagination
my pagingSource code: `
class DocPagingSource(val docRepository: DocRepository): PagingSource<Int, Data>() {
override fun getRefreshKey(state: PagingState<Int, Data>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Data> {
return try {
val currentPage = params.key?: 1
val city: String = ""
val response = docRepository.getDoctors(city, currentPage)
val page = Math.ceil(response.body()!!.total.toDouble()/5).toInt()
val data = response.body()!!.data
val responseData = mutableListOf<Data>()
responseData.addAll(data)
LoadResult.Page(
data = responseData,
prevKey = if(currentPage==1) null else -1,
nextKey = if (currentPage== page) null else currentPage.plus(1)
)
}catch (e: HttpException){
LoadResult.Error(e)
}catch (e: Exception){
LoadResult.Error(e)
}
}
`
My paging Adapter Code:
class DocAdapter(val context: Context): PagingDataAdapter<Data, DocAdapter.DocViewHolder>(DiffUtil()) {
private lateinit var binding: ItemDoctorsBinding
inner class DocViewHolder : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Data?) {
binding.apply {
txtDocCity.text = item?.city
txtDocName.text = item?.docName
txtDocFees.text = item?.docConsultationFee
txtDocYOE.text = item?.docYoE
txtDocSpecialisation.text = item?.docSpecialisation
Glide.with(context)
.load(item?.docProfileImgUrl)
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(docPhoto)
}
}
}
override fun onBindViewHolder(holder: DocViewHolder, position: Int) {
val item = getItem(position)
if (item!=null){
holder.bind(getItem(position)!!)
holder.setIsRecyclable(false)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DocViewHolder {
val inflater = LayoutInflater.from(context)
binding = ItemDoctorsBinding.inflate(inflater, parent, false)
return DocViewHolder()
}
class DiffUtil: androidx.recyclerview.widget.DiffUtil.ItemCallback<Data>(){
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.docId == newItem.docId
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem==newItem
}
}}
what I am getting after reaching my 16th item in doctor list on last page it should show entry till 16th item but after that it also shows again like this:
Also if i dont use holder.setIsRecyclable(false) in pagingAdapter then this android icon not shown but then list is populated with previous doctors:
on the top DR. Sixteen is shown like this:
and in between it again shows like this:
My doctorViewModel Class:
class DocViewModel(val repository: DocRepository): ViewModel() {
val loading = MutableLiveData<Boolean>()
val docList = Pager(PagingConfig(5, maxSize = 100)){
DocPagingSource(repository)
}.flow.cachedIn(viewModelScope)}
My main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var docViewModel: DocViewModel
private lateinit var docAdapter: DocAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val docListRepository = DocRepository()
val docFactory = DocViewModelFactory(docListRepository)
docViewModel = ViewModelProvider(this, docFactory).get(DocViewModel::class.java)
docAdapter = DocAdapter(this)
lifecycleScope.launchWhenCreated {
docViewModel.docList.collect{
docAdapter.submitData(it)
}
}
binding.docRecyclerView.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = docAdapter
setHasFixedSize(true)
}
}}
I have solved this error by applying a condition in my paging source code

Why is populating the BindingAdapter empty / null with complex case. MVVM

When I run the app, the fragments content is blank. Even though the log statements show, the list is populated. I tried implementing a favorite post feature. You can add/remove a favorite post to your list. This works fine.
The goal:
I want to display the favorite posts in FavoritePostsOverViewFragment. Using a recyclerView.
I'm also trying to follow MVVM architecture. Using a Room database. (no API at this point)
The problem(s):
Working with the 2 different objects seems a bit weird the way I do it right now. But it is populated at the moment
Please refer to the part "How I am getting the posts based on if they have been favorite by a user" Is there a less complex way of writing this?
The Binding Adapter is null / empty, not displaying the posts.
I am using the Adapter already in another fragment, it works fine there. I can see a list of posts and use the click listeners. So In my thoughts, I eliminated the adapter as a problem for this case.
The two data classes used:
data class Post(
var Id: Long = 0L,
var Text: String = "",
var Picture: Bitmap? = null,
var Link: String = "",
var UserId: String = "",
var UserEmail: String = ""
)
data class Favorite(
var Id: Long = 0L,
var UserId: String = "",
var PostId: Long = 0L
)
The Adapter
lass PostAdapter(val clickListener: PostListener, val favoriteListener: FavoriteListener) :
ListAdapter<Post, ViewHolder>(PostDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(clickListener, favoriteListener, item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
}
class ViewHolder(val binding: PostListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(clickListener: PostListener, favoriteListener: FavoriteListener, item: Post) {
binding.post = item
binding.clickListener = clickListener
binding.favoriteListener = favoriteListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
println(layoutInflater.toString())
val binding = PostListItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem.Id == newItem.Id
}
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem == newItem
}
}
class PostListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
class FavoriteListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
How I am getting the posts based on if they have been favorite by a user.
class PostRepository(private val faithDatabase: FaithDatabase) {
suspend fun getUserFavs(): List<Long> {
return withContext(Dispatchers.IO) {
faithDatabase.favoriteDatabaseDao.getUserFavorites(CredentialsManager.cachedUserProfile?.getId()!!)
}
}
suspend fun getFavos(): LiveData<List<Post>> {
val _items: MutableLiveData<List<Post>> = MutableLiveData(listOf())
val items: LiveData<List<Post>> = _items
val postIds: List<Long>
var dbPost: DatabasePost
withContext(Dispatchers.IO) {
postIds = getUserFavs()
}
for (id in postIds) {
withContext(Dispatchers.IO) {
dbPost = faithDatabase.postDatabaseDao.get(id)
}
val post = Post(
Text = dbPost.Text,
UserId = dbPost.UserId,
UserEmail = dbPost.UserEmail,
Link = dbPost.Link,
Picture = dbPost.Picture,
Id = dbPost.Id
)
_items.value = _items.value?.plus(post) ?: listOf(post)
}
Timber.i("items= " + items.value!!.size)
/*this logs=
I/PostRepository: items= 2*/
return items
}
My FavoritePostOverViewModel
class FavoritePostsOverviewViewModel(val database: PostDatabaseDao, app: Application) :
AndroidViewModel(app) {
private val db = FaithDatabase.getInstance(app.applicationContext)
private val postRepository = PostRepository(db)
var posts: LiveData<List<Post>>? = null
init {
viewModelScope.launch {
posts = repository.getFavos()
Timber.i(posts!!.value.toString())
/* this logs=
I/FavoritePostsOverviewViewModel: [Post(Id=1, Text=Name, Picture=android.graphics.Bitmap#ef3b553, Link=Add your link here, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com), Post(Id=4, Text=test, Picture=android.graphics.Bitmap#35ae90, Link=www.google.com, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com)]*/
}
}
my FavoritePostsOverViewFragment
class FavoritePostsOverViewFragment : Fragment() {
lateinit var binding: FragmentFavoritePostsBinding
private lateinit var favoritePostsOverviewViewModel: FavoritePostsOverviewViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// setup the db connection
val application = requireNotNull(this.activity).application
val dataSource = FaithDatabase.getInstance(application).postDatabaseDao
// create the factory + viewmodel
val viewModelFactory = FavoritePostsOverviewViewModelFactory(dataSource, application)
favoritePostsOverviewViewModel =
ViewModelProvider(this, viewModelFactory)[FavoritePostsOverviewViewModel::class.java]
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_favorite_posts, container, false)
// Giving the binding access to the favoritePostsOverviewViewModel
binding.favoritePostsOverviewViewModel = favoritePostsOverviewViewModel
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
binding.lifecycleOwner = this
// Sets the adapter of the PostAdapter RecyclerView with clickHandler lambda that
// tells the viewModel when our property is clicked
binding.postList.adapter = PostAdapter(PostListener {
favoritePostsOverviewViewModel.displayPropertyDetails(it)
}, FavoriteListener {
favoritePostsOverviewViewModel.FavoriteClick(it)
})
return binding.root
}
I have a Binding Adapter
#BindingAdapter("listData")
fun bindRecyclerViewPost(recyclerView: RecyclerView, data: List<Post>?) {
if (data.isNullOrEmpty()) {
return
}
val adapter = recyclerView.adapter as PostAdapter
adapter.submitList(data)
}
Used in the XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="favoritePostsOverviewViewModel"
type="com.example.ep3_devops_faith.ui.post.favorites.FavoritePostsOverviewViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:listData="#{favoritePostsOverviewViewModel.posts}"
tools:listitem="#layout/post_list_item"
tools:itemCount="16"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
referenced articles:
Android BindingAdapter order of execution?
LiveData Observer in BindingAdapter
https://developer.android.com/topic/architecture
https://developer.android.com/topic/libraries/data-binding/binding-adapters
https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/01_Introduction
try changing this line
app:listData="#{favoritePostsOverviewViewModel.posts}"
to
app:listData="#{favoritePostsOverviewViewModel.posts.value}"
I guess, you are binding list of posts in your binding adapter and you are passing LiveData<List>

RecyclerView binding with LiveData displaying wrong values after item is removed

I'm using a RecyclerView with LiveData and databinding. The user can remove elements from the recyclerview, and sometimes, after an item is removed, the items in the list are reordered incorrectly and the wrong item is removed.
In printing the values in LogCat, the correct values/order are always printed, so it's somewhere in the display/binding that the items are getting (I believe) recycled improperly, but I haven't been able to resolve it.
Here's what I'm working with: Fragment, ViewModel, ItemPresenter, Adapter, list_item.xml
Below are what I believe to be the relevant parts of each file:
Fragment
viewModel
.personalFilesLiveData
.observe(viewLifecycleOwner, Observer {
personalFilesAdapter.bind(it)
if(personalFilesAdapter.itemCount == 0){
this.activity?.findViewById<ConstraintLayout>(R.id.no_results_container)?.visibility = View.VISIBLE
}
else{
this.activity?.findViewById<ConstraintLayout>(R.id.no_results_container)?.visibility = View.GONE
}
})
Presenter
class ItemPresenter(
private val openContentAction: (String) -> Unit,
private val removePersonalFileAction: (String) -> Unit) {
private lateinit var myPersonalFile: MyPersonalFileData
val name get() = myPersonalFile.name
val fileName get() = myPersonalFile.fileName
val month get() = myPersonalFile.monthDay
val year get() = myPersonalFile.year
fun bind(file: MyPersonalFileData) {
Log.d("MyFilesPersonalFileItemPresenter", "name: " + file.name)
this.myPersonalFile = file
}
fun contentClicked() {
openContentAction(myPersonalFile.contentId)
}
fun removeClicked() {
removePersonalFileAction(myPersonalFile.contentId)
}
}
Adapter
class Adapter(
private val openContentAction: (String) -> Unit,
private val removePersonalFileAction: (String) -> Unit
) : RecyclerView.Adapter<DataboundViewHolder<ViewMyPersonalFilesItemBinding>>() {
private var data = emptyList<MyPersonalFileData>()
fun bind(data: List<MyPersonalFileData>) {
val diff = RecyclerViewDiffHelper.simpleDiffUtil(this.data, data) {
first, second -> first.contentId == second.contentId
}
this.data = data
diff.dispatchUpdatesTo(this)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataboundViewHolder<ViewMyPersonalFilesItemBinding> {
return DataboundViewHolder(
ViewMyPersonalFilesItemBinding.inflate(LayoutInflater.from(parent.context), parent, false).apply {
presenter = MyFilesPersonalFileItemPresenter(openContentAction, removePersonalFileAction)
}
)
}
override fun getItemCount() = data.count()
override fun onBindViewHolder(holder: DataboundViewHolder<ViewMyPersonalFilesItemBinding>, position: Int) {
AlternateBackgroundHelper.setBackground(holder.binding.root, position)
holder.binding.presenter?.bind(data[position])
}
}
class MyPersonalFileData(
val name: String,
val fileName: String,
val monthDay: String,
val year: String,
val contentId: String
) {
override fun toString(): String {
return "MyPersonalFileData(name='$name')"
}
}
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="presenter"
type="com.storyslab.helper.myfiles.bookmarks.MyFilesBookmarkItemPresenter" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:onClick="#{() -> presenter.contentClicked()}"
android:foreground="?selectableItemBackground"
tools:background="#313131">
<TextView
android:id="#+id/tv_content_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="#android:color/white"
android:textStyle="bold"
android:text="#{presenter.name}"
app:fontFamily="#font/oswald_regular"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toStartOf="#id/tv_date_top_line"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="#id/tv_file_name"
tools:text="Content Name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Any ideas?
I think the problem lies within this portion of your code in the adapter:
private var data = emptyList<MyPersonalFileData>()
fun bind(data: List<MyPersonalFileData>) {
val diff = RecyclerViewDiffHelper.simpleDiffUtil(this.data, data) {
first, second -> first.contentId == second.contentId
}
this.data = data // PROBLEM HERE!
diff.dispatchUpdatesTo(this)
}
You're replacing the main list of the adapter with the list that comes from your activity/fragment. In my opinion, we should never expose the same list reference to the adapter this way. Doing this may cause unexpected results like the one you're facing here.
Try like this and your problem should be resolved:
private val data = mutableListOf<MyPersonalFileData>()
fun bind(data: List<MyPersonalFileData>) {
val diff = RecyclerViewDiffHelper.simpleDiffUtil(this.data, data) {
first, second -> first.contentId == second.contentId
}
this.data.clear()
this.data.addAll(data)
diff.dispatchUpdatesTo(this)
}
Give it a try. Just replace the bind method and the data list with the following list.
private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
fun bind(newList: List<Operator>) {
differ.submitList(newList)
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Operator>() {
override fun areItemsTheSame(oldItem: MyPersonalFileData, newItem: MyPersonalFileData): Boolean =
oldItem.contentId == newItem.contentId
override fun areContentsTheSame(oldItem: Operator, newItem: Operator): Boolean {
return oldItem == newItem
}
}
}

Android Recycleview Multiple ViewTypes not working in kotlin

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

Categories

Resources