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

App Setup: Gradle, Packages & MVVM

Step 1: Create or open the project

You can build every file yourself by following these lessons, or open the finished project from the download and read along. To start fresh:

  1. 1Android Studio → New Project → Empty Views Activity (this template uses XML layouts)
  2. 2Name KartEasy, package com.codingeasily.karteasy, language Kotlin, minimum SDK API 26, build language Kotlin DSL
  3. 3Replace the generated Gradle files with the ones below and click Sync Now
  4. 4Or: File → Open → select the android/KartEasy folder from the ZIP
New project settings
🔒 Android Studio – New ProjectName: KartEasyPackage name: com.codingeasily.karteasyLanguage: KotlinMinimum SDK: API 26 ("Oreo"; Android 8.0)Build configuration language: Kotlin DSLFinish

Step 2: Version catalog and dependencies

All library versions live in one file, gradle/libs.versions.toml. The module file refers to them as libs.retrofit, libs.glide and so on.

TOML – gradle/libs.versions.toml
[versions]
agp = "8.7.3"
kotlin = "2.0.21"
coreKtx = "1.15.0"
appcompat = "1.7.0"
material = "1.12.0"
constraintlayout = "2.2.0"
recyclerview = "1.4.0"
viewpager2 = "1.1.0"
swiperefresh = "1.1.0"
activityKtx = "1.9.3"
fragmentKtx = "1.8.5"
lifecycle = "2.8.7"
navigation = "2.8.5"
datastore = "1.1.1"
splashscreen = "1.0.1"
coroutines = "1.9.0"
retrofit = "2.11.0"
okhttp = "4.12.0"
glide = "4.16.0"
junit = "4.13.2"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashscreen" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
androidx-viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefresh" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
LibraryUsed for
material, constraintlayout, recyclerview, viewpager2, swiperefreshlayoutthe UI: cards, chips, grids, banner slider, pull to refresh
lifecycle, activity-ktx, fragment-ktxViewModel, by viewModels(), lifecycle-aware collection
navigationmoving between screens, back stack, deep links, bottom nav + drawer
retrofit + converter-gson + okhttp-loggingcalling the PHP API, JSON → Kotlin, logs, offline cache
glideloading product images from URLs
datastore-preferencessaving the login token and settings
core-splashscreenthe Android 12+ style splash screen on every version
Kotlin DSL – build.gradle.kts (project)
// Top-level build file: only declares plugins, the app module applies them.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
}
Kotlin DSL – settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "KartEasy"
include(":app")
Kotlin DSL – app/build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "com.codingeasily.karteasy"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.codingeasily.karteasy"
        minSdk = 26
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    // Upload key for the Play Store. Create it once with Build > Generate Signed Bundle,
    // then put the 4 values in ~/.gradle/gradle.properties (never commit passwords).
    signingConfigs {
        create("release") {
            val storeFilePath = providers.gradleProperty("KARTEASY_STORE_FILE").orNull
            if (storeFilePath != null) {
                storeFile = file(storeFilePath)
                storePassword = providers.gradleProperty("KARTEASY_STORE_PASSWORD").orNull
                keyAlias = providers.gradleProperty("KARTEASY_KEY_ALIAS").orNull
                keyPassword = providers.gradleProperty("KARTEASY_KEY_PASSWORD").orNull
            }
        }
    }

    buildTypes {
        debug {
            // Emulator -> your computer running "php -S 0.0.0.0:8000" inside the backend folder
            buildConfigField("String", "BASE_URL", "\"http://10.0.2.2:8000/\"")
            applicationIdSuffix = ".debug"
            resValue("string", "app_name", "KartEasy Debug")
        }
        release {
            // Your live backend (must end with a slash)
            buildConfigField("String", "BASE_URL", "\"https://yourdomain.com/karteasy/\"")
            resValue("string", "app_name", "KartEasy")
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
            if (providers.gradleProperty("KARTEASY_STORE_FILE").isPresent) {
                signingConfig = signingConfigs.getByName("release")
            }
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    buildFeatures {
        viewBinding = true
        buildConfig = true
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.core.splashscreen)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.androidx.recyclerview)
    implementation(libs.androidx.viewpager2)
    implementation(libs.androidx.swiperefreshlayout)
    implementation(libs.androidx.activity.ktx)
    implementation(libs.androidx.fragment.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.livedata.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.navigation.fragment.ktx)
    implementation(libs.androidx.navigation.ui.ktx)
    implementation(libs.androidx.datastore.preferences)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.retrofit)
    implementation(libs.retrofit.converter.gson)
    implementation(libs.okhttp.logging)
    implementation(libs.glide)
    testImplementation(libs.junit)
}

Step 3: Debug and release API URL

The buildConfigField lines create BuildConfig.BASE_URL. Debug builds talk to your computer, release builds to your live server — you never change code to switch.

debug (Run ▶ in Android Studio)

  • BASE_URL = http://10.0.2.2:8000/
  • app name "KartEasy Debug"
  • package …karteasy.debug (both can be installed)
  • HTTP logging ON

release (Play Store)

  • BASE_URL = https://yourdomain.com/karteasy/
  • app name "KartEasy"
  • R8 shrinks and obfuscates code
  • HTTP logging OFF
One project, two configurations
📌 What is 10.0.2.2?

Inside the Android emulator, localhost is the emulator itself. The special address 10.0.2.2 means "the computer running the emulator", where your php -S server is listening.

Step 4: Manifest and network security

