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

Profile, Drawer और Bottom Nav

App का नक्शा

MainActivity (toolbar + drawer + bottom nav + NavHost)
  Bottom tabs (top level, hamburger icon)
    homeFragment ─ categoriesFragment ─ wishlistFragment ─ cartFragment ─ accountFragment
  गहरी screens (back arrow, bottom bar छुपा)
    productListFragment → productDetailFragment
    checkoutFragment → addressListFragment → addressEditFragment
    orderSuccessFragment → orderDetailFragment
    ordersFragment, editProfileFragment
AuthActivity: loginFragment ⇄ registerFragment
OnboardingActivity
3 activities में 17 screens
9:41▮▮ 85%☰KartEasy⋮Festive SaleShop now →👤Riya Sharma🏠Home📦My orders♡Wishlist⚙️Account❓Help & support🚪Logout9:41▮▮ 85%☰Account⋮R Riya Sharmatest@karteasy.in · 9876…4 Orders 2 Wishlist 3 CartMMy ordersSSaved addressesWWishlistEEdit profileSystemLightDarkLogout🏠Home🗂️Categories♡Wishlist🛒Cart👤Account
Navigation drawer और Account tab

Step 1: nav_main.xml

सारी shop screens और उनके arguments एक graph में। Optional arguments के defaults हैं, इसलिए navigate(R.id.productListFragment) बिना bundle के भी चलता है। Product detail एक deep link भी declare करता है।

XML – navigation/nav_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Every screen of the shop. Arguments are passed with bundleOf(...) using the ARG_ constants. -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_main"
    app:startDestination="@id/homeFragment">

    <!-- ===== Bottom navigation tabs ===== -->
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.codingeasily.karteasy.ui.home.HomeFragment"
        android:label="@string/app_name" />

    <fragment
        android:id="@+id/categoriesFragment"
        android:name="com.codingeasily.karteasy.ui.category.CategoriesFragment"
        android:label="@string/nav_categories" />

    <fragment
        android:id="@+id/wishlistFragment"
        android:name="com.codingeasily.karteasy.ui.wishlist.WishlistFragment"
        android:label="@string/nav_wishlist" />

    <fragment
        android:id="@+id/cartFragment"
        android:name="com.codingeasily.karteasy.ui.cart.CartFragment"
        android:label="@string/nav_cart">
        <action
            android:id="@+id/action_cart_to_checkout"
            app:destination="@id/checkoutFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
    </fragment>

    <fragment
        android:id="@+id/accountFragment"
        android:name="com.codingeasily.karteasy.ui.account.AccountFragment"
        android:label="@string/nav_account" />

    <!-- ===== Catalog ===== -->
    <fragment
        android:id="@+id/productListFragment"
        android:name="com.codingeasily.karteasy.ui.product.ProductListFragment"
        android:label="@string/title_products">
        <argument
            android:name="categoryId"
            android:defaultValue="0"
            app:argType="integer" />
        <argument
            android:name="title"
            android:defaultValue="@null"
            app:argType="string"
            app:nullable="true" />
        <argument
            android:name="sort"
            android:defaultValue="newest"
            app:argType="string" />
        <argument
            android:name="focusSearch"
            android:defaultValue="false"
            app:argType="boolean" />
    </fragment>

    <fragment
        android:id="@+id/productDetailFragment"
        android:name="com.codingeasily.karteasy.ui.product.ProductDetailFragment"
        android:label="@string/title_product">
        <argument
            android:name="productId"
            app:argType="integer" />
        <!-- Opens from links like https://karteasy.in/product/7 (see the deep links lesson) -->
        <deepLink app:uri="karteasy.in/product/{productId}" />
    </fragment>

    <!-- ===== Checkout flow ===== -->
    <fragment
        android:id="@+id/checkoutFragment"
        android:name="com.codingeasily.karteasy.ui.checkout.CheckoutFragment"
        android:label="@string/title_checkout">
        <action
            android:id="@+id/action_checkout_to_success"
            app:destination="@id/orderSuccessFragment"
            app:popUpTo="@id/cartFragment" />
    </fragment>

    <fragment
        android:id="@+id/orderSuccessFragment"
        android:name="com.codingeasily.karteasy.ui.orders.OrderSuccessFragment"
        android:label="@string/title_order_placed">
        <argument android:name="orderId" app:argType="integer" />
        <argument android:name="orderNo" app:argType="string" />
        <argument android:name="total" app:argType="string" />
        <action
            android:id="@+id/action_success_to_order_detail"
            app:destination="@id/orderDetailFragment"
            app:popUpTo="@id/homeFragment" />
    </fragment>

    <fragment
        android:id="@+id/addressListFragment"
        android:name="com.codingeasily.karteasy.ui.address.AddressListFragment"
        android:label="@string/nav_addresses">
        <argument
            android:name="pick"
            android:defaultValue="false"
            app:argType="boolean" />
    </fragment>

    <fragment
        android:id="@+id/addressEditFragment"
        android:name="com.codingeasily.karteasy.ui.address.AddressEditFragment"
        android:label="@string/title_address_edit">
        <argument
            android:name="addressId"
            android:defaultValue="0"
            app:argType="integer" />
    </fragment>

    <!-- ===== Orders & account ===== -->
    <fragment
        android:id="@+id/ordersFragment"
        android:name="com.codingeasily.karteasy.ui.orders.OrdersFragment"
        android:label="@string/nav_orders" />

    <fragment
        android:id="@+id/orderDetailFragment"
        android:name="com.codingeasily.karteasy.ui.orders.OrderDetailFragment"
        android:label="@string/title_order_detail">
        <argument
            android:name="orderId"
            app:argType="integer" />
    </fragment>

    <fragment
        android:id="@+id/editProfileFragment"
        android:name="com.codingeasily.karteasy.ui.account.EditProfileFragment"
        android:label="@string/title_edit_profile" />
