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

Product Detail Screen

Screen

9:41▮▮ 85%←Electronics⋮🖼️swipe → 2 photos ━ •Smart Watch ProAMOLED, SpO2, 7-day battery4.5 ★ 128 ratings62% off ₹4,999 ₹1,899In stock · 🚚 ₹499 से ऊपर freedelivery🛒 Add to cartBuy now9:41▮▮ 85%←Electronics⋮Quantity− 1 +Product details1.43 inch AMOLED screen, heart-rateand SpO2 tracking, 100+ sportsmodes…Ratings & reviews (128)★★★★★ Riya SharmaBattery easily lasts a …Similar products🔊Speaker Boom₹2,499🔋Power Bank₹1,499
Detail screen का ऊपरी हिस्सा, और नीचे (buttons नीचे fixed रहते हैं)

Step 1: fragment_product_detail.xml

तीन हिस्सों वाला ConstraintLayout: NestedScrollView (gallery, info card, description, reviews, similar products), fixed bottom bar, और shared state view। Grey screen पर हर block का white background है, इसलिए page अलग-अलग cards जैसा पढ़ा जाता है।

XML – layout/fragment_product_detail.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">

            <!-- Image gallery (swipe) + dots -->
            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/gallery"
                android:layout_width="match_parent"
                android:layout_height="320dp"
                android:background="@color/surface" />

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/gallery_dots"
                android:layout_width="match_parent"
                android:layout_height="20dp"
                android:background="@color/surface"
                app:tabBackground="@drawable/tab_dot"
                app:tabGravity="center"
                app:tabIndicatorHeight="0dp"
                app:tabMaxWidth="18dp"
                app:tabMinWidth="18dp"
                app:tabPaddingEnd="0dp"
                app:tabPaddingStart="0dp" />

            <!-- Name, rating, price -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/surface"
                android:orientation="vertical"
                android:padding="16dp">

                <TextView
                    android:id="@+id/name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textAppearance="?attr/textAppearanceTitleLarge"
                    android:textColor="@color/text_primary"
                    tools:text="Smart Watch Pro" />

                <TextView
                    android:id="@+id/short_desc"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="2dp"
                    android:textColor="@color/text_secondary"
                    tools:text="AMOLED, SpO2, 7-day battery" />

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

                    <TextView
                        android:id="@+id/rating_chip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@drawable/bg_rating"
                        android:paddingStart="8dp"
                        android:paddingTop="2dp"
                        android:paddingEnd="8dp"
                        android:paddingBottom="2dp"
                        android:textColor="@android:color/white"
                        android:textStyle="bold"
                        tools:text="4.5 ★" />

                    <TextView
                        android:id="@+id/rating_count"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="8dp"
                        android:textColor="@color/text_secondary"
                        tools:text="128 ratings" />
                </LinearLayout>

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

                    <TextView
                        android:id="@+id/discount"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginEnd="8dp"
                        android:textColor="@color/success"
                        android:textSize="20sp"
                        android:textStyle="bold"
                        tools:text="62% off" />

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

                    <TextView
                        android:id="@+id/price"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textColor="@color/text_primary"
                        android:textSize="24sp"
                        android:textStyle="bold"
                        tools:text="₹1,899" />
                </LinearLayout>

                <TextView
                    android:id="@+id/stock"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:textStyle="bold"
                    tools:text="In stock" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:drawablePadding="8dp"
                    android:gravity="center_vertical"
                    android:text="@string/free_delivery_note"
                    android:textColor="@color/text_secondary"
                    android:textSize="13sp"
                    app:drawableStartCompat="@drawable/ic_truck"
                    app:drawableTint="@color/brand" />

                <!-- Quantity stepper -->
                <LinearLayout
                    android:id="@+id/qty_row"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="16dp"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">

                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/quantity"
                        android:textColor="@color/text_primary"
                        android:textStyle="bold" />

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

                    <TextView
                        android:id="@+id/qty"
                        android:layout_width="44dp"
                        android:layout_height="wrap_content"
                        android:gravity="center"
                        android:textAppearance="?attr/textAppearanceTitleMedium"
                        android:textColor="@color/text_primary"
                        tools:text="1" />

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

                <TextView
                    android:id="@+id/in_cart"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:textColor="@color/brand"
                    android:visibility="gone"
                    tools:text="1 already in your cart" />
            </LinearLayout>

            <!-- Description -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:background="@color/surface"
                android:orientation="vertical"
                android:padding="16dp">

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

                <TextView
                    android:id="@+id/description"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ellipsize="end"
                    android:lineSpacingExtra="4dp"
                    android:maxLines="4"
                    android:textColor="@color/text_primary" />

                <TextView
                    android:id="@+id/description_toggle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:background="?attr/selectableItemBackground"
                    android:paddingTop="4dp"
                    android:paddingBottom="4dp"
                    android:text="@string/read_more"
                    android:textColor="@color/brand"
                    android:textStyle="bold" />
            </LinearLayout>

            <!-- Reviews -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:background="@color/surface"
                android:orientation="vertical"
                android:padding="16dp">

                <TextView
                    android:id="@+id/reviews_title"
                    style="@style/KartEasy.CardTitle"
                    tools:text="Ratings &amp; reviews (128)" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/review_list"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:nestedScrollingEnabled="false"
                    tools:listitem="@layout/item_review" />

                <TextView
                    android:id="@+id/no_reviews"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/no_reviews"
                    android:textColor="@color/text_secondary"
                    android:visibility="gone" />
            </LinearLayout>

            <!-- Similar products -->
            <TextView
                android:id="@+id/similar_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:paddingStart="16dp"
                android:paddingTop="12dp"
                android:paddingEnd="16dp"
                android:text="@string/similar_products"
                android:textAppearance="@style/TextAppearance.KartEasy.Section" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/similar_list"
                style="@style/KartEasy.HorizontalRow"
                android:paddingBottom="16dp"
                tools:listitem="@layout/item_product" />
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>

    <!-- Fixed bottom bar: Add to cart / Buy now -->
    <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:orientation="horizontal"
        android:padding="12dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <com.google.android.material.button.MaterialButton
            android:id="@+id/add_to_cart"
            style="@style/Widget.KartEasy.Button.Outlined"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="6dp"
            android:layout_weight="1"
            android:text="@string/add_to_cart"
            app:icon="@drawable/ic_cart" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/buy_now"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="6dp"
            android:layout_weight="1"
            android:backgroundTint="@color/accent"
            android:text="@string/buy_now"
            android:textColor="@android:color/white" />
    </LinearLayout>

    <include
        android:id="@+id/state"
        layout="@layout/layout_state" />
