Search code examples
androidkotlindata-bindingretrofit2dagger-2

JSON data not showing in the Recylerview MVVM


I'm trying to parse Json in Recyclerview using Data binding. When I run the app it's only showing blank screen with no error/crashes.

Json data:

{ "msg":[ "football", "cricket", "baseball", "rugby", "gulf" ], "status":"success" }

The Api

interface SportsApi {
    /**
     * Get the Sports from the API
     */
    @GET("/sports")
    fun getSports(): Observable<Sports>
}

The adaperclass:

class PostListAdapter: RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
    private lateinit var postList:Sports

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
        val binding: ItemPostBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_post, parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
        holder.bind(postList)
    }

    override fun getItemCount(): Int {

        return if(::postList.isInitialized) postList.msg.size else 0

    }

    fun updatePostList(postList: Sports){
        this.postList = postList
        notifyDataSetChanged()
    }

    class ViewHolder(private val binding: ItemPostBinding):RecyclerView.ViewHolder(binding.postTitle){
        private val viewModel = PostViewModel()

        fun bind(post: Sports){
            viewModel.bind(post)
            binding.viewModel = viewModel
        }
    }
}

And the PostViewModel class:

class PostViewModel:BaseViewModel() {
    private val postTitle = MutableLiveData<String>()

    fun bind(sports: Sports){
        postTitle.value = sports.msg.toString()
    }

    fun getPostTitle():MutableLiveData<String>{
        return postTitle
    }

}

Can Someone please let me know where am I doing incorrectly or how to resolve it? If you need any further detail please let me know.

Edit: item_post.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">

    <data>
        <variable
            name="viewModel"
            type="com.gk007.example.ui.post.PostViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="16dp"
        android:paddingRight="16dp">

        <TextView
            android:id="@+id/post_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            app:mutableText="@{viewModel.getPostTitle()}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
</layout>

PostListViewModel class:

class PostListViewModel:BaseViewModel(){
    @Inject
    lateinit var postApi: PostApi
    val postListAdapter: PostListAdapter = PostListAdapter()

    val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
    val errorMessage:MutableLiveData<Int> = MutableLiveData()
    val errorClickListener = View.OnClickListener { loadPosts() }

    private lateinit var subscription: Disposable

    init{
        loadPosts()
    }

    override fun onCleared() {
        super.onCleared()
        subscription.dispose()
    }

    private fun loadPosts(){
        subscription = postApi.getPosts()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe { onRetrievePostListStart() }
            .doOnTerminate { onRetrievePostListFinish() }
            .subscribe(
                { result -> onRetrievePostListSuccess(result) },
                { onRetrievePostListError() }
            )
    }

    private fun onRetrievePostListStart(){
        loadingVisibility.value = View.VISIBLE
        errorMessage.value = null
    }

    private fun onRetrievePostListFinish(){
        loadingVisibility.value = View.GONE
    }

    private fun onRetrievePostListSuccess(postList: List<String>){
        postListAdapter.updatePostList(postList)
    }

    private fun onRetrievePostListError(){
        errorMessage.value = R.string.post_error
    }
}

Solution

  • Try with this adapter and holder. Remove ViewModel from holder. Rather than passing full Sports object you should pass only List.

    class PostListAdapter: RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
        private lateinit var sports: List<String>
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
            val binding: ItemPostBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_post, parent, false)
            return ViewHolder(binding)
        }
    
        override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
            holder.bind(sports[position])
        }
    
        override fun getItemCount(): Int {
    
            return if(::sports.isInitialized) sports.size else 0
    
        }
    
        fun updateSports(sports: List<String>){
            this.sports = sports
            notifyDataSetChanged()
        }
    
        class ViewHolder(private val binding: ItemPostBinding):RecyclerView.ViewHolder(binding.root){
            fun bind(sport: String){
                binding.postTitle.text = sport
            }
        }
    }
    

    Then pass only msg, List to updateSports

    And also remove ViewModel from 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">
    
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingLeft="16dp"
            android:paddingRight="16dp">
    
            <TextView
                android:id="@+id/post_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    </layout>
    

    Updated ViewModel:

    class PostListViewModel:BaseViewModel(){
        @Inject
        lateinit var postApi: PostApi
        val postListAdapter: PostListAdapter = PostListAdapter()
    
        val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
        val errorMessage:MutableLiveData<Int> = MutableLiveData()
        val errorClickListener = View.OnClickListener { loadPosts() }
    
        private lateinit var subscription: Disposable
    
        init{
            loadPosts()
        }
    
        override fun onCleared() {
            super.onCleared()
            subscription.dispose()
        }
    
        private fun loadPosts(){
            subscription = postApi.getPosts()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe { onRetrievePostListStart() }
                .doOnTerminate { onRetrievePostListFinish() }
                .subscribe(
                    { result -> onRetrievePostListSuccess(result.msg) },
                    { onRetrievePostListError() }
                )
        }
    
        private fun onRetrievePostListStart(){
            loadingVisibility.value = View.VISIBLE
            errorMessage.value = null
        }
    
        private fun onRetrievePostListFinish(){
            loadingVisibility.value = View.GONE
        }
    
        private fun onRetrievePostListSuccess(postList: List<String>){
            postListAdapter.updateSports(postList)
        }
    
        private fun onRetrievePostListError(){
            errorMessage.value = R.string.post_error
        }
    }