Skip to content

About

Multi-platform store for sports supplements and healthy products, built with Kotlin Multiplatform

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

NutriSport (Kotlin Multiplatform)

Sports nutrition e-commerce app built with Kotlin Multiplatform and Compose Multiplatform.

The repository includes:

  • Android and iOS apps,
  • seller/admin flows inside the mobile app,
  • a Firebase backend (Auth, Firestore, Storage, Analytics, Crashlytics, and Functions),
  • tooling for seed data, docs generation, and Android release automation.

Tech stack

  • Kotlin Multiplatform, Compose Multiplatform, and Material 3
  • MVVM architecture, repositories, and DI with Koin
  • Firebase Auth, Firestore, Storage, Analytics, Crashlytics, and Functions
  • Ktor, Kotlin Coroutines, and Flow
  • Dokka, GitHub Actions, and Firebase CLI

Architecture and modules

Modular structure by domain and feature:

  • :androidApp -> Android host app, splash screen, permissions, and Android-only integrations
  • :composeApp -> shared Compose app entry point exported to Android and iOS
  • iosApp -> iOS host project (Xcode) and iOS lifecycle integrations
  • :core -> domain models and shared navigation/request-state primitives
  • :domain -> repository contracts and use cases
  • :data -> Firebase-backed repository implementations
  • :di -> Koin wiring and initialization
  • :shared -> shared UI, theming, i18n, analytics, preferences, and platform helpers
  • :navigation -> routes, stack rules, and feature wiring
  • :feature:auth -> login/register, Google Sign-In, forgot password, email verification
  • :feature:home + submodules -> home shell, catalog, categories, search, cart, checkout, and store pickup point selection
  • :feature:details -> product detail and guided add-to-cart flow
  • :feature:orders -> authenticated customer order history and order detail
  • :feature:blog -> localized educational video/blog content
  • :feature:contact -> support/contact flow
  • :feature:profile, :feature:settings -> profile and account settings
  • :feature:admin_panel, :feature:admin_panel:manage_product -> seller product management
  • :feature:payment_completed -> post-checkout result screen
  • :dbseed -> Firestore seed data and maintenance utilities
  • Firebase/functions -> Cloud Functions for checkout, push acknowledgements, stock sync, product i18n sync, hard delete, auth-provider lookup, and order email documents

Useful verification commands:

./gradlew :androidApp:assembleDebug
./gradlew quickAndroidTests
./gradlew androidCi
./gradlew allTestWithReport

Main features

  • Email/password and Google Sign-In authentication.
  • Product catalog with tokenized search and EN/ES localization.
  • Guided add-to-cart flow from the product details screen.
  • Cart and checkout with customer profile validation and two delivery methods: home delivery (requires full address) and store pickup (pickup point selection with map).
  • My Orders for authenticated customers: order history list, empty state, and order detail.
  • Backend-managed checkout for both PayPal and cash on delivery. Order creation is server-side only; the client never writes to the orders collection.
  • Seller/admin product management with stock validation and soft deactivation rules.
  • Role model (user, seller, admin) in customers/{uid}/privateData/roles.
  • Push notifications with topic subscription and delivery/open acknowledgement on Android and iOS.
  • Blog/video content and contact support flow.

Offline-first

The app is designed to remain functional in low-connectivity or fully offline conditions.

What is enabled

  • Firestore offline persistence — enabled at app startup via configureFirebasePersistence() before any Firestore operation runs. Cache size is set to 100 MB.
  • Coil3 disk cache — image cache limited to 50 MB so the catalog renders from disk when the network is unavailable.

How connectivity is tracked

  • NetworkMonitor is a KMP interface in :domain with a single Flow<Boolean> property (isOnline). Platform implementations live in :data:
    • Android: AndroidNetworkMonitor — backed by ConnectivityManager.
    • iOS: IosNetworkMonitor — backed by NWPathMonitor.
  • ConnectivityViewModel in :shared exposes both isOnline and hasResolvedConnectivity. The UI stays in a safe unresolved state until the first platform snapshot arrives, which avoids false positives on iOS startup.
  • OfflineChip is a dismissible bottom chip in :shared that appears after connectivity has been resolved and the device is offline. It is hoisted into NavGraph, so every screen inherits it automatically.
  • Critical flows that cannot complete offline are guarded at action level:
    • CheckoutScreen blocks PayPal and cash-on-delivery actions.
    • ManageProductScreen blocks save, delete, upload-image and delete-image actions.

