Search code examples
androidandroid-fragmentsandroid-recyclerviewviewmodelandroid-livedata

Android. When displaying a fragment on the screen, live data does not display the data


I recently started learning Android and I have a problem. I use MVVM, Coroutines, Live Data, Dagger 2, Retrofit in the project. The problem is that the data is not displayed in the Fragment in the Recycler view, but when changing the configuration (screen rotation), the data is displayed.I tried to find a solution on the internet. But what I found did not lead to success

MainPageFragment

`class MainPageFragment : Fragment() { // TODO: Rename and change types of parameters private var param1: String? = null private var param2: String? = null private lateinit var binding: FragmentMainPageBinding

@Inject
lateinit var factory : ShopAppViewModelFactory
private val viewModel: ShopAppViewModel by lazy {
   ViewModelProvider(this, factory).get(ShopAppViewModel::class.java)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    arguments?.let {
        param1 = it.getString(ARG_PARAM1)
        param2 = it.getString(ARG_PARAM2)
    }
    component.inject(this)
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    binding = FragmentMainPageBinding.inflate(inflater, container, false)

    val recyclerviewLatestProducts = binding.recyclerViewLatestProduct
    val recyclerviewProductsOnDiscont = binding.recyclerViewProductsOnDiscont

    val adapterLatestProduct = ShopAppRecyclerViewAdapter()
    val adapterProductsOnDiscont = ShopAppRecyclerViewAdapter()

    recyclerviewLatestProducts.adapter = adapterLatestProduct
    recyclerviewProductsOnDiscont.adapter = adapterProductsOnDiscont

    viewModel.getLatestProduct().observe(viewLifecycleOwner, {
        adapterLatestProduct.products = it
    })

    viewModel.getProductOnDiscont().observe(viewLifecycleOwner) {
        adapterProductsOnDiscont.products = it
    }

    viewModel.getDataAboutProductsFromNetwork()

    return binding.root
}

}`

ShopAppViewModel

`class ShopAppViewModel(private val repository: Repository) : ViewModel() {

private var latestProduct = MutableLiveData<List<Product>>()
private var productOnDiscont = MutableLiveData<List<Product>>()


fun getLatestProduct(): MutableLiveData<List<Product>> {
    return latestProduct
}

fun getProductOnDiscont(): MutableLiveData<List<Product>> {
    return productOnDiscont
}

fun getDataAboutProductsFromNetwork() {

    try {
        viewModelScope.launch {

            val latest = async {
                repository.getLatestProducts()
            }

            val flashSale = async {
                repository.getProductsOnDiscont()
            }

            processData(latest.await(), flashSale.await())
        }

    } catch (e: Exception) {
        Log.e(ContentValues.TAG, e.message.toString())
    }
}

private fun processData(responseGoodsLatest: Response<GoodsLatest>, responseFlashSale: Response<ProductOnDiscount>) {

    val bodyLatest = responseGoodsLatest.body()
    val bodyFlashSale = responseFlashSale.body()

    var listLatest = ArrayList<Product>()
    var listProductOnDiscont = ArrayList<Product>()

    if (responseGoodsLatest.isSuccessful && bodyLatest != null) {
        listLatest = transformLatestToProduct((bodyLatest.latest))
    }

    if (responseFlashSale.isSuccessful && bodyFlashSale != null) {
        listProductOnDiscont = transformDiscontToProduct((bodyFlashSale.flash_sale))
    }

    latestProduct.postValue(listLatest)
    productOnDiscont.postValue(listProductOnDiscont)

}

private fun transformLatestToProduct(latests: List<Latest>): ArrayList<Product> {

    val products = ArrayList<Product>()

    for(latest in latests) {
        products.add(Product.Latest(latest.category, latest.image_url, latest.name, latest.price))
    }

    return products
}

private fun transformDiscontToProduct(flashSales: List<FlashSale>): ArrayList<Product> {

    val products = ArrayList<Product>()

    for(flashSale in flashSales) {
        products.add(Product.FlashSale(flashSale.category, flashSale.discount, flashSale.image_url, flashSale.name, flashSale.price))
    }

    return products
}

}`

