Free tutorials & notes in Hindi & English · Clean code examples 🧰 मुफ़्त PDF और फ़ोटो टूल्स →
📱 Android App (Kotlin + XML)  ·  Lesson 21

Cart Screen

Screen

9:41▮▮ 85%☰Cart⋮🚚 Add ₹150 more for FREE delivery━━━━━━━━━━━━━━━━━━━━━━━━──────📘Learn Kotlin in 30 Days₹349− 1 +♡ Save for later🗑 RemovePrice (1 item)₹499Delivery charges₹40Total amount₹389Checkout🏠Home🗂️Categories♡Wishlist🛒Cart👤Account9:41▮▮ 85%☰Cart⋮🎉 You get FREE delivery on thisorder⌚Smart Watch Pro₹1,899− 1 +📘Learn Kotlin in 30 Days₹349 × 2− 1 +Total amount₹2,597You save ₹3,400 on this order🏠Home🗂️Categories♡Wishlist🛒Cart👤AccountLearn Kotlin removed UNDO
₹499 से कम पर progress bar और जोड़ने को कहता है; swipe item को Undo के साथ हटाता है

Step 1: layouts

Cart list free-delivery card और price summary के साथ एक NestedScrollView में है, nestedScrollingEnabled="false" के साथ ताकि सब एक page की तरह scroll हो। Total और Checkout button नीचे fixed हैं।

XML – layout/fragment_cart.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <androidx.core.widget.NestedScrollView
        android:id="@+id/content"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottom_bar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingTop="8dp">

            <!-- Free delivery progress -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:layout_marginEnd="12dp"
                android:background="@drawable/bg_offer_card"
                android:orientation="vertical"
                android:padding="12dp">

                <TextView
                    android:id="@+id/free_delivery"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:drawablePadding="8dp"
                    android:gravity="center_vertical"
                    android:textColor="@color/text_primary"
                    android:textStyle="bold"
                    app:drawableStartCompat="@drawable/ic_truck"
                    app:drawableTint="@color/brand"
                    tools:text="Add ₹120 more for FREE delivery" />

                <com.google.android.material.progressindicator.LinearProgressIndicator
                    android:id="@+id/free_delivery_bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:max="100"
                    app:trackCornerRadius="4dp"
                    app:trackThickness="6dp" />
            </LinearLayout>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:nestedScrollingEnabled="false"
                tools:itemCount="2"
                tools:listitem="@layout/item_cart" />

            <include
                android:id="@+id/summary"
                layout="@layout/layout_price_summary" />
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>

    <com.google.android.material.progressindicator.LinearProgressIndicator
        android:id="@+id/updating"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:visibility="invisible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/bottom_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_bottom_bar"
        android:elevation="8dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="12dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/total"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/text_primary"
                android:textSize="20sp"
                android:textStyle="bold"
                tools:text="₹2,337" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/total_amount"
                android:textColor="@color/text_secondary"
                android:textSize="12sp" />
        </LinearLayout>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/checkout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:backgroundTint="@color/accent"
            android:paddingStart="32dp"
            android:paddingEnd="32dp"
            android:text="@string/checkout"
            android:textColor="@android:color/white" />
    </LinearLayout>

    <include
        android:id="@+id/state"
        layout="@layout/layout_state" />