</navigation>

Step 2: activity_main.xml

DrawerLayout के दो children हैं: main column (toolbar, offline banner, NavHost, bottom navigation) और header वाला NavigationView drawer।

XML – layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- DrawerLayout = side menu. Inside: app bar, offline banner, screens (NavHost) and bottom navigation. -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:attr/colorBackground"
        android:orientation="vertical">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/brand"
            android:elevation="4dp"
            app:navigationIconTint="@color/on_brand"
            app:titleTextColor="@color/on_brand" />

        <TextView
            android:id="@+id/offline_banner"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/offline"
            android:drawablePadding="8dp"
            android:gravity="center"
            android:padding="6dp"
            android:text="@string/offline_banner"
            android:textColor="@android:color/white"
            android:textSize="13sp"
            android:visibility="gone" />

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_main" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_nav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/surface"
            app:itemActiveIndicatorStyle="@style/Widget.Material3.BottomNavigationView.ActiveIndicator"
            app:itemIconTint="@color/nav_item_color"
            app:itemTextColor="@color/nav_item_color"
            app:labelVisibilityMode="labeled"
            app:menu="@menu/bottom_nav_menu" />
    </LinearLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
XML – layout/nav_header.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_header_gradient"
    android:orientation="vertical"
    android:paddingStart="20dp"
    android:paddingTop="32dp"
    android:paddingEnd="20dp"
    android:paddingBottom="20dp">

    <TextView
        android:id="@+id/header_avatar"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:background="@drawable/bg_avatar"
        android:gravity="center"
        android:text="K"
        android:textColor="@android:color/white"
        android:textSize="28sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/header_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:textAppearance="?attr/textAppearanceTitleMedium"
        android:textColor="@android:color/white"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/header_email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#DDFFFFFF" />
</LinearLayout>

Screen items destinations वाले ही ids use करते हैं। जो items actions हैं (share, help, logout) वे action_… ids use करते हैं और code में handle होते हैं।

XML – menu/bottom_nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Item ids MUST match the destination ids in nav_main.xml so setupWithNavController() works -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/homeFragment" android:icon="@drawable/ic_home" android:title="@string/nav_home" />
    <item android:id="@+id/categoriesFragment" android:icon="@drawable/ic_category" android:title="@string/nav_categories" />
    <item android:id="@+id/wishlistFragment" android:icon="@drawable/ic_heart_outline" android:title="@string/nav_wishlist" />
    <item android:id="@+id/cartFragment" android:icon="@drawable/ic_cart" android:title="@string/nav_cart" />
    <item android:id="@+id/accountFragment" android:icon="@drawable/ic_person" android:title="@string/nav_account" />