Behaviour by operation type

Operation Offline behaviour
Catalog browsing Reads from Firestore cache — fully functional
Product details Reads from Firestore cache — fully functional
Order history Reads from Firestore cache — fully functional
Checkout (cash on delivery) Blocked by action guards — requires connectivity
Checkout (PayPal) Blocked by action guards — requires connectivity
Seller product upload/edit Blocked by action guards — requires connectivity
Write operations (cart, profile) Queued by Firebase SDK and synced when back online

Non-critical reads (catalog, order history) work entirely from the local Firestore cache. Critical writes that involve backend validation — checkout and admin actions — are blocked with a clear message and an invitation to retry when connectivity is restored.

Checkout architecture

  • Order creation is handled by Firebase Cloud Functions, not directly by the mobile client.
  • Pay on delivery calls a backend endpoint that validates the authenticated user, cart content, stock, and total amount before creating the order.
  • PayPal checkout is split into backend order creation and backend capture confirmation. The mobile app only opens the approval URL and sends the approved token back to the backend.
  • Firestore rules block direct client-side writes to orders, so only trusted backend code can create final order documents.
  • PayPal credentials are stored in Firebase Functions secrets, never in the app binary or repository source files.

My Orders

  • The drawer includes a dedicated My Orders entry for authenticated customers.
  • The orders screen streams the current user's orders in real time and sorts them by most recent first.
  • Empty-state and error-state UI are handled in the screen itself, so the user always gets feedback when there are no orders or when loading fails.
  • Tapping an order opens a detail screen that shows the order reference, date, admin status, total amount, and all purchased items.
  • The detail screen loads product metadata separately so item rows can show product thumbnails and names; if a product no longer exists, the UI falls back to the product id.
  • Order status chips reflect the backend status values (processing, completed, cancelled) and use the shared theme palette so they adapt to light and dark mode.

Requirements

  • JDK 21
  • Android Studio (latest stable recommended)
  • Xcode 16+ (for iOS)
  • Node.js 24+ (for Firebase/functions)
  • Firebase CLI (npm i -g firebase-tools) if you deploy backend resources

Project setup

  1. Open the repository once in Android Studio so local.properties points to your Android SDK.
  2. If you build iOS locally, set TEAM_ID in iosApp/Configuration/Config.xcconfig.
  3. If you use the Firestore seed utilities, place your Firebase Admin SDK JSON at dbseed/serviceAccountKey.json.
  4. If you want automatic order emails, install/configure a mail sender for the Firestore mail collection (for example the Firebase Trigger Email extension).
  5. If you want automatic product i18n completion in Cloud Functions, enable the Google Cloud Translation API in the Firebase/GCP project.
  6. If you want the pickup-point map on Android, fill the root secrets.properties file with MAPS_API_KEY=.... The repository ships local.defaults.properties as a safe fallback and the Android build reads the real key through the Secrets Gradle Plugin.

Local run

Android build from the project root:

./gradlew :androidApp:assembleDebug

Run from Android Studio when you need an emulator/device install, or use the generated APK/AAB from Gradle tasks.

For iOS:

  1. Set TEAM_ID in iosApp/Configuration/Config.xcconfig.
  2. Open iosApp/iosApp.xcodeproj in Xcode.
  3. Run the iosApp scheme on a simulator or device.

Tests

Fast Android suite (no iOS, ideal for daily iteration):

./gradlew quickAndroidTests

Generates the Android-only HTML report at build/reports/tests/platform/android/index.html.

Android CI suite used by GitHub Actions:

./gradlew androidCi

Full Android+iOS suite with HTML dashboard:

./gradlew allTestWithReport

Generates a cross-platform dashboard at build/reports/tests/crossPlatform/index.html.

Firestore seed data