ShopAppViewModelFactory

`@Suppress("UNCHECKED_CAST") class ShopAppViewModelFactory(private val repository: Repository): ViewModelProvider.Factory {

override fun <T : ViewModel> create(modelClass: Class<T>): T {

    if (modelClass.isAssignableFrom(ShopAppViewModel::class.java)) {
        return ShopAppViewModel(repository) as T
    }

    throw IllegalArgumentException("Unknown ViewModel class")
}

}`

ShopAppRecyclerViewAdapter

`class ShopAppRecyclerViewAdapter : RecyclerView.Adapter(){

var products = listOf<Product>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShopAppViewHolder {

    return when(viewType) {

        R.layout.recyclerview_latest_element -> ShopAppViewHolder.LatestViewHolder(
            RecyclerviewLatestElementBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )

        R.layout.recyclerview_sale_element -> ShopAppViewHolder.FlashSaleViewHolder(
            RecyclerviewSaleElementBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )

        else -> throw IllegalArgumentException("Invalid ViewType Provided")
    }
}

override fun onBindViewHolder(holder: ShopAppViewHolder, position: Int) {

    when (holder) {
        is ShopAppViewHolder.LatestViewHolder -> {
            val product = products[position] as Product.Latest
            holder.bind(product)
            Glide.with(holder.itemView.context)
                .load(product.image_url)
                .into(holder.imageView)
        }

        is ShopAppViewHolder.FlashSaleViewHolder -> {
            holder.bind(products[position] as Product.FlashSale)
            val product = products[position] as Product.FlashSale
            holder.bind(product)
            Glide.with(holder.itemView.context)
                .load(product.image_url)
                .into(holder.imageView)
        }
    }
}

override fun getItemCount(): Int = products.size


override fun getItemViewType(position: Int): Int {
    return when(products[position]){
        is Product.Latest -> R.layout.recyclerview_latest_element
        is Product.FlashSale -> R.layout.recyclerview_sale_element
    }
}

}`

ShopAppViewHolder

`sealed class ShopAppViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {

class LatestViewHolder(private val binding: RecyclerviewLatestElementBinding) : ShopAppViewHolder(binding) {

    val imageView = binding.imageViewBackground

    fun bind(product: Product.Latest) {

        binding.textViewCategory.text = product.category
        binding.textViewName.text = product.name
        binding.textViewPrice.text = product.price.toString()
    }
}

class FlashSaleViewHolder(private val binding: RecyclerviewSaleElementBinding) : ShopAppViewHolder(binding) {

    val imageView = binding.imageViewBackground

    fun bind(product: Product.FlashSale) {

        binding.textViewCategory.text = product.category
        binding.textViewName.text = product.name
        binding.textViewPrice.text = product.price.toString()
        binding.textViewSale.text = product.discount.toString()
    }
}

}`

The problem is that the data is not displayed in the Fragment in the Recycler view, but when changing the configuration (screen rotation), the data is displayed.I tried to find a solution on the internet. But what I found did not lead to success


Solution

  •  viewModel.getLatestProduct().observe(viewLifecycleOwner, {
            adapterLatestProduct.products = it
        })
    

    You cannot just set a new list and expect the Adapter to update. You must notify the Adapter about the change using notifyDataSetChanged().

    But you can simplify your Adapter by using ListAdapter. It will manage updating the list correctly and animate any changed (see also DiffCallback for better update handling).

    ListAdapter supports your approach with data changes via LiveData. When a new data set is submitted you just call adapter.submitList(dataList) to update the adapter.

       viewModel.getLatestProduct().observe(viewLifecycleOwner, {
                adapterLatestProduct.submitList(it)
            })