</menu>
XML – menu/drawer_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Screens: ids match nav_main.xml destinations -->
    <group android:id="@+id/group_main" android:checkableBehavior="single">
        <item android:id="@+id/homeFragment" android:icon="@drawable/ic_home" android:title="@string/nav_home" />
        <item android:id="@+id/categoriesFragment" android:icon="@drawable/ic_category" android:title="@string/nav_categories" />
        <item android:id="@+id/ordersFragment" android:icon="@drawable/ic_orders" android:title="@string/nav_orders" />
        <item android:id="@+id/wishlistFragment" android:icon="@drawable/ic_heart_outline" android:title="@string/nav_wishlist" />
        <item android:id="@+id/addressListFragment" android:icon="@drawable/ic_location" android:title="@string/nav_addresses" />
        <item android:id="@+id/accountFragment" android:icon="@drawable/ic_person" android:title="@string/nav_account" />
    </group>

    <!-- Actions handled in MainActivity -->
    <item android:title="@string/drawer_section_more">
        <menu>
            <item android:id="@+id/action_share" android:icon="@drawable/ic_share" android:title="@string/share_app" />
            <item android:id="@+id/action_help" android:icon="@drawable/ic_help" android:title="@string/help" />
            <item android:id="@+id/action_logout" android:icon="@drawable/ic_logout" android:title="@string/logout" />
        </menu>
    </item>
</menu>

Step 4: MainActivity

पूरी activity: splash decision (Splash lesson से), पाँच top-level destinations वाला AppBarConfiguration, NavController से जुड़े bottom nav + drawer, bottom bar छुपाने वाला destination listener, Back जो पहले drawer बंद करे, live drawer header, cart badge, offline banner, और expire sessions को login पर भेजने वाला 401 check।

Kotlin – MainActivity.kt
package com.codingeasily.karteasy

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.GravityCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.codingeasily.karteasy.databinding.ActivityMainBinding
import com.codingeasily.karteasy.ui.auth.AuthActivity
import com.codingeasily.karteasy.ui.onboarding.OnboardingActivity
import com.codingeasily.karteasy.util.hide
import com.codingeasily.karteasy.util.kartApp
import com.codingeasily.karteasy.util.visibleIf
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var navController: NavController
    private lateinit var appBarConfig: AppBarConfiguration
    private var ready = false

    private val askNotifications = registerForActivityResult(ActivityResultContracts.RequestPermission()) { }

    override fun onCreate(savedInstanceState: Bundle?) {
        // 1) Splash screen stays until we know where to go (onboarding / login / home).
        val splash = installSplashScreen()
        splash.setKeepOnScreenCondition { !ready }
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            val app = kartApp
            app.session.load()
            applyDarkMode(app.session.darkMode())
            when {
                !app.session.isOnboardingDone() -> openAndFinish(OnboardingActivity::class.java)
                !app.session.isLoggedIn -> openAndFinish(AuthActivity::class.java)
                else -> setupUi()
            }
            ready = true
        }
    }

    private fun openAndFinish(target: Class<*>) {
        startActivity(Intent(this, target))
        finish()
    }

    private fun setupUi() {
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)

        val host = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
        navController = host.navController

        // Top-level screens show the hamburger icon instead of a back arrow.
        appBarConfig = AppBarConfiguration(
            setOf(R.id.homeFragment, R.id.categoriesFragment, R.id.wishlistFragment, R.id.cartFragment, R.id.accountFragment),
            binding.drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfig)
        binding.bottomNav.setupWithNavController(navController)
        binding.navView.setupWithNavController(navController)

        // Drawer items that are not screens
        binding.navView.setNavigationItemSelectedListener { item ->
            when (item.itemId) {
                R.id.action_share -> shareApp()
                R.id.action_help -> showHelp()
                R.id.action_logout -> confirmLogout()
                else -> NavigationUI.onNavDestinationSelected(item, navController)
            }
            binding.drawerLayout.closeDrawer(GravityCompat.START)
            true
        }

        // Hide the bottom bar on detail / checkout screens.
        navController.addOnDestinationChangedListener { _, destination, _ ->
            val top = destination.id in appBarConfig.topLevelDestinations
            binding.bottomNav.visibleIf(top)
        }

        // Back button closes the drawer first.
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
                    binding.drawerLayout.closeDrawer(GravityCompat.START)
                } else {
                    isEnabled = false
                    onBackPressedDispatcher.onBackPressed()
                    isEnabled = true
                }
            }
        })

        observeApp()
        askNotificationPermission()
    }

    private fun observeApp() {
        val app = kartApp
        val header = binding.navView.getHeaderView(0)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    app.session.userFlow.collect { (name, email) ->
                        header.findViewById<TextView>(R.id.header_name).text = name
                        header.findViewById<TextView>(R.id.header_email).text = email
                        header.findViewById<TextView>(R.id.header_avatar).text =
                            name.trim().take(1).uppercase().ifEmpty { "K" }
                    }
                }
                // Cart badge on the bottom navigation
                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)
                    }
                }
                // Offline banner
                launch {
                    app.network.online.collect { online -> binding.offlineBanner.visibleIf(!online) }
                }
            }
        }
        // Load the cart count once (also checks that the token is still valid).
        lifecycleScope.launch {
            val r = app.repository.me()
            if (r is com.codingeasily.karteasy.data.api.Result.Error && r.code == 401) {
                app.session.clearLogin()
                openAndFinish(AuthActivity::class.java)
            }
        }
    }

    private fun askNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
        ) {
            askNotifications.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
    }

    private fun shareApp() {
        val send = Intent(Intent.ACTION_SEND).apply {
            type = "text/plain"
            putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text))
        }
        startActivity(Intent.createChooser(send, getString(R.string.share_app)))
    }

    private fun showHelp() {
        MaterialAlertDialogBuilder(this)
            .setTitle(R.string.help_title)
            .setMessage(R.string.help_message)
            .setPositiveButton(R.string.ok, null)
            .show()
    }

    fun confirmLogout() {
        MaterialAlertDialogBuilder(this)
            .setTitle(R.string.logout)
            .setMessage(R.string.logout_confirm)
            .setNegativeButton(R.string.cancel, null)
            .setPositiveButton(R.string.logout) { _, _ ->
                lifecycleScope.launch {
                    kartApp.repository.logout()
                    binding.bottomNav.hide()
                    openAndFinish(AuthActivity::class.java)
                }
            }
            .show()
    }

    override fun onSupportNavigateUp(): Boolean =
        navController.navigateUp(appBarConfig) || super.onSupportNavigateUp()

    companion object {
        fun applyDarkMode(mode: String) {
            AppCompatDelegate.setDefaultNightMode(
                when (mode) {
                    "light" -> AppCompatDelegate.MODE_NIGHT_NO
                    "dark" -> AppCompatDelegate.MODE_NIGHT_YES
                    else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
                }
            )
        }
    }
}
setupActionBarWithNavControllertoolbar में title + ☰ / ←bottomNav.setupWithNavControllertabs ↔ destinations, हर tab की state savenavView.setupWithNavControllerdrawer items ↔ destinationssetNavigationItemSelectedListenershare / help / logout + बाकी NavigationUI से
चार calls सारी navigation UI जोड़ती हैं

Step 5: Account screen

पहले letter वाले avatar के साथ gradient header, auth/me.php से तीन tap होने वाले counters, एक KartEasy.MenuRow style से बनी menu rows, theme switch और Logout।