</androidx.constraintlayout.widget.ConstraintLayout>
XML – layout/item_cart.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
    android:layout_marginStart="12dp"
    android:layout_marginTop="6dp"
    android:layout_marginEnd="12dp"
    android:layout_marginBottom="6dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="12dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <com.google.android.material.imageview.ShapeableImageView
                android:id="@+id/image"
                android:layout_width="88dp"
                android:layout_height="88dp"
                android:contentDescription="@string/product_image"
                android:scaleType="centerCrop"
                app:shapeAppearanceOverlay="@style/ShapeAppearance.KartEasy.Rounded" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:layout_weight="1"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ellipsize="end"
                    android:maxLines="2"
                    android:textColor="@color/text_primary"
                    tools:text="Smart Watch Pro" />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:baselineAligned="true"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/price"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textAppearance="@style/TextAppearance.KartEasy.Price"
                        tools:text="₹1,899" />

                    <TextView
                        android:id="@+id/mrp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="8dp"
                        android:textColor="@color/text_secondary"
                        android:textSize="12sp"
                        tools:text="₹4,999" />
                </LinearLayout>

                <TextView
                    android:id="@+id/warning"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="2dp"
                    android:textColor="@color/sale"
                    android:textSize="12sp"
                    android:textStyle="bold"
                    android:visibility="gone"
                    tools:text="Out of stock" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">

                    <com.google.android.material.button.MaterialButton
                        android:id="@+id/minus"
                        style="@style/Widget.KartEasy.QtyButton"
                        android:layout_width="36dp"
                        android:layout_height="36dp"
                        android:contentDescription="@string/decrease"
                        android:text="−" />

                    <TextView
                        android:id="@+id/qty"
                        android:layout_width="36dp"
                        android:layout_height="wrap_content"
                        android:gravity="center"
                        android:textColor="@color/text_primary"
                        android:textStyle="bold"
                        tools:text="2" />

                    <com.google.android.material.button.MaterialButton
                        android:id="@+id/plus"
                        style="@style/Widget.KartEasy.QtyButton"
                        android:layout_width="36dp"
                        android:layout_height="36dp"
                        android:contentDescription="@string/increase"
                        android:text="+" />

                    <TextView
                        android:id="@+id/line_total"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="end"
                        android:textColor="@color/text_secondary"
                        android:textSize="13sp"
                        tools:text="Total: ₹3,798" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="10dp"
            android:background="@color/divider" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <com.google.android.material.button.MaterialButton
                android:id="@+id/save"
                style="@style/Widget.Material3.Button.TextButton.Icon"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/save_for_later"
                app:icon="@drawable/ic_heart_outline" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/remove"
                style="@style/Widget.Material3.Button.TextButton.Icon"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/remove"
                android:textColor="@color/sale"
                app:icon="@drawable/ic_delete"
                app:iconTint="@color/sale" />
        </LinearLayout>
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>

Step 2: shared price summary

Cart और Checkout एक ही price box दिखाते हैं। यह एक layout है जो दोनों screens में include होता है, और एक extension function LayoutPriceSummaryBinding.bind(cart) उसे भरता है — "Price (3 items)" के लिए plurals और coupon row सिर्फ coupon लगने पर।

XML – layout/layout_price_summary.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Price details card. Filled by PriceSummary.kt: binding.summary.bind(cart) -->
<com.google.android.material.card.MaterialCardView 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"
    android:layout_margin="12dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            style="@style/KartEasy.CardTitle"
            android:text="@string/price_details" />

        <LinearLayout style="@style/KartEasy.SummaryRow">
            <TextView
                android:id="@+id/items_label"
                style="@style/KartEasy.SummaryLabel"
                tools:text="Price (3 items)" />
            <TextView
                android:id="@+id/mrp_value"
                style="@style/KartEasy.SummaryValue"
                tools:text="₹5,997" />
        </LinearLayout>

        <LinearLayout style="@style/KartEasy.SummaryRow">
            <TextView
                style="@style/KartEasy.SummaryLabel"
                android:text="@string/product_discount" />
            <TextView
                android:id="@+id/product_discount_value"
                style="@style/KartEasy.SummaryValue"
                android:textColor="@color/success"
                tools:text="− ₹3,400" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/coupon_row"
            style="@style/KartEasy.SummaryRow"
            android:visibility="gone">
            <TextView
                android:id="@+id/coupon_label"
                style="@style/KartEasy.SummaryLabel"
                tools:text="Coupon (SAVE10)" />
            <TextView
                android:id="@+id/coupon_value"
                style="@style/KartEasy.SummaryValue"
                android:textColor="@color/success"
                tools:text="− ₹259.70" />
        </LinearLayout>

        <LinearLayout style="@style/KartEasy.SummaryRow">
            <TextView
                style="@style/KartEasy.SummaryLabel"
                android:text="@string/delivery_charges" />
            <TextView
                android:id="@+id/delivery_value"
                style="@style/KartEasy.SummaryValue"
                tools:text="FREE" />
        </LinearLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:background="@color/divider" />

        <LinearLayout style="@style/KartEasy.SummaryRow">
            <TextView
                style="@style/KartEasy.SummaryLabel"
                android:text="@string/total_amount"
                android:textColor="@color/text_primary"
                android:textStyle="bold" />
            <TextView
                android:id="@+id/total_value"
                style="@style/KartEasy.SummaryValue"
                android:textStyle="bold"
                tools:text="₹2,337.30" />
        </LinearLayout>

        <TextView
            android:id="@+id/savings"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textColor="@color/success"
            android:textStyle="bold"
            tools:text="You save ₹3,659.70 on this order" />
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>
Kotlin – ui/cart/PriceSummary.kt
package com.codingeasily.karteasy.ui.cart

