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

Release App & Host the API

Go-live plan

Database on hostingcreate + import database.sqlUpload backendpublic_html/karteasyconfig.phpDB details + https BASE_URLCheckapi/ + admin + smoke testApp release URLBASE_URL in build.gradle.ktsSigned AABGenerate Signed BundlePlay Consolelisting, testing, production
Server first, then the app

Step 1: Create the database

  1. 1hPanel → Databases → MySQL Databases
  2. 2Database name karteasy, user kart, a strong password → Create. Hostinger adds a prefix: u123456789_karteasy, u123456789_kart
  3. 3Click Enter phpMyAdmin for that database
  4. 4Import → choose database.sql → Go → 14 tables appear
Hostinger hPanel (cPanel is very similar: MySQL Database Wizard)
🔒 hpanel.hostinger.com – MySQL DatabasesDatabase name: u123456789_karteasyUsername: u123456789_kartPassword: ••••••••••••Create✔ Database created · Enter phpMyAdmin →

Step 2: Upload the backend

  1. 1hPanel → File Manager → public_html
  2. 2Create the folder karteasy
  3. 3Upload a ZIP of the backend folder contents and click Extract (faster than many single files)
  4. 4Make sure hidden files like .htaccess are there (File Manager → Settings → Show hidden files)
Uploading with File Manager
📁 public_html/
  📁 karteasy/
    .htaccess
    database.sql      (protected by .htaccess – you can also delete it now)
    📁 api/  admin/  includes/  uploads/  tests/
The live folder

Step 3: Edit config.php

Open karteasy/includes/config.php in File Manager and replace the default values with your database details and your domain:

PHP – includes/config.php
// includes/config.php on Hostinger (example values – use yours from hPanel)
define('DB_HOST', getenv('KE_DB_HOST') ?: 'localhost');
define('DB_PORT', getenv('KE_DB_PORT') ?: '3306');
define('DB_NAME', getenv('KE_DB_NAME') ?: 'u123456789_karteasy');
define('DB_USER', getenv('KE_DB_USER') ?: 'u123456789_kart');
define('DB_PASS', getenv('KE_DB_PASS') ?: 'Your-Strong-DB-Password');

define('BASE_URL', rtrim(getenv('KE_BASE_URL') ?: 'https://yourdomain.com/karteasy', '/'));
define('DEBUG', getenv('KE_DEBUG') === '1'); // stays false online
🔒 Turn on SSL first

hPanel → Security → SSL → install the free certificate and enable Force HTTPS. The app's release build only talks to https.

Step 4: Check it online

  1. 1Open https://yourdomain.com/karteasy/api/ → "database": "connected", "products": 16
  2. 2Open an image: …/karteasy/uploads/products/p7.png
  3. 3Open …/karteasy/includes/config.php → must be 403 Forbidden
  4. 4Log in to …/karteasy/admin/ and change the admin password
  5. 5From your computer: php tests/smoke-test.php https://yourdomain.com/karteasy
Five checks before touching the app
Terminal – what a passing run looks like (local test shown)
KartEasy API test → http://127.0.0.1:8200/api/ PASS API is running and DB connected PASS home.php has banners, categories and products PASS products.php search sorted by price PASS unknown product gives 404 PASS cart without token gives 401 PASS register a new customer PASS same email again gives 409 PASS me.php returns the new user PASS add to cart PASS too many gives 409 PASS update quantity to 2 PASS total = subtotal - discount + delivery PASS wishlist toggle on PASS wishlist toggle off PASS bad phone gives 422 PASS save address (first one is default) PASS invalid coupon gives 422 PASS place order PASS cart is empty after the order PASS order detail has items and timeline PASS review before delivery gives 403 PASS cancel order (stock goes back) PASS cancel again gives 409 PASS cannot open another user's order (404) PASS logout PASS old token no longer works 26 passed, 0 failed

Step 5: Point the app to the live API

Only the release block changes. Keep the trailing slash — Retrofit needs it.

Kotlin DSL – app/build.gradle.kts
// app/build.gradle.kts – release block
release {
    buildConfigField("String", "BASE_URL", "\"https://yourdomain.com/karteasy/\"")
    resValue("string", "app_name", "KartEasy")
    isMinifyEnabled = true
    isShrinkResources = true
    …
}