XML – layout/fragment_account.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="24dp">

        <!-- Profile header -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_header_gradient"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:padding="20dp">

            <TextView
                android:id="@+id/avatar"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:background="@drawable/bg_avatar"
                android:gravity="center"
                android:textColor="@android:color/white"
                android:textSize="28sp"
                android:textStyle="bold"
                tools:text="R" />

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

                <TextView
                    android:id="@+id/name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?attr/textAppearanceTitleLarge"
                    android:textColor="@android:color/white"
                    android:textStyle="bold"
                    tools:text="Riya Sharma" />

                <TextView
                    android:id="@+id/email"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#DDFFFFFF"
                    tools:text="test@karteasy.in" />

                <TextView
                    android:id="@+id/phone"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#DDFFFFFF"
                    tools:text="9876543210" />
            </LinearLayout>
        </LinearLayout>

        <!-- Counts -->
        <com.google.android.material.card.MaterialCardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_marginTop="-16dp"
            android:layout_marginEnd="12dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:paddingTop="12dp"
                android:paddingBottom="12dp">

                <LinearLayout
                    android:id="@+id/stat_orders"
                    style="@style/KartEasy.Stat">
                    <TextView
                        android:id="@+id/orders_count"
                        style="@style/KartEasy.StatNumber"
                        tools:text="4" />
                    <TextView
                        style="@style/KartEasy.StatLabel"
                        android:text="@string/orders" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/stat_wishlist"
                    style="@style/KartEasy.Stat">
                    <TextView
                        android:id="@+id/wishlist_count"
                        style="@style/KartEasy.StatNumber"
                        tools:text="2" />
                    <TextView
                        style="@style/KartEasy.StatLabel"
                        android:text="@string/wishlist" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/stat_cart"
                    style="@style/KartEasy.Stat">
                    <TextView
                        android:id="@+id/cart_count"
                        style="@style/KartEasy.StatNumber"
                        tools:text="3" />
                    <TextView
                        style="@style/KartEasy.StatLabel"
                        android:text="@string/cart" />
                </LinearLayout>
            </LinearLayout>
        </com.google.android.material.card.MaterialCardView>

        <!-- Menu rows -->
        <com.google.android.material.card.MaterialCardView
            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">

                <TextView
                    android:id="@+id/row_orders"
                    style="@style/KartEasy.MenuRow"
                    app:drawableStartCompat="@drawable/ic_orders"
                    android:text="@string/nav_orders" />

                <TextView
                    android:id="@+id/row_addresses"
                    style="@style/KartEasy.MenuRow"
                    app:drawableStartCompat="@drawable/ic_location"
                    android:text="@string/nav_addresses" />

                <TextView
                    android:id="@+id/row_wishlist"
                    style="@style/KartEasy.MenuRow"
                    app:drawableStartCompat="@drawable/ic_heart_outline"
                    android:text="@string/nav_wishlist" />

                <TextView
                    android:id="@+id/row_profile"
                    style="@style/KartEasy.MenuRow"
                    app:drawableStartCompat="@drawable/ic_edit"
                    android:text="@string/title_edit_profile" />
            </LinearLayout>
        </com.google.android.material.card.MaterialCardView>

        <!-- Theme -->
        <com.google.android.material.card.MaterialCardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_marginEnd="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/theme" />

                <com.google.android.material.button.MaterialButtonToggleGroup
                    android:id="@+id/theme_group"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:selectionRequired="true"
                    app:singleSelection="true">

                    <com.google.android.material.button.MaterialButton
                        android:id="@+id/theme_system"
                        style="@style/Widget.Material3.Button.OutlinedButton"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/theme_system" />

                    <com.google.android.material.button.MaterialButton
                        android:id="@+id/theme_light"
                        style="@style/Widget.Material3.Button.OutlinedButton"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/theme_light" />

                    <com.google.android.material.button.MaterialButton
                        android:id="@+id/theme_dark"
                        style="@style/Widget.Material3.Button.OutlinedButton"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/theme_dark" />
                </com.google.android.material.button.MaterialButtonToggleGroup>
            </LinearLayout>
        </com.google.android.material.card.MaterialCardView>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/logout_button"
            style="@style/Widget.KartEasy.Button.Outlined"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="12dp"
            android:text="@string/logout"
            android:textColor="@color/sale" />

        <TextView
            android:id="@+id/version"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="@color/text_secondary"
            android:textSize="12sp"
            tools:text="KartEasy v1.0 · Made with CodingEasily" />
    </LinearLayout>
</ScrollView>
Kotlin – ui/account/AccountFragment.kt
package com.codingeasily.karteasy.ui.account

import android.app.Application
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import com.codingeasily.karteasy.BuildConfig
import com.codingeasily.karteasy.MainActivity
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.api.Result
import com.codingeasily.karteasy.data.model.User
import com.codingeasily.karteasy.databinding.FragmentAccountBinding
import com.codingeasily.karteasy.util.collectWhenStarted
import com.codingeasily.karteasy.util.kartApp
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class AccountViewModel(app: Application) : AndroidViewModel(app) {
    private val repo = app.kartApp.repository
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user

    fun load() {
        viewModelScope.launch {
            val r = repo.me()
            if (r is Result.Success) _user.value = r.data
        }
    }
}