import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.model.Cart
import com.codingeasily.karteasy.databinding.LayoutPriceSummaryBinding
import com.codingeasily.karteasy.util.Format
import com.codingeasily.karteasy.util.visibleIf

/** Fills layout_price_summary.xml. Used by both Cart and Checkout. */
fun LayoutPriceSummaryBinding.bind(cart: Cart) {
    val ctx = root.context
    itemsLabel.text = ctx.resources.getQuantityString(R.plurals.price_items, cart.itemCount, cart.itemCount)
    mrpValue.text = Format.price(cart.mrpTotal)
    productDiscountValue.text = ctx.getString(R.string.minus_amount, Format.price(cart.mrpTotal - cart.subtotal))
    couponRow.visibleIf(cart.discount > 0)
    couponLabel.text = ctx.getString(R.string.coupon_label, cart.couponCode ?: "")
    couponValue.text = ctx.getString(R.string.minus_amount, Format.price(cart.discount))
    deliveryValue.text = if (cart.deliveryFee > 0) Format.price(cart.deliveryFee) else ctx.getString(R.string.free)
    totalValue.text = Format.price(cart.total)
    savings.visibleIf(cart.youSave > 0)
    savings.text = ctx.getString(R.string.you_save, Format.price(cart.youSave))
}

Step 3: CartAdapter

Minus button 1 पर disabled (वहाँ Remove use करें), plus button stock या 10 पर। Unavailable items 60% fade होते हैं और red warning दिखाते हैं।

Kotlin – ui/cart/CartAdapter.kt
package com.codingeasily.karteasy.ui.cart

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.model.CartItem
import com.codingeasily.karteasy.databinding.ItemCartBinding
import com.codingeasily.karteasy.util.Format
import com.codingeasily.karteasy.util.load
import com.codingeasily.karteasy.util.strike
import com.codingeasily.karteasy.util.visibleIf

class CartAdapter(
    private val onQty: (CartItem, Int) -> Unit,
    private val onRemove: (CartItem) -> Unit,
    private val onSave: (CartItem) -> Unit,
    private val onOpen: (CartItem) -> Unit
) : ListAdapter<CartItem, CartAdapter.Holder>(DIFF) {

    inner class Holder(val b: ItemCartBinding) : RecyclerView.ViewHolder(b.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        Holder(ItemCartBinding.inflate(LayoutInflater.from(parent.context), parent, false))

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val item = getItem(position)
        val b = holder.b
        val ctx = b.root.context
        b.image.load(item.image)
        b.name.text = item.name
        b.price.text = Format.price(item.price)
        b.mrp.strike(Format.price(item.mrp))
        b.mrp.visibleIf(item.mrp > item.price)
        b.qty.text = item.qty.toString()
        b.lineTotal.text = ctx.getString(R.string.line_total, Format.price(item.lineTotal))
        b.minus.isEnabled = item.qty > 1
        b.plus.isEnabled = item.qty < minOf(10, item.stock)
        b.warning.visibleIf(!item.available)
        b.warning.text = if (item.stock <= 0) ctx.getString(R.string.out_of_stock)
        else ctx.getString(R.string.only_left, item.stock)
        b.root.alpha = if (item.available) 1f else 0.6f

        b.minus.setOnClickListener { onQty(item, -1) }
        b.plus.setOnClickListener { onQty(item, +1) }
        b.remove.setOnClickListener { onRemove(item) }
        b.save.setOnClickListener { onSave(item) }
        b.image.setOnClickListener { onOpen(item) }
        b.name.setOnClickListener { onOpen(item) }
    }

    companion object {
        val DIFF = object : DiffUtil.ItemCallback<CartItem>() {
            override fun areItemsTheSame(a: CartItem, b: CartItem) = a.productId == b.productId
            override fun areContentsTheSame(a: CartItem, b: CartItem) = a == b
        }
    }
}

Step 4: CartViewModel

हर action (− / +, remove, undo, wishlist में move) update() से जाता है: request चलते समय taps ignore करता है (busy) और पूरा cart server के जवाब से बदल देता है। Screen कभी ऐसे totals नहीं दिखा सकती जो server ने calculate न किए हों।

