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.