class AccountFragment : Fragment() {

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

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val nav = findNavController()
        binding.rowOrders.setOnClickListener { nav.navigate(R.id.ordersFragment) }
        binding.rowAddresses.setOnClickListener { nav.navigate(R.id.addressListFragment) }
        binding.rowWishlist.setOnClickListener { nav.navigate(R.id.wishlistFragment) }
        binding.rowProfile.setOnClickListener { nav.navigate(R.id.editProfileFragment) }
        binding.statOrders.setOnClickListener { nav.navigate(R.id.ordersFragment) }
        binding.statWishlist.setOnClickListener { nav.navigate(R.id.wishlistFragment) }
        binding.statCart.setOnClickListener { nav.navigate(R.id.cartFragment) }
        binding.logoutButton.setOnClickListener { (requireActivity() as MainActivity).confirmLogout() }
        binding.version.text = getString(R.string.version_text, BuildConfig.VERSION_NAME)

        // Theme: System / Light / Dark
        viewLifecycleOwner.lifecycleScope.launch {
            val mode = requireContext().kartApp.session.darkMode()
            binding.themeGroup.check(
                when (mode) {
                    "light" -> R.id.theme_light
                    "dark" -> R.id.theme_dark
                    else -> R.id.theme_system
                }
            )
            binding.themeGroup.addOnButtonCheckedListener { _, id, checked ->
                if (!checked) return@addOnButtonCheckedListener
                val newMode = when (id) {
                    R.id.theme_light -> "light"
                    R.id.theme_dark -> "dark"
                    else -> "system"
                }
                viewLifecycleOwner.lifecycleScope.launch {
                    requireContext().kartApp.session.setDarkMode(newMode)
                    MainActivity.applyDarkMode(newMode)
                }
            }
        }

        collectWhenStarted(vm.user) { u ->
            if (u == null) return@collectWhenStarted
            binding.avatar.text = u.name.trim().take(1).uppercase()
            binding.name.text = u.name
            binding.email.text = u.email
            binding.phone.text = u.phone ?: getString(R.string.add_phone)
            binding.ordersCount.text = u.orderCount.toString()
            binding.wishlistCount.text = u.wishlistCount.toString()
            binding.cartCount.text = u.cartCount.toString()
        }
    }

    override fun onResume() {
        super.onResume()
        vm.load()
    }

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

Dark mode switch

System / Light / Dark वाला MaterialButtonToggleGroup। Choice DataStore में save होती है और AppCompatDelegate.setDefaultNightMode() से लगती है — activity values-night colours के साथ फिर से बनती है। अगली बार शुरू होने पर MainActivity कुछ भी दिखाने से पहले saved mode लगा देती है।

System (default)

  • phone की setting follow करे
  • MODE_NIGHT_FOLLOW_SYSTEM

Light / Dark

  • user सिर्फ इस app के लिए तय करे
  • MODE_NIGHT_NO / MODE_NIGHT_YES
DataStore में "system", "light" या "dark" के रूप में saved

Step 6: Edit profile

Name और phone बदल सकते हैं; email field helper text के साथ disabled है। Password वाला हिस्सा optional है: "New password" भरने पर current password ज़रूरी है, बिल्कुल auth/update.php जैसा। Drawer header अपने आप update होता है क्योंकि वह session.userFlow observe करता है।

XML – layout/fragment_edit_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/name_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/full_name"
            app:startIconDrawable="@drawable/ic_person">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/name_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName|textCapWords" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/email_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:enabled="false"
            android:hint="@string/email"
            app:helperText="@string/email_locked"
            app:startIconDrawable="@drawable/ic_email">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/email_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/phone_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:hint="@string/mobile"
            app:prefixText="+91 "
            app:startIconDrawable="@drawable/ic_phone">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/phone_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="phone"
                android:maxLength="10" />
        </com.google.android.material.textfield.TextInputLayout>

        <TextView
            style="@style/KartEasy.CardTitle"
            android:layout_marginTop="24dp"
            android:text="@string/change_password" />

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/current_password_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/current_password"
            app:endIconMode="password_toggle">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/current_password_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/new_password_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:hint="@string/new_password"
            app:endIconMode="password_toggle">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/new_password_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/save_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="@string/save" />
    </LinearLayout>
</ScrollView>
Kotlin – ui/account/EditProfileFragment.kt
package com.codingeasily.karteasy.ui.account