Kotlin – ui/cart/CartViewModel.kt
package com.codingeasily.karteasy.ui.cart

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.codingeasily.karteasy.data.api.Result
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.Cart
import com.codingeasily.karteasy.data.model.CartItem
import com.codingeasily.karteasy.util.kartApp
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class CartViewModel(app: Application) : AndroidViewModel(app) {

    private val repo = app.kartApp.repository
    private val _state = MutableStateFlow<UiState<Cart>>(UiState.Loading)
    val state: StateFlow<UiState<Cart>> = _state

    private val _busy = MutableStateFlow(false)
    val busy: StateFlow<Boolean> = _busy

    private val _messages = MutableSharedFlow<String>()
    val messages: SharedFlow<String> = _messages

    fun load() {
        viewModelScope.launch {
            if (_state.value !is UiState.Success) _state.value = UiState.Loading
            _state.value = when (val r = repo.cart()) {
                is Result.Success -> UiState.Success(r.data)
                is Result.Error -> UiState.Error(r.message)
            }
        }
    }

    fun changeQty(item: CartItem, delta: Int) = update { repo.updateCart(item.productId, item.qty + delta) }

    fun remove(item: CartItem) = update { repo.removeFromCart(item.productId) }

    /** Undo for the "Item removed" snackbar. */
    fun restore(item: CartItem) = update { repo.addToCart(item.productId, item.qty) }

    fun moveToWishlist(item: CartItem) {
        viewModelScope.launch {
            val r = repo.toggleWishlist(item.productId)
            if (r is Result.Success && !r.data.inWishlist) repo.toggleWishlist(item.productId) // was already there
            update { repo.removeFromCart(item.productId) }
            _messages.emit("Moved to wishlist.")
        }
    }

    /** Every cart call returns the full new cart, so the screen simply shows that. */
    private fun update(call: suspend () -> Result<Cart>) {
        if (_busy.value) return
        _busy.value = true
        viewModelScope.launch {
            when (val r = call()) {
                is Result.Success -> _state.value = UiState.Success(r.data)
                is Result.Error -> _messages.emit(r.message)
            }
            _busy.value = false
        }
    }
}
+ tapbusy = truePOST cart/update.phpqty + 1server cartनए items + totalsstate = Successlist, summary, total फिर से
हर cart change का एक loop

Step 5: CartFragment

Items न हों तो fragment "Start shopping" वाला empty state दिखाता है, वरना list, summary, नीचे total और free-delivery card।

Kotlin – ui/cart/CartFragment.kt
package com.codingeasily.karteasy.ui.cart

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.CartItem
import com.codingeasily.karteasy.databinding.FragmentCartBinding
import com.codingeasily.karteasy.ui.product.ProductDetailFragment
import com.codingeasily.karteasy.util.Format
import com.codingeasily.karteasy.util.collectWhenStarted
import com.codingeasily.karteasy.util.done
import com.codingeasily.karteasy.util.empty
import com.codingeasily.karteasy.util.error
import com.codingeasily.karteasy.util.hide
import com.codingeasily.karteasy.util.loading
import com.codingeasily.karteasy.util.show
import com.codingeasily.karteasy.util.snack
import com.google.android.material.snackbar.Snackbar

class CartFragment : Fragment() {

    companion object {
        /** Must match FREE_DELIVERY_ABOVE in the PHP config. */
        const val FREE_DELIVERY_ABOVE = 499.0
    }