Initial seed data lives in dbseed/seeds/products.json, dbseed/seeds/customers.json, and dbseed/seeds/orders.json.

Seed Firestore from the repository root:

./gradlew :dbseed:run

Update only localized product fields for existing documents:

./gradlew :dbseed:updateProductsI18n

Seed only the pickup-point sample data:

./gradlew :dbseed:seedPickupPoints

Notes:

  • :dbseed:run clears and recreates the customers, products, orders, and pickup_points collections.
  • :dbseed:seedPickupPoints only clears and recreates pickup_points.
  • Both seed tasks expect dbseed/serviceAccountKey.json to exist locally.

Documentation (KDoc + Dokka)

API documentation is written with KDoc in Kotlin sources and generated with Dokka.

Commands (run from the repository root):

./gradlew docsAndroidApp
./gradlew docsProject
./gradlew docsProjectOpen
./gradlew docsProjectServe
./gradlew docsProjectServeStop

Notes:

  • Consolidated HTML index: build/dokka/html/index.html
  • docsProjectServe starts a local background server and finishes successfully.
  • Short developer guide: docs/kmp-documentation-guide.md

Android CI/CD and Play Store release

This project is prepared to validate Android changes in GitHub Actions and to generate signed release artifacts for :androidApp (applicationId = dev.andrescoder.nutrisport).

What the repo does

  • pull_request -> main: runs ./gradlew androidCi and uploads test/lint reports.
  • push -> main: runs the same validation and then builds a signed .aab and .apk.
  • workflow_dispatch: lets you trigger the same release-artifact flow manually from GitHub and, when needed, override versionCode and versionName for a Play Store-ready release.
  • Artifacts are uploaded to GitHub Actions, not committed back into the repository.

What you need to configure once in GitHub

  1. Commit the .github/workflows/ files in this repository.
  2. Create these GitHub Actions secrets in Settings -> Secrets and variables -> Actions:
    • ANDROID_UPLOAD_KEYSTORE_BASE64
    • ANDROID_UPLOAD_STORE_PASSWORD
    • ANDROID_UPLOAD_KEY_ALIAS
    • ANDROID_UPLOAD_KEY_PASSWORD
    • ANDROID_MAPS_API_KEY
  3. Protect main, require pull requests, and require the Verify Android job before merge.
  4. Keep versionCode and versionName in androidApp/build.gradle.kts up to date for normal branch builds, or provide them manually when launching a final Play Store release with workflow_dispatch.

If you already have the Play upload keystore, convert it to Base64 and store that value in ANDROID_UPLOAD_KEYSTORE_BASE64.

macOS:

base64 -i "$HOME/keys/nutrisport-upload.jks" | pbcopy

Linux:

base64 -w 0 "$HOME/keys/nutrisport-upload.jks" | xclip -selection clipboard

Local validation and manual signed build

Run from the repository root:

./gradlew androidCi

If you need a local signed release build, export the signing credentials first:

export NUTRISPORT_UPLOAD_STORE_FILE="$HOME/keys/nutrisport-upload.jks"
export NUTRISPORT_UPLOAD_STORE_PASSWORD="<store-password>"
export NUTRISPORT_UPLOAD_KEY_ALIAS="nutrisport_upload"
export NUTRISPORT_UPLOAD_KEY_PASSWORD="<key-password>"

Then build both release artifacts:

./gradlew androidReleaseArtifacts \
  -Pandroid.injected.signing.store.file="$NUTRISPORT_UPLOAD_STORE_FILE" \
  -Pandroid.injected.signing.store.password="$NUTRISPORT_UPLOAD_STORE_PASSWORD" \
  -Pandroid.injected.signing.key.alias="$NUTRISPORT_UPLOAD_KEY_ALIAS" \
  -Pandroid.injected.signing.key.password="$NUTRISPORT_UPLOAD_KEY_PASSWORD"

Expected outputs:

  • androidApp/build/outputs/bundle/release/androidApp-release.aab
  • androidApp/build/outputs/apk/release/androidApp-release.apk

Optional verification:

jarsigner -verify -verbose -certs androidApp/build/outputs/bundle/release/androidApp-release.aab

