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