    private var _binding: FragmentCartBinding? = null
    private val binding get() = _binding!!
    private val vm: CartViewModel by viewModels()

    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?) {
        val adapter = CartAdapter(
            onQty = { item, d -> vm.changeQty(item, d) },
            onRemove = ::removeWithUndo,
            onSave = { vm.moveToWishlist(it) },
            onOpen = { findNavController().navigate(R.id.productDetailFragment, bundleOf(ProductDetailFragment.ARG_PRODUCT_ID to it.productId)) }
        )
        binding.list.layoutManager = LinearLayoutManager(requireContext())
        binding.list.adapter = adapter

        // Swipe left or right to remove an item.
        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
            override fun onMove(rv: RecyclerView, vh: RecyclerView.ViewHolder, t: RecyclerView.ViewHolder) = false
            override fun onSwiped(vh: RecyclerView.ViewHolder, direction: Int) {
                val pos = vh.bindingAdapterPosition
                adapter.notifyItemChanged(pos)          // put the row back; the new cart list removes it
                removeWithUndo(adapter.currentList[pos])
            }
        }).attachToRecyclerView(binding.list)

        binding.checkout.setOnClickListener { findNavController().navigate(R.id.action_cart_to_checkout) }

        collectWhenStarted(vm.state) { state ->
            when (state) {
                is UiState.Loading -> { binding.content.hide(); binding.bottomBar.hide(); binding.state.loading() }
                is UiState.Error -> { binding.content.hide(); binding.bottomBar.hide(); binding.state.error(state.message) { vm.load() } }
                is UiState.Success -> {
                    val cart = state.data
                    adapter.submitList(cart.items)
                    if (cart.items.isEmpty()) {
                        binding.content.hide(); binding.bottomBar.hide()
                        binding.state.empty(R.drawable.ic_cart_empty, R.string.cart_empty, R.string.start_shopping) {
                            findNavController().navigate(R.id.homeFragment)
                        }
                        return@collectWhenStarted
                    }
                    binding.state.done(); binding.content.show(); binding.bottomBar.show()
                    binding.summary.bind(cart)
                    binding.total.text = Format.price(cart.total)
                    binding.checkout.isEnabled = cart.itemCount > 0

                    // "Add ₹120 more for FREE delivery" + progress bar
                    val left = FREE_DELIVERY_ABOVE - cart.subtotal
                    if (left > 0) {
                        binding.freeDelivery.text = getString(R.string.free_delivery_left, Format.price(left))
                        binding.freeDeliveryBar.progress = ((cart.subtotal / FREE_DELIVERY_ABOVE) * 100).toInt()
                        binding.freeDeliveryBar.show()
                    } else {
                        binding.freeDelivery.setText(R.string.free_delivery_unlocked)
                        binding.freeDeliveryBar.hide()
                    }
                }
            }
        }
        collectWhenStarted(vm.busy) { binding.updating.visibility = if (it) View.VISIBLE else View.INVISIBLE }
        collectWhenStarted(vm.messages) { snack(it) }
    }

    override fun onResume() {
        super.onResume()
        vm.load()   // the cart may have changed on another screen
    }

    private fun removeWithUndo(item: CartItem) {
        vm.remove(item)
        Snackbar.make(binding.root, getString(R.string.removed_item, item.name), Snackbar.LENGTH_LONG)
            .setAction(R.string.undo) { vm.restore(item) }
            .setAnchorView(binding.bottomBar)
            .show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Swipe से remove + Undo

ItemTouchHelper left या right swipe पहचानता है। हम तुरंत API से item हटवाते हैं और Undo वाला Snackbar दिखाते हैं; Undo वही product और quantity वापस जोड़ देता है। Snackbar bottom bar के ऊपर anchored है ताकि Checkout button न ढके।

UserCartFragmentAPIrow swipe ←remove.php {product_id}Snackbar "… removed UNDO"UNDO tapadd.php {product_id, qty}
Undo = वापस जोड़ना

Cart badge

Cart tab पर number एक Material BadgeDrawable है। यह repository.cartCount follow करता है, जिसे हर cart call update करती है — इसलिए Home, Detail या Wishlist से add करने पर badge तुरंत बदलता है।

Kotlin – MainActivity.kt (badge वाला हिस्सा)
// MainActivity.kt – the repository's cartCount drives the badge on the Cart tab
launch {
    app.repository.cartCount.collect { count ->
        val badge = binding.bottomNav.getOrCreateBadge(R.id.cartFragment)
        badge.isVisible = count > 0
        badge.number = count
        badge.backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.sale)
    }
}

अक्सर पूछे जाने वाले प्रश्न (FAQ)

onResume() में cart reload क्यों?
Cart दूसरी screens पर बदल सकता है (product detail, wishlist "move to cart")। Tab दिखते ही reload करने से वह सही रहता है; नई list load होते समय पुरानी screen पर रहती है, इसलिए flicker नहीं होता।
FREE_DELIVERY_ABOVE app में भी क्यों है?
सिर्फ progress bar text ("Add ₹120 more…") के लिए। असली delivery fee हमेशा API से आती है। config.php में rule बदलें तो यह constant भी बदलें।
← E-commerce App Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

अगला topicWishlist Screen आपकी प्रगति: 21 / 28 (75%) · Lesson 22 / 28 आगे बढ़ें →

यह lesson कैसा लगा?

आपकी राय से हम content और बेहतर बनाते हैं।

अपना review और सुझाव लिखें (वैकल्पिक)

Review approve होने के बाद आपका नाम और शहर इसके साथ public दिख सकते हैं। Email या phone न लिखें।

▶ CodingEasily on YouTube

Video से सीखें – आसान Hindi में

चुनी हुई video: RDBMS भाग 3 – Class XI Commerce IT

▶ Channel subscribe करें