</androidx.constraintlayout.widget.ConstraintLayout>

Step 2: gallery और review items

Gallery एक और ViewPager2 है; images fitCenter use करती हैं ताकि पूरा product दिखे। Reviews छोटा indicator RatingBar दिखाते हैं।

XML – layout/item_gallery_image.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/surface"
    android:contentDescription="@string/product_image"
    android:scaleType="fitCenter" />
Kotlin – ui/product/GalleryAdapter.kt
package com.codingeasily.karteasy.ui.product

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.databinding.ItemGalleryImageBinding
import com.codingeasily.karteasy.util.load

class GalleryAdapter(private val images: List<String>) : RecyclerView.Adapter<GalleryAdapter.Holder>() {
    inner class Holder(val b: ItemGalleryImageBinding) : RecyclerView.ViewHolder(b.root)

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

    override fun getItemCount() = images.size

    override fun onBindViewHolder(holder: Holder, position: Int) = holder.b.image.load(images[position])
}
XML – layout/item_review.xml
<?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"
    android:orientation="vertical"
    android:paddingTop="10dp"
    android:paddingBottom="10dp">

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

        <RatingBar
            android:id="@+id/rating_bar"
            style="?android:attr/ratingBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:isIndicator="true"
            android:numStars="5"
            android:progressTint="@color/rating" />

        <TextView
            android:id="@+id/name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_weight="1"
            android:textColor="@color/text_primary"
            android:textStyle="bold"
            tools:text="Riya Sharma" />

        <TextView
            android:id="@+id/date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/text_secondary"
            android:textSize="12sp"
            tools:text="2026-10-19" />
    </LinearLayout>

    <TextView
        android:id="@+id/comment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textColor="@color/text_primary"
        tools:text="Battery easily lasts a week." />
</LinearLayout>
Kotlin – ui/product/ReviewAdapter.kt
package com.codingeasily.karteasy.ui.product

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.data.model.Review
import com.codingeasily.karteasy.databinding.ItemReviewBinding
import com.codingeasily.karteasy.util.visibleIf

class ReviewAdapter(private val reviews: List<Review>) : RecyclerView.Adapter<ReviewAdapter.Holder>() {
    inner class Holder(val b: ItemReviewBinding) : RecyclerView.ViewHolder(b.root)

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

    override fun getItemCount() = reviews.size

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val r = reviews[position]
        holder.b.name.text = r.name
        holder.b.ratingBar.rating = r.rating.toFloat()
        holder.b.date.text = r.date
        holder.b.comment.text = r.comment
        holder.b.comment.visibleIf(!r.comment.isNullOrBlank())
    }
}

Step 3: ProductDetailViewModel

Product id SavedStateHandle से आता है। Product state के अलावा ViewModel चुनी हुई quantity (stock और 10 तक सीमित), add करते समय दोनों buttons बंद करने वाला busy flag, और one-time events रखता है: एक message, या Buy now के बाद "cart पर जाओ"।

Kotlin – ui/product/ProductDetailViewModel.kt
package com.codingeasily.karteasy.ui.product

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.codingeasily.karteasy.data.api.Result
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.ProductDetail
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 ProductDetailViewModel(app: Application, args: SavedStateHandle) : AndroidViewModel(app) {

    sealed class Event {
        data class Message(val text: String) : Event()
        object GoToCart : Event()
    }

    private val repo = app.kartApp.repository
    val productId: Int = args.get<Int>(ProductDetailFragment.ARG_PRODUCT_ID) ?: 0

    private val _state = MutableStateFlow<UiState<ProductDetail>>(UiState.Loading)
    val state: StateFlow<UiState<ProductDetail>> = _state

    /** Quantity chosen with the − / + buttons. */
    private val _qty = MutableStateFlow(1)
    val qty: StateFlow<Int> = _qty

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

    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events

    init { load() }

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

    fun changeQty(delta: Int) {
        val p = (_state.value as? UiState.Success)?.data ?: return
        _qty.value = (_qty.value + delta).coerceIn(1, minOf(10, maxOf(1, p.stock)))
    }

    /** buyNow = true opens the cart right after adding. */
    fun addToCart(buyNow: Boolean) {
        if (_busy.value) return
        _busy.value = true
        viewModelScope.launch {
            when (val r = repo.addToCart(productId, _qty.value)) {
                is Result.Success -> {
                    _events.emit(Event.Message(r.message))
                    if (buyNow) _events.emit(Event.GoToCart)
                    _qty.value = 1
                    load()   // refreshes "already in cart" count and stock
                }
                is Result.Error -> _events.emit(Event.Message(r.message))
            }
            _busy.value = false
        }
    }

    fun toggleWishlist() {
        viewModelScope.launch {
            when (val r = repo.toggleWishlist(productId)) {
                is Result.Success -> {
                    val cur = (_state.value as? UiState.Success)?.data
                    if (cur != null) _state.value = UiState.Success(cur.copy(inWishlist = r.data.inWishlist))
                    _events.emit(Event.Message(r.message))
                }
                is Result.Error -> _events.emit(Event.Message(r.message))
            }
        }
    }
}

Step 4: ProductDetailFragment

bind() हर view भरता है। Gallery adapter सिर्फ पहली बार बनता है (वरना "Add to cart" के बाद refresh photo 1 पर वापस कूद जाता)। 5 या कम बचे हों तो stock text red हो जाता है।

Kotlin – ui/product/ProductDetailFragment.kt
package com.codingeasily.karteasy.ui.product

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.ProductDetail
import com.codingeasily.karteasy.databinding.FragmentProductDetailBinding
import com.codingeasily.karteasy.util.Format
import com.codingeasily.karteasy.util.collectWhenStarted
import com.codingeasily.karteasy.util.done
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.codingeasily.karteasy.util.strike
import com.codingeasily.karteasy.util.visibleIf
import com.google.android.material.tabs.TabLayoutMediator

class ProductDetailFragment : Fragment() {

    companion object {
        const val ARG_PRODUCT_ID = "productId"
    }

    private var _binding: FragmentProductDetailBinding? = null
    private val binding get() = _binding!!
    private val vm: ProductDetailViewModel by viewModels()
    private var product: ProductDetail? = null
    private var menu: Menu? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentProductDetailBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Toolbar menu: wishlist heart + share (MenuProvider replaces setHasOptionsMenu)
        requireActivity().addMenuProvider(object : MenuProvider {
            override fun onCreateMenu(m: Menu, inflater: MenuInflater) {
                inflater.inflate(R.menu.menu_product_detail, m)
                menu = m
                updateHeart()
            }

            override fun onMenuItemSelected(item: MenuItem): Boolean = when (item.itemId) {
                R.id.action_wishlist -> { vm.toggleWishlist(); true }
                R.id.action_share -> { share(); true }
                else -> false
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED)

        val similar = ProductAdapter(
            fixedWidthDp = 150,
            onClick = { findNavController().navigate(R.id.productDetailFragment, bundleOf(ARG_PRODUCT_ID to it.id)) },
            onWishlist = { }
        )
        binding.similarList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
        binding.similarList.adapter = similar

        binding.minus.setOnClickListener { vm.changeQty(-1) }
        binding.plus.setOnClickListener { vm.changeQty(+1) }
        binding.addToCart.setOnClickListener { vm.addToCart(buyNow = false) }
        binding.buyNow.setOnClickListener { vm.addToCart(buyNow = true) }
        binding.descriptionToggle.setOnClickListener {
            val expanded = binding.description.maxLines == Int.MAX_VALUE
            binding.description.maxLines = if (expanded) 4 else Int.MAX_VALUE
            binding.descriptionToggle.setText(if (expanded) R.string.read_more else R.string.read_less)
        }

        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 -> {
                    binding.state.done(); binding.content.show(); binding.bottomBar.show()
                    bind(state.data)
                    similar.submitList(state.data.similar)
                }
            }
        }
        collectWhenStarted(vm.qty) { binding.qty.text = it.toString() }
        collectWhenStarted(vm.busy) { busy ->
            binding.addToCart.isEnabled = !busy
            binding.buyNow.isEnabled = !busy
        }
        collectWhenStarted(vm.events) { e ->
            when (e) {
                is ProductDetailViewModel.Event.Message -> snack(e.text)
                ProductDetailViewModel.Event.GoToCart -> findNavController().navigate(R.id.cartFragment)
            }
        }
    }

    private fun bind(p: ProductDetail) {
        val firstTime = product == null
        product = p
        (requireActivity() as AppCompatActivity).supportActionBar?.title = p.categoryName

        if (firstTime) {
            binding.gallery.adapter = GalleryAdapter(p.images)
            TabLayoutMediator(binding.galleryDots, binding.gallery) { _, _ -> }.attach()
            binding.galleryDots.visibleIf(p.images.size > 1)
        }
        binding.name.text = p.name
        binding.shortDesc.text = p.shortDesc
        binding.ratingChip.text = getString(R.string.rating_value, p.rating)
        binding.ratingCount.text = getString(R.string.ratings_reviews, p.ratingCount)
        binding.price.text = Format.price(p.price)
        binding.mrp.strike(Format.price(p.mrp))
        binding.mrp.visibleIf(p.mrp > p.price)
        binding.discount.text = getString(R.string.percent_off, p.discountPct)
        binding.discount.visibleIf(p.discountPct > 0)

        val inStock = p.stock > 0
        binding.stock.text = when {
            !inStock -> getString(R.string.out_of_stock)
            p.stock <= 5 -> getString(R.string.only_left, p.stock)
            else -> getString(R.string.in_stock)
        }
        binding.stock.setTextColor(ContextCompat.getColor(requireContext(), if (p.stock in 1..5 || !inStock) R.color.sale else R.color.success))
        binding.qtyRow.visibleIf(inStock)
        binding.addToCart.isEnabled = inStock
        binding.buyNow.isEnabled = inStock
        binding.inCart.visibleIf(p.cartQty > 0)
        binding.inCart.text = getString(R.string.already_in_cart, p.cartQty)

        binding.description.text = p.description
        binding.descriptionToggle.visibleIf((p.description?.length ?: 0) > 180)

        binding.reviewsTitle.text = getString(R.string.reviews_title, p.ratingCount)
        binding.reviewList.layoutManager = LinearLayoutManager(requireContext())
        binding.reviewList.adapter = ReviewAdapter(p.reviews)
        binding.noReviews.visibleIf(p.reviews.isEmpty())
        binding.similarTitle.visibleIf(p.similar.isNotEmpty())
        updateHeart()
    }

    private fun updateHeart() {
        val item = menu?.findItem(R.id.action_wishlist) ?: return
        val inWishlist = product?.inWishlist == true
        item.setIcon(if (inWishlist) R.drawable.ic_heart else R.drawable.ic_heart_outline)
        item.setTitle(if (inWishlist) R.string.remove_from_wishlist else R.string.add_to_wishlist)
    }

    private fun share() {
        val p = product ?: return
        val text = getString(R.string.share_product, p.name, Format.price(p.price))
        startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, text), null))
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        menu = null
        product = null
    }
}
stockTextColourButtons
0Out of stockreddisabled, quantity छुपी
1 – 5Hurry, only 3 left!redenabled
6+In stockgreenenabled

Heart और share icons toolbar में हैं। addMenuProvider(…, viewLifecycleOwner, RESUMED) setHasOptionsMenu का modern replacement है: screen दिखने पर menu जुड़ता है और user के जाते ही अपने आप हट जाता है।

XML – menu/menu_product_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_wishlist"
        android:icon="@drawable/ic_heart_outline"
        android:title="@string/add_to_wishlist"
        app:iconTint="@color/on_brand"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_share"
        android:icon="@drawable/ic_share"
        android:title="@string/share"
        app:iconTint="@color/on_brand"
        app:showAsAction="ifRoom" />
</menu>

Add to cart vs Buy now

Add to cart

  • POST cart/add.php {product_id, qty}
  • Snackbar "Added to cart."
  • product पर ही रहें
  • Cart tab का badge update

Buy now

  • वही API call
  • फिर Event.GoToCart
  • Cart screen पर navigate
  • user सीधे checkout पर
एक function, addToCart(buyNow), दो behaviours

छोटी बातें जो मायने रखती हैं

  • "Read more" सिर्फ 180 characters से लंबे descriptions पर दिखता है।
  • "1 already in your cart" बताता है कि user पहले जोड़ चुका है।
  • Share छोटा text बनाता है: "Check out Smart Watch Pro for just ₹1,899 on KartEasy!"
  • Similar product tap करने पर ऊपर दूसरी detail screen खुलती है — Back पिछली पर लौटाता है।
  • Toolbar category name दिखाता है, product name content में बड़ा title है।

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

Bottom bar scroll view के बाहर क्यों है?
ताकि "Add to cart" और "Buy now" हमेशा दिखें, user कहीं भी scroll करे। ConstraintLayout scroll view को top और bar के बीच रखता है।
Colours या sizes (variants) कैसे जोड़ें?
product_variants table (product_id, name, price, stock) जोड़ें, product.php में list लौटाएँ, ChipGroup में दिखाएँ, और cart/add.php के साथ variant_id भेजें। तब stock और price variant row से आएँगे।
← E-commerce App Tutorial
🔗

Share this topic with a friend

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

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

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

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

यह lesson कैसा लगा?

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

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

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

▶ CodingEasily on YouTube

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

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

▶ Channel subscribe करें