import android.app.Application
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.api.Result
import com.codingeasily.karteasy.data.model.User
import com.codingeasily.karteasy.databinding.FragmentEditProfileBinding
import com.codingeasily.karteasy.util.Validator
import com.codingeasily.karteasy.util.collectWhenStarted
import com.codingeasily.karteasy.util.kartApp
import com.codingeasily.karteasy.util.snack
import com.codingeasily.karteasy.util.toast
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 EditProfileViewModel(app: Application) : AndroidViewModel(app) {
    private val repo = app.kartApp.repository
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user
    private val _result = MutableSharedFlow<Result<User>>()
    val result: SharedFlow<Result<User>> = _result
    private val _saving = MutableStateFlow(false)
    val saving: StateFlow<Boolean> = _saving

    init {
        viewModelScope.launch {
            val r = repo.me()
            if (r is Result.Success) _user.value = r.data
        }
    }

    fun save(name: String, phone: String, current: String, new: String) {
        if (_saving.value) return
        _saving.value = true
        viewModelScope.launch {
            _result.emit(repo.updateProfile(name, phone, current, new))
            _saving.value = false
        }
    }
}

class EditProfileFragment : Fragment() {

    private var _binding: FragmentEditProfileBinding? = null
    private val binding get() = _binding!!
    private val vm: EditProfileViewModel by viewModels()
    private var filled = false

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        filled = savedInstanceState != null
        collectWhenStarted(vm.user) { u ->
            if (u == null || filled) return@collectWhenStarted
            filled = true
            binding.nameInput.setText(u.name)
            binding.emailInput.setText(u.email)
            binding.phoneInput.setText(u.phone)
        }
        collectWhenStarted(vm.saving) { binding.saveButton.isEnabled = !it }
        collectWhenStarted(vm.result) { r ->
            when (r) {
                is Result.Success -> { requireContext().toast(r.message); findNavController().navigateUp() }
                is Result.Error -> snack(r.message)
            }
        }
        binding.saveButton.setOnClickListener { save() }
    }

    private fun save() {
        val name = binding.nameInput.text.toString()
        val phone = binding.phoneInput.text.toString()
        val current = binding.currentPasswordInput.text.toString()
        val new = binding.newPasswordInput.text.toString()
        binding.nameLayout.error = if (Validator.name(name)) null else getString(R.string.err_name)
        binding.phoneLayout.error = if (phone.isEmpty() || Validator.phone(phone)) null else getString(R.string.err_phone)
        binding.newPasswordLayout.error = if (new.isEmpty() || Validator.password(new)) null else getString(R.string.err_password)
        binding.currentPasswordLayout.error = if (new.isNotEmpty() && current.isEmpty()) getString(R.string.err_current_password) else null
        if (listOf(binding.nameLayout, binding.phoneLayout, binding.newPasswordLayout, binding.currentPasswordLayout).any { it.error != null }) return
        vm.save(name, phone, current, new)
    }

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

Logout

confirm dialogLogout?POST auth/logout.phpserver यह token delete करेclearLogin()DataStore token हटेcache.evictAll()इस user का कोई data न बचेAuthActivitylogin screen
repository.logout() + MainActivity.confirmLogout()
🧹 HTTP cache क्यों साफ़ करें?

Offline cache cart और orders जैसे API जवाब रखता है। उसी phone पर कोई और login करे तो उसे पिछले user का data कभी नहीं दिखना चाहिए, offline भी नहीं।

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

Menu item ids destination ids जैसे क्यों होने चाहिए?
setupWithNavController() उस destination पर जाता है जिसका id tap किए item के id से मिले। Same ids होने पर bottom bar और drawer दोनों बिना click code के चलते हैं।
Detail screens पर bottom bar क्यों छुपता है?
Product detail, checkout और order screens पर fixed action bar के लिए जगह चाहिए। Destination-changed listener bottom bar सिर्फ पाँच top-level tabs पर दिखाता है।
किसी tab से Back Home पर जाता है?
हाँ। Home start destination है, इसलिए Categories, Wishlist, Cart या Account से Back पहले Home पर लाता है, और Home पर Back app बंद करता है (drawer खुला हो तो पहले उसे बंद करके)।
← E-commerce App Tutorial
🔗

Share this topic with a friend

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

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

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

अगला topicNotifications, Errors और Offline आपकी प्रगति: 25 / 28 (89%) · Lesson 26 / 28 आगे बढ़ें →

यह lesson कैसा लगा?

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

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

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

▶ CodingEasily on YouTube

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

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

▶ Channel subscribe करें