简体   繁体   中英

Grid reusable recycler view not displaying in view pager

I have a recycler view which I am reusing along with a fragment being shared by two screens in a view pager, with a tab layout on top. The recycler view will display a vertical list on the first tab, and a grid on the second tab. The list is rendered fine for the first tab and for the second tab, the ImageViewHolder is instantiated and its data is set correctly, it is just a gallery of images but it is not displayed on the screen. This is what appears instead在此处输入图像描述

When if I tap back on news the list disappears from the screen which only shows this same ugly layout from the images tab. I honestly have no clue what is happening. The data is being fetched from the API and the variables are filled correctly. This is the code I have in my fragment, note it is reused by the second tab and the right logic gets executed. I set the adapter's layout manager and the data in the observeImages function, I also call fetch news at the end because I need it so the user can store images and news locally, I could have processed both in parallel too

package io.keepcoding.globaldisastertracker.ui.detail

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.floatingactionbutton.FloatingActionButton
import io.keepcoding.globaldisastertracker.R
import io.keepcoding.globaldisastertracker.repository.local.DisasterEventsRoomDatabase
import io.keepcoding.globaldisastertracker.repository.local.LocalHelperImpl
import io.keepcoding.globaldisastertracker.repository.remote.ApiHelperImpl
import io.keepcoding.globaldisastertracker.repository.remote.RemoteDataManager
import io.keepcoding.globaldisastertracker.ui.main.EventItemViewModel
import io.keepcoding.globaldisastertracker.utils.CustomViewModelFactory
import io.keepcoding.globaldisastertracker.utils.Status
import kotlinx.android.synthetic.main.fragment_detail.list
import kotlinx.android.synthetic.main.fragment_detail.loadingView
import kotlinx.android.synthetic.main.fragment_detail.retry
import kotlinx.android.synthetic.main.try_again.*

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_FROM_SERVER = "FROM_SERVER"
private const val ARG_EVENT_ITEM = "EVENT_ITEM"
private const val ARG_IS_NEWS_FRAGMENT ="IS_NEWS_FRAGMENT"


