0

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):

  1. 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?
  2. 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

1 Answers1

0

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>

Vivek Gupta
  • 747
  • 4
  • 13