XML – app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- NetworkMonitor checks if the phone is online -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- Android 13+ asks before showing the "Order placed" notification -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:name=".KartApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:supportsRtl="true"
        android:theme="@style/Theme.KartEasy">

        <!-- Launcher: shows the splash screen, then decides where to go -->
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@style/Theme.KartEasy.Splash"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <!-- Creates intent filters for the <deepLink> entries in nav_main.xml -->
            <nav-graph android:value="@navigation/nav_main" />
        </activity>

        <activity
            android:name=".ui.onboarding.OnboardingActivity"
            android:exported="false" />

        <activity
            android:name=".ui.auth.AuthActivity"
            android:exported="false"
            android:windowSoftInputMode="adjustResize" />
    </application>
</manifest>

Android blocks plain http:// by default. We allow it only for the development addresses, so the live app is forced to use HTTPS:

XML – res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- HTTPS everywhere. Plain HTTP is allowed only for your own computer while developing
     (10.0.2.2 = the computer, seen from the Android emulator). -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">10.0.2.2</domain>
        <domain includeSubdomains="false">localhost</domain>
    </domain-config>
</network-security-config>

Step 5: Package structure

📁 app/src/main/java/com/codingeasily/karteasy/
  KartApp.kt                Application: creates shared objects once
  MainActivity.kt           splash, toolbar, drawer, bottom nav, badge
  📁 data/
    📁 api/      ApiService.kt  ApiClient.kt  Result.kt
    📁 model/    Models.kt                     (all JSON classes)
    📁 repo/     ShopRepository.kt             (only class that calls the API)
    📁 session/  SessionManager.kt             (DataStore: token, theme …)
  📁 notify/     Notifier.kt
  📁 util/       Format.kt  Validator.kt  NetworkMonitor.kt  Extensions.kt
  📁 ui/
    📁 onboarding/  auth/  home/  category/  product/
    📁 cart/  wishlist/  checkout/  address/  orders/  account/

📁 app/src/main/res/
  📁 layout/ (35 files)  drawable/ (47)  navigation/ nav_main, nav_auth
  📁 menu/  values/  values-night/  values-sw600dp/  xml/  anim/  color/
Organise by layer (data, util) and by screen (ui/…)

MVVM: who does what

FragmentViewModelShopRepositoryPHP APIvm.addToCart(7)repo.addToCart(7)POST cart/add.phpJSONResult.Success(cart)StateFlow emits new stateupdate views
A click travels down, data flows back up
LayerKnows aboutNever does
Fragment + XMLviews, clicks, the ViewModelnetwork calls, business rules
ViewModelscreen state, the repositorytouch views (so it survives rotation)
RepositoryApiService, SessionManageranything about screens

Step 6: KartApp (shared objects)

The Application class is created before any screen and lives as long as the app. We create the session, network monitor and repository there once, and every screen reuses them.

Kotlin – KartApp.kt
package com.codingeasily.karteasy

import android.app.Application
import com.codingeasily.karteasy.data.api.ApiClient
import com.codingeasily.karteasy.data.repo.ShopRepository
import com.codingeasily.karteasy.data.session.SessionManager
import com.codingeasily.karteasy.notify.Notifier
import com.codingeasily.karteasy.util.NetworkMonitor

/**
 * Application class = simple "service locator".
 * Objects created here live as long as the app and are shared by every screen:
 * (application as KartApp).repository
 */
class KartApp : Application() {

    lateinit var session: SessionManager
        private set
    lateinit var network: NetworkMonitor
        private set
    lateinit var repository: ShopRepository
        private set

    override fun onCreate() {
        super.onCreate()
        session = SessionManager(this)
        network = NetworkMonitor(this)
        repository = ShopRepository(ApiClient.create(this, session, network), session)
        Notifier.createChannels(this)
    }
}
Kotlin – using it
// Anywhere in a Fragment or ViewModel:
val repo = requireContext().kartApp.repository      // Extensions.kt: Context.kartApp

// In an AndroidViewModel:
class HomeViewModel(app: Application) : AndroidViewModel(app) {
    private val repo = app.kartApp.repository
}

Step 7: Run app + backend together

Terminal 1cd backend && php -S 0.0.0.0:8000Browser checklocalhost:8000/api/ → connectedAndroid StudioRun ▶ on an emulatorAppcalls 10.0.2.2:8000
Start the server first, then the app
📱 Testing on a real phone

Connect the phone to the same Wi-Fi, find your PC's IP (ipconfig / ifconfig, e.g. 192.168.1.5), change the debug BASE_URL to http://192.168.1.5:8000/ and add that IP to network_security_config.xml.

Frequently Asked Questions

Gradle sync shows an error — what should I check?
Use Android Studio Ladybug or newer with JDK 17 (the default bundled JDK), make sure you have internet for the first sync, and accept the SDK 35 download if asked. If a library was updated, Android Studio's "Update to" suggestion in libs.versions.toml is safe to accept.
Why no Hilt or Dagger?
A small Application class that creates the repository once is enough for this app and easy to read. When the project grows, Hilt can replace KartApp without changing the ViewModels much.
Why minSdk 26?
Android 8.0+ covers almost all phones in use in India today, and it lets us use notification channels and adaptive icons without extra checks.
← E-commerce App Tutorial
🔗

Share this topic with a friend

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

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

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

Next topicBeautiful Theme: Colors, Fonts & Styles Your progress: 13 / 28 (46%) · Lesson 14 / 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