In my MenuFragment I display products that I can then add to the cart, when I click on any product in the logs I see that the product is added to the list and the list is updated, but when I go to CartFragment nothing is displayed, in the logs I see that the observer observes an empty list and passes one to the adapter.
package com.example.restauratio.cart
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 androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.restauratio.R
import com.example.restauratio.databinding.FragmentCartBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class CartFragment : Fragment() {
private val cartViewModel: CartViewModel by viewModels()
private var _binding: FragmentCartBinding? = null
private val binding get() = _binding!!
private lateinit var cartAdapter: CartAdapter
private val actionCartToPop = R.id.action_cartFragment_pop
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCartBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
cartAdapter = CartAdapter(cartViewModel)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = cartAdapter
cartViewModel.cartItems.observe(viewLifecycleOwner) { cartItems ->
Log.d("CartFragment", "Observed cart items: $cartItems")
cartAdapter.setCartItems(cartItems)
}
binding.imageView4.setOnClickListener {
findNavController().navigate(actionCartToPop)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
package com.example.restauratio.cart
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.restauratio.menu.DishModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class CartViewModel @Inject constructor() : ViewModel() {
private val _cartItems = MutableLiveData<List<DishModel>>()
val cartItems: LiveData<List<DishModel>> get() = _cartItems
fun addToCart(dish: DishModel) {
Log.d("CartViewModel", "Adding to cart: $dish")
val currentItems = _cartItems.value.orEmpty().toMutableList()
val existingDish = currentItems.find { it.id == dish.id }
if (existingDish != null) {
existingDish.quantity += 1
} else {
currentItems.add(dish.copy(quantity = 1))
}
_cartItems.value = currentItems
Log.d("CartViewModel", "Updated cart items: ${_cartItems.value}")
}
fun removeFromCart(cartItem: DishModel) {
Log.d("CartViewModel", "Removing from cart: $cartItem")
val currentCartItems = _cartItems.value.orEmpty().toMutableList()
currentCartItems.remove(cartItem)
_cartItems.value = currentCartItems
}
fun clearCart() {
Log.d("CartViewModel", "Clearing cart")
_cartItems.value = emptyList()
}
}
package com.example.restauratio.cart
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.example.restauratio.databinding.CartItemBinding
import com.example.restauratio.menu.DishModel
import de.hdodenhof.circleimageview.CircleImageView
class CartAdapter(
private val cartViewModel: CartViewModel
) : RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
private var cartItems: List<DishModel> = emptyList()
inner class CartViewHolder(binding: CartItemBinding) : RecyclerView.ViewHolder(binding.root) {
val dishImage: CircleImageView = binding.imageView2
val dishName: TextView = binding.textView2
val dishPrice: TextView = binding.textView
val quantityTextView: TextView = binding.textView55
val removeFromCartButton: ImageView = binding.imageView8
init {
removeFromCartButton.setOnClickListener {
val removedDish = cartItems[adapterPosition]
cartViewModel.removeFromCart(removedDish)
Toast.makeText(itemView.context, "Danie usunięte z koszyka", Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
val inflater = LayoutInflater.from(parent.context)
val cartItemBinding = CartItemBinding.inflate(inflater, parent, false)
return CartViewHolder(cartItemBinding)
}
override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
val cartItem = cartItems[position]
holder.dishName.text = cartItem.name
holder.dishPrice.text = String.format("%.2f zł", cartItem.price)
holder.quantityTextView.text = cartItem.quantity.toString()
}
override fun getItemCount(): Int {
return cartItems.size
}
fun setCartItems(newCartItems: List<DishModel>) {
Log.d("CartAdapter", "Setting cart items: $cartItems")
cartItems = newCartItems
notifyDataSetChanged()
}
}
package com.example.restauratio.menu
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.core.view.GravityCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.restauratio.R
import com.example.restauratio.cart.CartFragment
import com.example.restauratio.cart.CartViewModel
import com.example.restauratio.databinding.FragmentMenuBinding
import com.example.restauratio.loginSession.SessionManager
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MenuFragment : Fragment() {
@Inject
lateinit var sessionManager: SessionManager
private val menuViewModel: MenuViewModel by viewModels()
private val cartViewModel: CartViewModel by viewModels()
private var _binding: FragmentMenuBinding? = null
private val binding get() = _binding!!
private lateinit var dishAdapter: DishAdapter
private val actionMenuToAboutUs = R.id.action_menu_to_aboutUs
private val actionMenuToReservation = R.id.action_menu_to_reservationView
private val actionMenuToRules = R.id.action_menu_to_rulesView
private val actionMenuToPrivacyPolicy = R.id.action_menu_to_privacyPolicy
private val actionLogout = R.id.action_menu_pop
private val actionMenuToAlerts = R.id.action_menu_to_alerts
private val actionMenuToProfile = R.id.action_menu_to_profile
private val actionMenuToOrders = R.id.action_menu_to_orders
private val actionMenuToCart = R.id.action_menu_to_cartFragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMenuBinding.inflate(inflater, container, false)
val drawerLayout = binding.drawerLayout
val mainView: View = binding.root
mainView.setOnClickListener {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
}
}
binding.hamburgerButton.setOnClickListener {
onHamburgerButtonClick()
}
binding.imageView6.setOnClickListener{
findNavController().navigate(actionMenuToAlerts)
}
binding.imageView2.setOnClickListener{
findNavController().navigate(actionMenuToProfile)
}
binding.imageView7.setOnClickListener{
findNavController().navigate(actionMenuToOrders)
}
binding.imageView.setOnClickListener {
findNavController().navigate(actionMenuToCart)
}
binding.navigationView.setNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.about_us -> {
findNavController().navigate(actionMenuToAboutUs)
}
R.id.reservation -> {
findNavController().navigate(actionMenuToReservation)
}
R.id.rules -> {
findNavController().navigate(actionMenuToRules)
}
R.id.privacy_policy -> {
findNavController().navigate(actionMenuToPrivacyPolicy)
}
R.id.logout -> {
sessionManager.logout()
findNavController().navigate(actionLogout)
}
}
binding.drawerLayout.closeDrawer(GravityCompat.START)
true
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dishAdapter = DishAdapter(cartViewModel)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = dishAdapter
menuViewModel.dishes.observe(viewLifecycleOwner) { dishes ->
dishAdapter.setDishes(dishes)
}
menuViewModel.loadDishes(categoryId = null, name = null)
binding.textInputLayout3.editText?.addTextChangedListener { text ->
val searchQuery = text.toString().trim()
menuViewModel.loadDishes(categoryId = null, name = null, searchQuery = searchQuery)
}
}
private fun onHamburgerButtonClick() {
val drawerLayout = binding.drawerLayout
if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.openDrawer(GravityCompat.START)
} else {
drawerLayout.closeDrawer(GravityCompat.START)
}
}
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (sessionManager.isLoggedIn()) {
requireActivity().finish()
} else {
findNavController().popBackStack()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
package com.example.restauratio.menu
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.restauratio.request.AuthService
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MenuViewModel @Inject constructor(
private val authService: AuthService
) : ViewModel() {
private val _dishes = MutableLiveData<List<DishModel>>()
val dishes: LiveData<List<DishModel>> get() = _dishes
fun loadDishes(
categoryId: Int?,
name: String?,
searchQuery: String? = null
) {
viewModelScope.launch {
try {
val dishesResponse = authService.getDishes(DishRequest(categoryId, name))
if (dishesResponse.isSuccessful) {
val allDishes = dishesResponse.body()?.dishes ?: emptyList()
val filteredDishes = if (!searchQuery.isNullOrBlank()) {
allDishes.filter { dish -> dish.name.contains(searchQuery, ignoreCase = true) }
} else {
allDishes
}
_dishes.value = filteredDishes
} else {
Log.e("MenuViewModel", "Failed to fetch dishes. Error code: ${dishesResponse.code()}")
}
} catch (e: Exception) {
Log.e("MenuViewModel", "An error occurred while fetching dishes", e)
}
}
}
}
package com.example.restauratio.menu
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.example.restauratio.cart.CartViewModel
import com.example.restauratio.databinding.MenuItemBinding
import de.hdodenhof.circleimageview.CircleImageView
class DishAdapter(
private val cartViewModel: CartViewModel
) : RecyclerView.Adapter<DishAdapter.DishViewHolder>() {
private var dishes: List<DishModel> = emptyList()
inner class DishViewHolder(binding: MenuItemBinding) : RecyclerView.ViewHolder(binding.root) {
val dishImage: CircleImageView = binding.imageView2
val dishName: TextView = binding.textView2
val dishPrice: TextView = binding.textView
val addToCartButton: ImageView = binding.imageView8
init {
addToCartButton.setOnClickListener {
val clickedDish = dishes[adapterPosition]
cartViewModel.addToCart(clickedDish)
Toast.makeText(itemView.context, "Danie dodane do koszyka", Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DishViewHolder {
val inflater = LayoutInflater.from(parent.context)
val menuItemBinding = MenuItemBinding.inflate(inflater, parent, false)
return DishViewHolder(menuItemBinding)
}
override fun onBindViewHolder(holder: DishViewHolder, position: Int) {
val dish = dishes[position]
holder.dishName.text = dish.name
holder.dishPrice.text = String.format("%.2f zł", dish.price)
}
override fun getItemCount(): Int {
return dishes.size
}
fun setDishes(newDishes: List<DishModel>) {
dishes = newDishes
notifyDataSetChanged()
}
}
package com.example.restauratio.menu
data class DishModel(
val id: Int,
val name: String,
val description: String,
val price: Double,
val dishCategoryIds: List<Int>,
var quantity: Int = 0
)
data class DishRequest(
val categoryId: Int?,
val name: String?
)
data class DishResponse(
val dishes: List<DishModel>,
val total: Int
)
has no idea why this is happening
When you switch to CartFragment, a new instance of fragment is created which also creates a new instance of viewmodel. To solve this introduce the concept of sharedviewmodel by using activityViewModels
instead of viewModels
.
activityViewModels
is tied to the activity hosting the fragments, while viewModels
is tied to each fragment. Below is a sample of how your code should look:
CartFragment
@AndroidEntryPoint
class CartFragment : Fragment() {
private val cartViewModel: CartViewModel by activityViewModels()
....
}
MenuFragment
@AndroidEntryPoint
class MenuFragment : Fragment() {
...
private val cartViewModel: CartViewModel by activityViewModels()
...
To understand more, check on this codelab - Shared viewmodels across fragments
Another option incase you are saving the data to db or api, then add a new function to just get the data and update the observable then call the function on the CartFragment onviewCreated.