defaultConfig {
    versionCode = 1        // +1 for every Play Store upload
    versionName = "1.0"    // what users see
}

Before publishing, remove the Use demo account button from fragment_login.xml and LoginFragment.kt, and replace the demo shop data with your real products in the admin panel.

Step 6: Signing key and release build

Play Store apps must be signed. Create your upload key once: Android Studio → Build → Generate Signed App Bundle / APK → Android App Bundle → Create new… (save the .jks file safely). Then let Gradle use it without putting passwords in the project:

Properties – ~/.gradle/gradle.properties
# ~/.gradle/gradle.properties  (in your user folder – NOT inside the project, never commit it)
KARTEASY_STORE_FILE=/home/you/keys/karteasy-upload.jks
KARTEASY_STORE_PASSWORD=your-store-password
KARTEASY_KEY_ALIAS=upload
KARTEASY_KEY_PASSWORD=your-key-password

The signingConfigs block in app/build.gradle.kts (Setup lesson) reads these four values. Build the bundle:

Terminal
# Windows: gradlew.bat bundleRelease
./gradlew bundleRelease

# Result:
app/build/outputs/bundle/release/app-release.aab

APK

  • installs directly on a phone
  • good for sharing a test build
  • Build → Build APK(s)

AAB (App Bundle)

  • required by Google Play
  • Play makes small APKs per device
  • bundleRelease
Upload the .aab to Play Console

R8 and ProGuard rules

isMinifyEnabled = true lets R8 remove unused code and rename classes — the app becomes smaller and harder to reverse-engineer. But Gson reads field names with reflection, so we must keep our model classes as they are:

ProGuard – app/proguard-rules.pro
# Gson creates the model classes with reflection, so R8 must keep their fields.
-keep class com.codingeasily.karteasy.data.model.** { *; }

# Retrofit + coroutines: keep generic signatures of suspend functions.
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

# Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-dontwarn okhttp3.internal.platform.**
⚠️ Always test the release build

Install the release build on a phone before uploading. A missing keep rule shows up as empty lists or all-zero prices only in release, never in debug.

Step 7: Publish on Google Play

  1. 1Create a Google Play developer account (one-time fee) and a new app KartEasy
  2. 2Store listing: short and full description, 512 × 512 icon, 1024 × 500 feature graphic, at least 2 phone screenshots
  3. 3App content: privacy policy URL (you collect name, email, phone and address), data safety form, content rating, target audience
  4. 4Testing → Internal testing: upload the .aab, add testers, install from the Play link
  5. 5New personal developer accounts must run a closed test with real testers before production — follow the checklist Play Console shows
  6. 6Production → create release → upload the same or a newer .aab → rollout
From AAB to Play Store

The Android course has a full lesson on this: Release on Play Store.

Updating later

ChangeWhat to do
Products, prices, banners, couponsadmin panel only — no app update
A bug in an API fileupload the fixed PHP file — every phone gets the fix at once
New screen or app bugversionCode + 1, new .aab, new release in Play Console
New API fieldadd it to PHP first (old apps ignore unknown fields), then use it in the app

The backend README in the download repeats these steps:

Markdown – backend/README.md
# KartEasy Backend (PHP + MySQL REST API + Admin Panel)

Backend for the **KartEasy** Android shopping app from the CodingEasily tutorial
(https://codingeasily.in/tutorial/android-ecommerce/).

Works on **PHP 7.2 or newer** (tested on PHP 8.3) and **MySQL 5.7+ / MariaDB 10.3+**.
No Composer, no framework: upload and it runs on any shared hosting (Hostinger, cPanel, XAMPP).

## Folder structure

```
karteasy/
├── .htaccess            passes the Authorization header, hides database.sql
├── database.sql         tables + sample data (16 products, 6 categories, 3 banners, 2 coupons)
├── includes/            config, DB connection, helpers (blocked from the browser)
├── api/                 JSON endpoints used by the app
│   ├── home.php categories.php banners.php products.php product.php
│   ├── auth/      register login logout me update
│   ├── cart/      list add update remove
│   ├── wishlist/  list toggle
│   ├── address/   list save delete
│   ├── coupons/   list apply
│   ├── orders/    place list detail cancel
│   └── reviews/   list add
├── admin/               admin panel (login, dashboard, orders, products, categories, banners, coupons, customers)
├── uploads/             product / category / banner images (PHP execution disabled)
└── tests/smoke-test.php  26 automatic API checks (run from the command line)
```

## Install on Hostinger (or any cPanel hosting)

1. **Create a database**: hPanel → Databases → MySQL Databases. Note the database name, user and password.
2. **Import tables**: open phpMyAdmin for that database → Import → choose `database.sql` → Go.
3. **Upload files**: File Manager → `public_html` → create folder `karteasy` → upload everything from this folder into it.
4. **Edit `includes/config.php`**: set `DB_NAME`, `DB_USER`, `DB_PASS` and `BASE_URL`
   (for example `https://yourdomain.com/karteasy`).
5. **Test**: open `https://yourdomain.com/karteasy/api/` → you should see `"database":"connected"`.
6. **Admin panel**: `https://yourdomain.com/karteasy/admin/`
   login `admin@karteasy.in` / `Admin@123` → **change this password** (see below).

Demo customer for the app: `test@karteasy.in` / `Test@123`.

### Change the admin password

Run this once in a PHP file or `php -r`, then put the output in phpMyAdmin → `admins.password_hash`:

```php
<?php echo password_hash('YourNewStrongPassword', PASSWORD_DEFAULT);
```

## Run locally (XAMPP / PHP built-in server)

```bash
mysql -u root -p -e "CREATE DATABASE karteasy CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
mysql -u root -p karteasy < database.sql
# edit includes/config.php (DB_USER=root, BASE_URL=http://10.0.2.2:8000) then:
php -S 0.0.0.0:8000
```

The Android emulator reaches your computer at `http://10.0.2.2:8000/`.
A real phone on the same Wi-Fi uses your computer's IP, e.g. `http://192.168.1.5:8000/`.

## Test the API

```bash
php tests/smoke-test.php http://localhost:8000
php tests/smoke-test.php https://yourdomain.com/karteasy
```

It registers a fresh test customer and runs a full flow (cart, wishlist, address, order, cancel, logout).
You should see `26 passed, 0 failed`.

## Response format

Every endpoint returns the same JSON shape:

```json
{ "success": true, "message": "Added to cart.", "data": { ... } }
```

Errors use a proper HTTP status (401 not logged in, 404 not found, 409 conflict such as stock,
422 validation) with `"success": false` and a readable `message`.

## Authentication

`auth/login.php` and `auth/register.php` return a 64-character `token`.
Send it on every protected request:

```
Authorization: Bearer <token>
```

Only the SHA-256 hash of the token is stored in `user_tokens`, tokens expire after 30 days,
and `auth/logout.php` deletes it.

## Security checklist (already done)

- Prepared statements everywhere (no SQL injection), whitelisted sort columns
- `password_hash()` / `password_verify()` for passwords
- Prices, coupons and totals are always calculated on the server
- Order placement runs in one transaction with `SELECT ... FOR UPDATE` (no overselling)
- Admin: session login, `session_regenerate_id`, CSRF token on every form, login rate limit,
  `htmlspecialchars` on all output
- Uploads: real image check with `getimagesize`, 2 MB limit, random file names, no PHP in `uploads/`
- `includes/` and `database.sql` cannot be opened from the browser

Before going live: use HTTPS, change the admin password, keep `DEBUG` off.

Frequently Asked Questions

Can I use a subdomain like api.mysite.com?
Yes. Create the subdomain in hPanel, upload the backend files into its folder, and set BASE_URL to https://api.mysite.com (and the app release BASE_URL to https://api.mysite.com/).
I lost my upload key — what now?
With Play App Signing (the default), Google keeps the real app signing key. You can request an upload key reset in Play Console. Still, back up the .jks file and passwords somewhere safe.
Images do not load in the release app but work in debug.
Check that BASE_URL in config.php is https and correct — image URLs are built from it. Plain http image URLs are blocked in release by the network security config.
← E-commerce App Tutorial
🔗

Share this topic with a friend

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

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

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

Next topicE-commerce Project Viva Questions Your progress: 27 / 28 (96%) · Lesson 28 / 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