/**
 * A simple [Fragment] subclass.
 * Use the [ListFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class DetailFragment : Fragment() {
    // Fragment parameters
    private val fromServer: Boolean by lazy {
        var server = false
        arguments?.let {
            server = it.getBoolean(ARG_FROM_SERVER)
        }
        server
    }
    private val eventItem: EventItemViewModel by lazy {
        lateinit var eventViewModel: EventItemViewModel
        arguments?.let {
            eventViewModel = it.getParcelable(ARG_EVENT_ITEM)!!
        }
        eventViewModel
    }
    private var isNewsFragment: Boolean = false

    private var imageItems: List<ImageItemViewModel?>? = mutableListOf(null)

    private var newsItems: List<NewsItemViewModel?>? = mutableListOf(null)

    private var detailsAdapter: DetailAdapter? = null


    private val viewModel: DetailFragmentViewModel by lazy {
        val factory = CustomViewModelFactory(requireActivity().application,
            ApiHelperImpl(RemoteDataManager().bingSearchApi, RemoteDataManager().eonetApi),
            LocalHelperImpl(DisasterEventsRoomDatabase.getInstance(requireActivity()))
        )
        ViewModelProvider(this, factory).get(DetailFragmentViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            isNewsFragment = it.getBoolean(ARG_IS_NEWS_FRAGMENT)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_detail, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setUpListeners()
        setUpRecyclerView()
        setUpObservers()
    }

    fun setFABClickListener(fab: FloatingActionButton?){
        if(fromServer){
            fab?.setOnClickListener {
                viewModel.saveEvent(eventViewModel = eventItem)
            }
        } else {
            fab?.setOnClickListener {
                viewModel.deleteEvent(eventItem.id!!)
            }
        }
    }

    private fun setUpListeners(){
        buttonRetry.setOnClickListener {
            fetchData(eventItem)
        }
    }

    private fun setAdapter(){
        context?.let { context ->
            detailsAdapter = DetailAdapter(context, requireActivity() as DetailActivity)
            detailsAdapter?.setData(newsItems, imageItems, isNewsFragment)
        }
    }

    private fun setUpRecyclerView(){
        if(isNewsFragment){ // Display linear layout with news
            list.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        } else { // Display grid layout with images
            list.layoutManager = GridLayoutManager(context, 3)
            list.addItemDecoration(DividerItemDecoration(context, GridLayoutManager.VERTICAL))
        }
        setAdapter()
    }

    private fun fetchData(event: EventItemViewModel){
        if(fromServer){
            event.title?.let { title ->
                if(isNewsFragment) viewModel.fetchApiNews(title)
                else viewModel.fetchApiImages(title)
            }
        } else {
            event.id?.let {id ->
                if(isNewsFragment) viewModel.loadNewsFromLocal(id)
                else viewModel.loadImagesFromLocal(id)
            }
        }
    }

    private fun observeImages(){
        viewModel.getImages().observe(viewLifecycleOwner, Observer { images ->
            when (images.status) {
                Status.SUCCESS -> {
                    imageItems = images.data
                    loadingView.visibility = View.GONE
                    retry.visibility = View.GONE
                    list.visibility = View.VISIBLE
                    setAdapter()
                    list.adapter = detailsAdapter
                    viewModel.fetchApiNews(eventItem.title!!)
                }
                Status.LOADING -> {
                    loadingView.visibility = View.VISIBLE
                    retry.visibility = View.INVISIBLE
                    list.visibility = View.INVISIBLE
                }
                Status.ERROR -> {
                    retry.visibility = View.VISIBLE
                    loadingView.visibility = View.INVISIBLE
                    list.visibility = View.INVISIBLE
                }
            }
        })
    }

    private fun observeNews(){
        viewModel.getNews().observe(viewLifecycleOwner, Observer { news ->
            when (news.status) {
                Status.SUCCESS -> {
                    newsItems = news.data
                    loadingView.visibility = View.GONE
                    retry.visibility = View.GONE
                    list.visibility = View.VISIBLE
                    setAdapter()
                    list.adapter = detailsAdapter
                    viewModel.fetchApiImages(eventItem.title!!) // We fetch the images too, in order to store them, if user has not switched to images tab
                }
                Status.LOADING -> {
                    retry.visibility = View.INVISIBLE
                    loadingView.visibility = View.VISIBLE
                    list.visibility = View.INVISIBLE
                }
                Status.ERROR -> {
                    retry.visibility = View.VISIBLE
                    loadingView.visibility = View.INVISIBLE
                    list.visibility = View.INVISIBLE
                }
            }
        })
    }
    private fun setUpObservers(){
        fetchData(eventItem)
        if(isNewsFragment){
            observeNews()
        } else {
            observeImages()
        }
        viewModel.getSnackBar().observe(viewLifecycleOwner, Observer { snackBar ->
            when(snackBar.status) {
                Status.SUCCESS -> Toast.makeText(requireActivity().application, snackBar.data, Toast.LENGTH_LONG).show()
                else -> Toast.makeText(requireActivity().application, snackBar.message, Toast.LENGTH_LONG).show()
            }
        })
    }


    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment ListFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(fromServer: Boolean, eventItem : EventItemViewModel, isNewsFragment: Boolean) =
            DetailFragment().apply {
                arguments = Bundle().apply {
                    putBoolean(ARG_FROM_SERVER, fromServer)
                    putBoolean(ARG_IS_NEWS_FRAGMENT, isNewsFragment)
                    putParcelable(ARG_EVENT_ITEM, eventItem)

                }
            }
    }
}

This is the code for my adapter

package io.keepcoding.globaldisastertracker.ui.detail

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import io.keepcoding.globaldisastertracker.R
import kotlinx.android.synthetic.main.events_recycler_view_item.view.*
import kotlinx.android.synthetic.main.images_recycler_view_item.view.*
import kotlinx.android.synthetic.main.news_recycler_view_item.view.*

class DetailAdapter(val context: Context, itemClickListener: DetailInteractionListener? = null) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var newsItems = listOf<NewsItemViewModel?>()
    private var imagesItems = listOf<ImageItemViewModel?>()
    private var isNewsFragment: Boolean = false

    private var newsDetailInteractionListener: ((View) -> Unit)? = {
        if(it.tag is NewsItemViewModel){
            itemClickListener?.onNewsItemClick((it.tag as NewsItemViewModel).newsUrl as String)
        }
    }
    private var imagesDetailInteractionListener: ((View) ->Unit)? = {
        if(it.tag is ImageItemViewModel){
            itemClickListener?.onImageItemClick((it.tag as ImageItemViewModel).image as String)
        }
    }
    companion object {
        const val NEWS = 1
        const val IMAGE = 2
    }

    fun setData(newsList: List<NewsItemViewModel?>?, imagesList: List<ImageItemViewModel?>?, isNews: Boolean){
        isNewsFragment = isNews
        if(isNewsFragment) {
            newsList?.let {
                newsItems = it
            }
        }
        else{
            imagesList?.let {
                this.imagesItems = it
            }
        }
        notifyDataSetChanged()
    }

    inner class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var article: NewsItemViewModel? = null
            set(value) {
                field = value
                itemView.tag = field
                field?.let {
                    Glide.with(context)
                        .load(it.thumbnail)
                        .apply {
                            RequestOptions()
                                .placeholder(R.drawable.ic_launcher_background)

                        }.into(itemView.news_image)
                    itemView.headline.text = it.title
                    itemView.content.text = it.description
                }
            }
    }

    inner class ImagesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var image: ImageItemViewModel? = null
            set(value) {
                field = value
                itemView.tag = field
                field?.let {
                    Glide.with(context)
                        .load(it.image)
                        .apply {
                            RequestOptions()
                                .placeholder(R.drawable.ic_launcher_background)

                        }.into(itemView.image)
                }
            }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if(isNewsFragment){
            return NewsViewHolder(LayoutInflater.from(context).inflate(R.layout.news_recycler_view_item, parent, false))
        }
        return ImagesViewHolder(LayoutInflater.from(context).inflate(R.layout.images_recycler_view_item, parent, false))
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if(isNewsFragment){
            val article = newsItems[position]
            (holder as NewsViewHolder).article = article
            holder.itemView.setOnClickListener(newsDetailInteractionListener)
        } else {
            val image = imagesItems[position]
            (holder as ImagesViewHolder).image = image
            holder.itemView.setOnClickListener(imagesDetailInteractionListener)
        }
    }

    override fun getItemCount(): Int {
        return if(isNewsFragment){
            newsItems.size
        } else imagesItems.size
    }
}

The logic runs fine in the adapter and the data is there so I think the issue is with the layout files but I am really bad at writing the xml for the UI, it gives me all sorts of bugs sometimes.

These are my layout files

DetailActivity

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    tools:context=".ui.detail.DetailActivity"
    >
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"/>

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?actionBarSize"
        app:tabTextColor="@color/design_default_color_on_primary"
        app:tabGravity="fill"
        app:tabIndicatorColor="@color/design_default_color_on_primary"
        app:tabIndicatorHeight="4dp"
        app:tabBackground="@color/colorPrimary"
        app:tabMode="fixed">
    </com.google.android.material.tabs.TabLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </androidx.viewpager2.widget.ViewPager2>
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email" />
    </RelativeLayout>
</LinearLayout>

Fragment detail

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.detail.DetailFragment"
    android:orientation="vertical">

    <include
        android:id="@+id/loadingView"
        layout="@layout/view_loading" />
    <include
        android:id="@+id/retry"
        layout="@layout/try_again"
        android:visibility="invisible"
        />
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />
</LinearLayout>

Image item

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity = "center"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_launcher_background" />
    />
</LinearLayout>

Github link is here https://github.com/AntonioRoldan/GlobalDisasterTracker

While setting the recylerView it is declared as GridLayout but in fact in your DetailAdapter the RecyclerView.ViewHolder for your images was declared as LinearLayout .

private fun setUpRecyclerView(){
        if(isNewsFragment){ // Display linear layout with news
            list.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        } else { // Display grid layout with images
            list.layoutManager = GridLayoutManager(context, 3)
            list.addItemDecoration(DividerItemDecoration(context, GridLayoutManager.VERTICAL))
        }
        setAdapter()
    }

So it's a problem with your layout/images_recycler_view_item.xml file. Try changing it to GridLayout as follows:

<?xml version="1.0" encoding="utf-8"?>

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/item"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:scaleType="centerCrop"
            android:src="@drawable/ic_launcher_background" />
        />
    </androidx.cardview.widget.CardView>
</GridLayout>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM