Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
📱 Android App (Kotlin + XML)  ·  Lesson 25

Profile, Drawer & Bottom Nav

The app map

MainActivity (toolbar + drawer + bottom nav + NavHost)
  Bottom tabs (top level, hamburger icon)
    homeFragment ─ categoriesFragment ─ wishlistFragment ─ cartFragment ─ accountFragment
  Deeper screens (back arrow, bottom bar hidden)
    productListFragment → productDetailFragment
    checkoutFragment → addressListFragment → addressEditFragment
    orderSuccessFragment → orderDetailFragment
    ordersFragment, editProfileFragment
AuthActivity: loginFragment ⇄ registerFragment
OnboardingActivity
17 screens in 3 activities
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
The navigation drawer and the Account tab

Step 1: nav_main.xml

All shop screens and their arguments in one graph. Arguments have defaults where they are optional, so navigate(R.id.productListFragment) also works without a bundle. The product detail also declares a deep link.

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

A DrawerLayout holds two children: the main column (toolbar, offline banner, NavHost, bottom navigation) and the NavigationView drawer with its header.

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 use the same ids as the destinations. Items that are actions (share, help, logout) use action_… ids and are handled in code.

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

The full activity: splash decision (from the Splash lesson), AppBarConfiguration with five top-level destinations, bottom nav + drawer wired to the NavController, a destination listener that hides the bottom bar, Back that closes the drawer first, live drawer header, cart badge, offline banner, and a 401 check that sends expired sessions to login.

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
                }
            )
        }
    }
}
setupActionBarWithNavControllertitle + ☰ / ← in the toolbarbottomNav.setupWithNavControllertabs ↔ destinations, saves each tab statenavView.setupWithNavControllerdrawer items ↔ destinationssetNavigationItemSelectedListenershare / help / logout + everything else via NavigationUI
Four calls connect all navigation UI

Step 5: Account screen

A gradient header with the first letter as avatar, three tappable counters from auth/me.php, menu rows built from one KartEasy.MenuRow style, the theme switch and 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

A MaterialButtonToggleGroup with System / Light / Dark. The choice is saved in DataStore and applied with AppCompatDelegate.setDefaultNightMode() — the activity recreates itself with the values-night colours. At the next start MainActivity applies the saved mode before showing anything.

System (default)

  • follows the phone setting
  • MODE_NIGHT_FOLLOW_SYSTEM

Light / Dark

  • user decides for this app only
  • MODE_NIGHT_NO / MODE_NIGHT_YES
Saved in DataStore as "system", "light" or "dark"

Step 6: Edit profile

Name and phone can change; the email field is disabled with a helper text. The password section is optional: filling "New password" requires the current password, exactly like auth/update.php. The drawer header updates by itself because it observes session.userFlow.

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 deletes this tokenclearLogin()DataStore token removedcache.evictAll()no data of this user staysAuthActivitylogin screen
repository.logout() + MainActivity.confirmLogout()
🧹 Why clear the HTTP cache?

The offline cache stores API answers like the cart and orders. If another person logs in on the same phone, they must never see the previous user's data, even offline.

Frequently Asked Questions

Why must menu item ids equal destination ids?
setupWithNavController() navigates to the destination whose id matches the tapped item id. With the same ids, both the bottom bar and the drawer work with zero click code.
Why is the bottom bar hidden on detail screens?
On product detail, checkout and order screens the space is needed for the fixed action bar. The destination-changed listener shows the bottom bar only on the five top-level tabs.
Does Back from a tab go to Home?
Yes. Home is the start destination, so Back from Categories, Wishlist, Cart or Account returns to Home first, and Back on Home closes the app (after closing the drawer if it is open).
← E-commerce App Tutorial
🔗

Share this topic with a friend

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

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

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

Next topicNotifications, Errors & Offline Your progress: 25 / 28 (89%) · Lesson 26 / 28 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free