Release flow

  1. For regular branch automation, merge to main and let the workflow build signed artifacts using the versions committed in androidApp/build.gradle.kts.
  2. For a definitive Play Store release, open Actions -> Android CI and Release Artifacts -> Run workflow.
  3. Fill in both manual inputs:
    • release_version_code
    • release_version_name
  4. Run the workflow and wait for Build Release Artifacts to finish in green.
  5. Download the android-release-... artifact from the run page.
  6. Extract the ZIP downloaded by GitHub and upload the .aab to Testing -> Internal testing in Play Console.
  7. Promote to production with staged rollout after validating internal metrics.

Important:

  • release_version_code and release_version_name must be provided together for manual releases.
  • Play Console requires a unique versionCode for every uploaded release.

Security and release notes

  • Never commit the upload keystore or passwords to Git.
  • Keep at least two encrypted backups of the .jks file.
  • If you lose the upload key material, future Play updates may require a key reset process.
  • Before the first production release, complete Play App Signing, Data safety, Content rating, and Privacy policy requirements.

Firebase deployment

1) Cloud Functions

cd Firebase/functions
npm install
npm run build
cd ..
firebase deploy --only functions

Selective deploy:

cd Firebase
firebase deploy --only functions:sendPushNotification,functions:hardDeleteProduct,functions:setUserAccessState

Checkout-related Functions:

cd Firebase
firebase deploy --only functions:createCashOnDeliveryOrder,functions:createPaypalOrder,functions:capturePaypalOrder

PayPal secrets for checkout:

cd Firebase
firebase functions:secrets:set PAYPAL_CLIENT_ID
firebase functions:secrets:set PAYPAL_CLIENT_SECRET

Notes:

  • When rotating a PayPal secret, update it with firebase functions:secrets:set ... and redeploy the PayPal Functions so the new secret version is picked up.
  • For a safe rollout, deploy checkout Functions before deploying stricter Firestore rules that block direct client order creation.
  • syncProductLocalizedText relies on the Google Cloud Translation API being enabled in the target project.
  • createEmailDocument only writes to the Firestore mail collection; a mail sender extension or equivalent backend consumer must exist in the Firebase project for emails to actually be sent.

2) Firestore Rules

cd Firebase
firebase deploy --only firestore:rules

Current checkout security model:

  • Clients can read their own orders, but cannot create orders documents directly.
  • Final order creation happens only through Cloud Functions / Admin SDK.
  • Pickup points are read-only for authenticated users and live in the root pickup_points collection.

3) Firestore Indexes

cd Firebase
firebase deploy --only firestore:indexes

For this feature, no extra composite index is required for pickup_points; the app only uses a single-field equality filter on isActive.

Mobile screenshots

Login (Dark) Register (Dark) Drawer (Dark)
mobile-01 mobile-02 mobile-03
Home (Dark) Seller (Dark) Cart (Dark)
mobile-04 mobile-05 mobile-06
Product (Dark) New Product (Dark) Modals (Dark)
mobile-07 mobile-08 mobile-09
Blog (Dark) Details (Dark) Error (Dark)
mobile-10 mobile-11 mobile-12
Category (Dark) Category Details (Dark) Guide (Dark)
mobile-13 mobile-14 mobile-15
Login (Light) Register (Light) Home (Light)
mobile-16 mobile-17 mobile-18
Drawer (Light) Seller (Light) Product (Light)
mobile-19 mobile-20 mobile-21
Cart (Light) Category (Light) Checkout (Light)
mobile-22 mobile-23 mobile-24
Logout (Light) Contact (Light) Settings (Light)
mobile-25 mobile-26 mobile-27
Guide (Light) Menu User (Dark) My Orders (Dark)
mobile-28 mobile-29 mobile-30
Order Details (Dark) Login Error (Dark) Checkout Error (Dark)
mobile-31 mobile-32 mobile-33

Notes

  • The admin web dashboard is maintained in a separate project (nutrisport-admin).

About

Multi-platform store for sports supplements and healthy products, built with Kotlin Multiplatform

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages