App Setup: Gradle, Packages और MVVM
Step 1: Project बनाएँ या खोलें
इन lessons को follow करके हर file खुद बना सकते हैं, या download से बना-बनाया project खोलकर साथ में पढ़ सकते हैं। नए से शुरू करने के लिए:
- 1Android Studio → New Project → Empty Views Activity (यह template XML layouts use करता है)
- 2Name KartEasy, package com.codingeasily.karteasy, language Kotlin, minimum SDK API 26, build language Kotlin DSL
- 3Generated Gradle files को नीचे वाली files से बदलें और Sync Now दबाएँ
- 4या: File → Open → ZIP का
android/KartEasyfolder चुनें
Step 2: Version catalog और dependencies
सारे library versions एक file में रहते हैं, gradle/libs.versions.toml। Module file उन्हें libs.retrofit, libs.glide आदि नाम से use करती है।
[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" }| Library | किसके लिए |
|---|---|
| material, constraintlayout, recyclerview, viewpager2, swiperefreshlayout | UI: cards, chips, grids, banner slider, pull to refresh |
| lifecycle, activity-ktx, fragment-ktx | ViewModel, by viewModels(), lifecycle-aware collection |
| navigation | screens के बीच जाना, back stack, deep links, bottom nav + drawer |
| retrofit + converter-gson + okhttp-logging | PHP API call करना, JSON → Kotlin, logs, offline cache |
| glide | URLs से product images load करना |
| datastore-preferences | login token और settings save करना |
| core-splashscreen | हर version पर Android 12+ style splash screen |
// 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
}pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "KartEasy"
include(":app")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 और release API URL
buildConfigField lines BuildConfig.BASE_URL बनाती हैं। Debug builds आपके computer से बात करते हैं, release builds live server से — switch करने के लिए code नहीं बदलना पड़ता।
debug (Android Studio में Run ▶)
- BASE_URL = http://10.0.2.2:8000/
- app name "KartEasy Debug"
- package …karteasy.debug (दोनों install हो सकते हैं)
- HTTP logging ON
release (Play Store)
- BASE_URL = https://yourdomain.com/karteasy/
- app name "KartEasy"
- R8 code shrink और obfuscate करता है
- HTTP logging OFF
Android emulator के अंदर localhost खुद emulator है। Special address 10.0.2.2 का मतलब है "वह computer जिस पर emulator चल रहा है", जहाँ आपका php -S server सुन रहा है।
Step 4: Manifest और network security
<?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 default रूप से plain http:// block करता है। हम इसे सिर्फ development addresses के लिए allow करते हैं, ताकि live app को HTTPS ही use करना पड़े:
<?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/
MVVM: कौन क्या करता है
| Layer | किसके बारे में जानती है | कभी नहीं करती |
|---|---|---|
| Fragment + XML | views, clicks, ViewModel | network calls, business rules |
| ViewModel | screen state, repository | views छूना (ताकि rotation में बची रहे) |
| Repository | ApiService, SessionManager | screens के बारे में कुछ भी |
Step 6: KartApp (shared objects)
Application class किसी भी screen से पहले बनती है और app जितनी देर चलती है उतनी देर रहती है। हम session, network monitor और repository वहाँ एक बार बनाते हैं, और हर screen उन्हें reuse करती है।
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)
}
}// 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: App + backend साथ चलाएँ
Phone को उसी Wi-Fi पर रखें, अपने PC का IP निकालें (ipconfig / ifconfig, जैसे 192.168.1.5), debug BASE_URL को http://192.168.1.5:8000/ करें और वह IP network_security_config.xml में जोड़ें।
