ParkEZ Documentation

Complete setup and deployment guide for your multi-app smart parking management system — Customer, Owner, Watchman, and Admin Panel.

v1.0 - Flutter + Firebase
🚗
Platform
Android, iOS & Web
🔧
Framework
Flutter + GetX + Firebase
📦
Apps Included
4 Apps (Customer / Owner / Watchman / Admin)
Backend
Firestore + Storage + FCM

About ParkEZ

ParkEZ is a complete smart parking management ecosystem that connects drivers searching for parking, owners listing their parking spaces, watchmen managing on-ground operations, and administrators overseeing the entire platform. Built with Flutter and Firebase, it ships as four tightly integrated apps that share a single backend.

Key Features

📍

Find & Book Parking

Customers can discover nearby parking spaces on the map, view photos, check availability, and book slots in real time.

🔐

Multi-Method Login

Phone OTP, Google Sign-In, and Apple Sign-In through Firebase Authentication.

💵

Wallet & Payments

Built-in wallet for owners, customer payments, refunds on cancellation, and a complete transaction ledger.

🔔

Real-Time Notifications

Booking, check-in, check-out, and admin announcements pushed via Firebase Cloud Messaging (FCM).

📊

Admin Web Panel

Manage owners, watchmen, customers, parking spaces, bookings, reviews, and platform-wide settings.

Firebase Backend

Firestore, Firebase Storage, and FCM — a fully managed backend with no separate server to maintain.

What's Included

Before starting the setup, make sure you have the following tools and accounts ready.

⚠ External Services & Costs

ParkEZ relies on Firebase (Auth, Firestore, Storage, Cloud Messaging, Hosting) and Google Maps Platform. Both are third-party services with their own pricing: Firebase's free Spark plan and Google Maps' monthly free credit cover development and small deployments, but production usage beyond the free tiers is billed by Google to your own account. Payment gateways (Stripe, Razorpay, PayPal) also charge per-transaction fees. These costs are not included in the item price — review Firebase pricing and Google Maps pricing before going live.

Required Software

Software Version Purpose
Flutter SDK 3.22 or later (stable) Framework for building all four apps
Dart SDK Bundled with Flutter Programming language for Flutter
Android Studio Latest IDE & Android SDK / Emulator
Xcode (macOS only) Latest iOS development & simulator
Node.js & npm LTS version Firebase CLI & Firestore database import
Firebase CLI Latest Deploy and manage Firebase services
Git Latest Version control
VS Code / Android Studio Latest Code editor with Flutter / Dart plugins

Required Accounts

Important:
  • Phone authentication and some Firebase Storage quotas require the Firebase Blaze plan (pay-as-you-go). The free Spark plan still includes generous limits for development.
  • Ensure your system meets Flutter's minimum system requirements.

The ParkEZ package ships as a single repository containing four Flutter projects and a shared documentation folder.

Mobile · Android / iOS

Customer App

End-user app for drivers to discover, book, and pay for parking spaces, manage their wallet, view booking history, and rate parkings.

Folder: /customer
Mobile · Android / iOS

Owner App

For parking-space owners to onboard their lots, set pricing, manage availability, view earnings, and withdraw to their bank.

Folder: /owner
Mobile · Android / iOS

Watchman App

For on-ground staff assigned by owners to verify vehicles, scan booking QR codes, perform check-in / check-out, and handle disputes.

Folder: /watchman
Web · Flutter Web

Admin Panel

Web dashboard for administrators to manage owners, watchmen, customers, parkings, bookings, payments, reviews, notifications, and settings.

Folder: /admin_panel

Repository Layout

ParkEZ/
+-- admin_panel/        # Flutter Web - Admin Dashboard
+-- customer/           # Flutter Mobile - Customer App
+-- owner/              # Flutter Mobile - Owner App
+-- watchman/           # Flutter Mobile - Watchman App
+-- android/            # Shared Android scripts (if any)
+-- cloud_functions/    # Firebase Cloud Functions - server-side payment operations
+-- documentation/      # This documentation
+-- database/           # Firestore collection import / export scripts
+-- firestore_index/    # firestore.indexes.json + rules
+-- README.md
Shared Firebase Project

All four apps connect to the same Firebase project. Each app is registered as a separate platform within that project (Android / iOS / Web) so they share Firestore, Authentication, Storage, and Cloud Messaging.

New to Flutter or Firebase? Don't worry. This page explains the whole setup in plain words. Follow the steps from top to bottom — each one links to a detailed page with exact commands, screenshots, and copy buttons on every command.

Quick Start Video

Prefer to watch instead of read? This short walkthrough shows the complete setup from start to finish — downloading the package, creating the Firebase project, importing the database, connecting the Admin Panel, and running the mobile apps.

What is in this package?

What do you need before starting?

The Whole Setup — 7 Simple Steps (same order as the left menu)

  1. Step 1 — Installation & Firebase. Install the free Flutter tool on your computer, create your Firebase project (this is your backend — no server needed), turn on logins, and import our ready-made database.
  2. Step 2 — Admin Panel. Connect the admin website to your Firebase, put it on your domain, and open it in a browser. From here you can change the app name, colors, and logo with simple upload buttons.
  3. Step 3 — Mobile Apps. Rename the three mobile apps to your brand, connect them to the same Firebase, add your Google Maps key, and build the Android / iPhone files ready for the stores.
  4. Step 4 — iOS Configuration. Bundle IDs, capabilities, APNS key, and App Store publishing for the three iOS apps (needs a Mac; skip if launching Android-only first).
  5. Step 5 — Push Notifications. A short setting so users get booking alerts on their phones.
  6. Step 6 — Landing Page. Put the marketing website online. Its text and images are edited from the Admin Panel.
  7. Step 7 — Localization (optional). 14 languages are already included. Turn them on/off, or add your own.
Estimated Setup Time

Following this guide carefully, the complete end-to-end setup for all four apps typically takes 2–3 hours depending on your familiarity with Flutter and Firebase.

Golden rules for a smooth setup
  • Do the steps in order — later steps depend on earlier ones.
  • All four apps must use the same one Firebase project.
  • If anything shows an error, open Troubleshooting (Common Errors) — the most common problems are listed there with their exact fix.
  • Want to see the apps before setting up? Browse the Screenshots (App UI) section.

This is the complete, ordered installation walkthrough. Each step links to the detailed section covering it — do them in this order; later steps depend on earlier ones.

Phase 1 — Environment

  1. Install the Flutter SDK and add it to your PATH — Setting Up Flutter.
  2. Install Android Studio (with an emulator) and, on macOS, Xcode + CocoaPods for iOS builds.
  3. Run flutter doctor and resolve every reported issue before continuing.
  4. Unzip the package. You will see customer/, owner/, watchman/, admin_panel/, landing_page/, cloud_functions/ and documentation/.

Phase 2 — Firebase Project

  1. Create a Firebase project at console.firebase.google.com.
  2. Register all four apps (three mobile + admin web) — Firebase Setup (CLI) or manual setup.
  3. Enable sign-in providers (Email, Phone, Google, Apple) — Enabling Authentication.
  4. Create the Firestore database and import the starter collections — Firestore Import.
  5. Deploy the Firestore indexes — Indexing Import.
  6. Enable Firebase Storage and apply the rules + CORS config — Storage Setup and CORS.
  7. Deploy the Cloud Functions and Firestore security rules (required for payments, wallets & security) — Cloud Functions.

Phase 3 — API Keys & Signing

  1. Create a Google Maps API key and add it to Android/iOS — Google Maps API.
  2. Generate SHA-1 / SHA-256 keys and add them to Firebase — Generate SHA Keys.
  3. Configure push notifications (FCM; APNS key on iOS) — Notification Setup.

Phase 4 — Run & Deploy

  1. Run each mobile app: flutter pub get then flutter run inside customer/, owner/, watchman/Run Flutter App.
  2. Set up and deploy the Admin Panel — Admin Panel Setup, then Deploy on Server or Deploy on Firebase.
  3. Map your domain and enable SSL — Production Deployment.
  4. Log in to the Admin Panel and complete in-app configuration — see the Configuration Guide.
Stuck?

Every common failure we know of is listed in Troubleshooting with its fix.

ParkEZ targets Flutter stable. Follow the steps below for your operating system.

Setting Up Flutter on Windows

System Requirements
Download Flutter SDK
Add Flutter to System Path
Verify Installation
flutter doctor
Install Android Studio
Set Up an Android Emulator
Final Setup Check
flutter doctor

Setting Up Flutter on macOS

System Requirements
Download Flutter SDK
Add Flutter to PATH
export PATH="$PATH:$HOME/development/flutter/bin"
Install Xcode
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
Install CocoaPods
sudo gem install cocoapods
Verify Installation
flutter doctor

ParkEZ supports three authentication methods used across the Customer, Owner, and Watchman apps. Enable each one in the Firebase Console.

1. Enable Phone Authentication

2. Enable Google Sign-In

3. Enable Apple Sign-In

Note:

Apple Sign-In requires an active Apple Developer account ($99/year). It is mandatory for iOS apps that offer third-party login options.

Import the pre-built ParkEZ Firestore collections (users, owners, watchmen, parkings, bookings, transactions, settings, etc.) to populate your database with the required structure.

1. Create the Firestore Database

  1. Open the Firebase Console and select your project.
  2. In the left menu go to BuildFirestore Database.
  3. Click Create database.
  4. Pick the location closest to your users (e.g. asia-south1 for India). This cannot be changed later.
  5. Choose a starting mode — either is fine, because you will set the rules yourself in the next step:
    • Start in production mode → rules begin as allow read, write: if false;everything is blocked, so the apps will show no data until you update the rules.
    • Start in test mode → rules begin open but expire after 30 days, after which everything is blocked automatically.
  6. Click Create and wait for the database to be provisioned.

2. Set the Firestore Security Rules

ParkEZ ships with production-grade security rules (cloud_functions/firestore.rules, also included in firestore_index.zip). They scope every collection: customers only manage their own profile, wallet history, and bookings; owners only their own parkings and staff; watchmen only their assigned parking; admins manage everything. Wallet balances and payment secrets are locked so they can only be touched by the Cloud Functions (Admin SDK) and the Admin Panel — never directly by an app user.

Option A (recommended): the rules deploy automatically together with the Cloud Functions in the Cloud Functions step (firebase deploy --only functions,firestore) — nothing to do here.

Option B (manual): in Firestore Database → Rules, delete everything in the editor, paste the following, and click Publish:

rules_version = '2';

// ParkEZ production security rules.
//
// Highlights:
//  - Nothing is writable without authentication; most reads require sign-in.
//  - Wallet balances (walletAmount) can NEVER be changed from a client app:
//    all balance mutations go through the Cloud Functions (Admin SDK).
//  - wallet_transaction documents are read-only for their owner and can only
//    be created by the Cloud Functions / admin panel.
//  - Payment gateway secret keys live in settings/payment_secrets, which only
//    admins can read; the apps read only the sanitized settings/payment doc.
//  - Users/owners/watchmen only see and manage what belongs to them; the
//    admin panel (users present in the `admin` collection) manages everything.

service cloud.firestore {
  match /databases/{database}/documents {

    function isAuthed() {
      return request.auth != null;
    }

    function isSelf(id) {
      return isAuthed() && request.auth.uid == id;
    }

    function isAdmin() {
      return isAuthed() && exists(/databases/$(database)/documents/admin/$(request.auth.uid));
    }

    // The client may not change the wallet balance of an existing document.
    function walletUntouched() {
      return !request.resource.data.diff(resource.data).affectedKeys().hasAny(['walletAmount']);
    }

    // New profile documents must start with an empty wallet.
    function walletStartsEmpty() {
      return !('walletAmount' in request.resource.data)
        || request.resource.data.walletAmount in ['0', '0.0', 0, 0.0];
    }

    function isWatchmanFor(parkingId) {
      return isAuthed()
        && exists(/databases/$(database)/documents/watchman/$(request.auth.uid))
        && get(/databases/$(database)/documents/watchman/$(request.auth.uid)).data.assignedParkingId == parkingId;
    }

    // ── Admin panel users ────────────────────────────────────────────────
    match /admin/{adminId} {
      allow read, write: if isAdmin();
    }

    // ── Profiles (wallet balance locked for clients) ─────────────────────
    match /customers/{userId} {
      allow read: if isAuthed();
      allow create: if isAdmin() || (isSelf(userId) && walletStartsEmpty());
      allow update: if isAdmin() || (isSelf(userId) && walletUntouched());
      allow delete: if isAdmin() || isSelf(userId);

      match /{subCollection}/{docId} {
        allow read: if isAuthed();
        allow write: if isAdmin() || isSelf(userId);
      }
    }

    match /owners/{ownerId} {
      allow read: if isAuthed();
      allow create: if isAdmin() || (isSelf(ownerId) && walletStartsEmpty());
      allow update: if isAdmin() || (isSelf(ownerId) && walletUntouched());
      allow delete: if isAdmin() || isSelf(ownerId);

      match /{subCollection}/{docId} {
        allow read: if isAuthed();
        allow write: if isAdmin() || isSelf(ownerId);
      }
    }

    match /watchman/{watchmanId} {
      allow read: if isAuthed();
      // A watchman edits their own profile; the owner who employs them (and
      // admins) manage the account.
      allow create: if isAdmin() || (isAuthed() && request.resource.data.ownerId == request.auth.uid);
      allow update: if isAdmin() || isSelf(watchmanId)
        || (isAuthed() && resource.data.ownerId == request.auth.uid);
      allow delete: if isAdmin() || (isAuthed() && resource.data.ownerId == request.auth.uid);
    }

    // ── Parking & slots ──────────────────────────────────────────────────
    match /parking/{parkingId} {
      allow read: if true; // public listings (browse before login)
      allow create: if isAdmin() || (isAuthed() && request.resource.data.ownerId == request.auth.uid);
      allow update: if isAdmin()
        || (isAuthed() && resource.data.ownerId == request.auth.uid)
        || isWatchmanFor(parkingId)
        // customers may only bump the review counters after rating
        || (isAuthed() && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['reviewSum', 'reviewCount']));
      allow delete: if isAdmin() || (isAuthed() && resource.data.ownerId == request.auth.uid);

      match /{subCollection}/{docId} {
        allow read: if true;
        // slot status changes during booking / check-in / check-out
        allow write: if isAuthed();
      }
    }

    // ── Bookings (participants only) ─────────────────────────────────────
    match /bookings/{bookingId} {
      allow read: if isAdmin()
        || (isAuthed() && (resource.data.userId == request.auth.uid || resource.data.ownerId == request.auth.uid))
        || isWatchmanFor(resource.data.parkingId);
      allow create: if isAdmin() || (isAuthed() && request.resource.data.userId == request.auth.uid);
      allow update: if isAdmin()
        || (isAuthed() && (resource.data.userId == request.auth.uid || resource.data.ownerId == request.auth.uid))
        || isWatchmanFor(resource.data.parkingId);
      allow delete: if isAdmin();
    }

    // ── Money (Cloud Functions / Admin SDK only) ─────────────────────────
    match /wallet_transaction/{txnId} {
      allow read: if isAdmin() || (isAuthed() && resource.data.userId == request.auth.uid);
      allow write: if isAdmin(); // apps never write these; Cloud Functions bypass rules
    }

    match /transaction_log/{logId} {
      allow read: if isAdmin() || (isAuthed() && resource.data.userId == request.auth.uid);
      allow create: if isAdmin() || (isAuthed() && request.resource.data.userId == request.auth.uid);
      allow update, delete: if isAdmin();
    }

    // ── Settings ─────────────────────────────────────────────────────────
    // Gateway secret keys: admins only. The apps and Cloud Functions never
    // need to read this document from the client side.
    match /settings/payment_secrets {
      allow read, write: if isAdmin();
    }

    match /settings/{settingId} {
      // Everything except the secrets document is public app config
      // (branding, landing page content, sanitized payment config, …).
      allow read: if settingId != 'payment_secrets';
      allow write: if isAdmin();
    }

    // ── Admin-managed content (public read) ──────────────────────────────
    match /currencies/{docId} { allow read: if true; allow write: if isAdmin(); }
    match /languages/{docId} { allow read: if true; allow write: if isAdmin(); }
    match /country_tax/{docId} { allow read: if true; allow write: if isAdmin(); }
    match /onboarding_screen/{docId} { allow read: if true; allow write: if isAdmin(); }
    match /parking_facilities/{docId} { allow read: if true; allow write: if isAdmin(); }
    match /coupons/{docId} { allow read: if isAuthed(); allow write: if isAdmin(); }
    match /email_template/{docId} { allow read: if isAuthed(); allow write: if isAdmin(); }
    match /documents/{docId} { allow read: if isAuthed(); allow write: if isAdmin(); }
    match /role_permissions/{docId} { allow read: if isAdmin(); allow write: if isAdmin(); }
    match /broadcast_notifications/{docId} { allow read: if isAuthed(); allow write: if isAdmin(); }

    // ── Reviews ──────────────────────────────────────────────────────────
    match /review_customer/{reviewId} {
      allow read: if true;
      allow create: if isAuthed();
      allow update, delete: if isAdmin() || (isAuthed() && resource.data.customerId == request.auth.uid);
    }

    // ── Notifications & chat ─────────────────────────────────────────────
    // A notification's recipient is identified by the role-specific id field
    // (customerId / ownerId / watchmanId). Only the recipient — and admins —
    // can read, update (e.g. mark as read), or delete it. Any signed-in user
    // may create one (apps notify counterparties about booking events).
    match /notification/{notificationId} {
      function isRecipient() {
        return isAuthed()
          && (resource.data.get('customerId', '') == request.auth.uid
            || resource.data.get('ownerId', '') == request.auth.uid
            || resource.data.get('watchmanId', '') == request.auth.uid);
      }
      allow read: if isAdmin() || isRecipient();
      allow create: if isAuthed();
      allow update, delete: if isAdmin() || isRecipient();
    }

    // Chat rooms are private to their participants: every room document must
    // store a `participants` array of user ids. Only those users — and
    // admins — can read or write the room and its messages, and a message
    // can only be created or edited by its own author (senderId).
    match /chat_rooms/{roomId} {
      function isRoomParticipant() {
        return isAuthed() && request.auth.uid in resource.data.get('participants', []);
      }
      allow read: if isAdmin() || isRoomParticipant();
      allow create: if isAdmin()
        || (isAuthed() && request.auth.uid in request.resource.data.get('participants', []));
      allow update: if isAdmin()
        || (isRoomParticipant()
          && request.resource.data.get('participants', []) == resource.data.get('participants', []));
      allow delete: if isAdmin();

      match /messages/{messageId} {
        function isParticipant() {
          return isAuthed()
            && request.auth.uid in get(/databases/$(database)/documents/chat_rooms/$(roomId)).data.get('participants', []);
        }
        allow read: if isAdmin() || isParticipant();
        allow create: if isAdmin()
          || (isParticipant() && request.resource.data.get('senderId', '') == request.auth.uid);
        allow update, delete: if isAdmin()
          || (isParticipant() && resource.data.get('senderId', '') == request.auth.uid);
      }
    }

    // ── Everything else: admins only ─────────────────────────────────────
    match /{document=**} {
      allow read, write: if isAdmin();
    }
  }
}
Never use open rules

Do not publish allow read, write: if true — not even “temporarily for testing”. Open rules let anyone read or modify your entire database, including wallets and payment configuration. The database import in the next steps does not need open rules: it uses a service account (Admin SDK), which bypasses security rules.

Seeing [cloud_firestore/permission-denied]?

If any app shows empty screens and the console logs permission-denied, the rules were not published (or an old version is live) — re-check this step, or run firebase deploy --only firestore from the cloud_functions folder. Also make sure you are signed in inside the app: almost all data requires an authenticated user.

3. Install Node.js and npm

node -v
npm -v

4. Extract Database Files

5. Open Terminal

cd path/to/extracted/folder

6. Generate Firebase Service Account Key

🔎 Navigation Path: Firebase Console → ⚙ (Gear Icon) → Project Settings → Service accounts → Firebase Admin SDK → Node.js → Generate new private key

7. Run Import Command

npm install firestore-export-import
node import
Success:

Once the import completes, your Firestore database will have all required ParkEZ collections including customers, owners, watchmen, parkings, bookings, transactions, reviews, notifications, and settings.

Important Notes:
  • Ensure you are inside the correct project directory before running any Firebase CLI commands.
  • You must have Editor or Owner role in your Firebase project.
  • Indexes are required for queries like "nearby parkings by city + price range" and "bookings by owner + status + date".

1. Navigate to firestore_index Folder

cd path/to/firestore_index
Step A: Run firebase init
firebase init
Terminal Output:
You're about to initialize a Firebase project in this directory:
  C:\path\to\your\firestore_index

? Are you ready to proceed? (Y/n)
Step B: Select Firebase Features
Select this option:
(*) Firestore: Configure security rules and indexes files for Firestore

Use arrow keys to move, Space to select, Enter to confirm.

Step C: Choose Firebase Project
Step D: Set Up Firestore Rules and Indexes
Step E: Deploy Firestore Indexes
firebase deploy --only firestore:indexes
Deployment Complete
Expected Terminal Output:
=== Deploying to 'parkez-xxxxx'...

i  firestore: reading indexes from firestore.indexes.json...
i  cloud.firestore: checking firestore.rules for compilation errors...
+  cloud.firestore: rules file firestore.rules compiled successfully
i  firestore: deploying indexes...
+  firestore: deployed indexes in firestore.indexes.json successfully

+  Deploy complete!

Project Console: https://console.firebase.google.com/project/parkez-xxxxx/overview

ParkEZ uses Firebase Storage to host parking photos, profile pictures, owner KYC documents, and watchman ID proofs.

1. Enable Storage

2. Apply Storage Rules

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /parkings/{parkingId}/{allFiles=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }
    match /users/{userId}/{allFiles=**} {
      allow read: if request.auth != null;
      allow write: if request.auth != null && request.auth.uid == userId;
    }
  }
}
Tip:

For production, tighten read access on user-uploaded KYC documents to admins only. See the optional rules block in storage.rules shipped with the project.

3. Configure CORS on the Storage Bucket (required for web)

Browsers block the Admin Panel and Landing Page from loading Storage images unless the bucket sends CORS headers — without this step, images upload fine from mobile but appear broken/blank on the web.

  1. Open Google Cloud Console → select your Firebase project → click the Cloud Shell icon (top right).
  2. Create the CORS config file:
echo '[{ "origin": ["*"], "method": ["GET"], "maxAgeSeconds": 3600, "responseHeader": ["Content-Type"] }]' > cors-config.json
  1. Apply it to your bucket (find the bucket name on the Storage page, e.g. your-project.firebasestorage.app):
gsutil cors set cors-config.json gs://YOUR_BUCKET_NAME
Note

Full walkthrough with screenshots of every Cloud Shell step: CORS Configuration. If Admin Panel images ever show broken after deploy, this step is the first thing to re-check (see Troubleshooting).

CORS (Cross-Origin Resource Sharing) must be configured on your Firebase Storage bucket so the Admin Panel can load parking images and uploaded documents.

1. Open the Google Cloud Console

2. Select Your Project

3. Activate Google Cloud Shell

Welcome to Cloud Shell! Type "help" to get started.
Your Cloud Platform project in this session is set to parkez-xxxxx.

4. Create a CORS Configuration File

echo '[{ "origin": ["*"], "method": ["GET"], "maxAgeSeconds": 3600, "responseHeader": ["Content-Type"] }]' > cors-config.json

5. Get Your Storage Bucket Name

6. Apply the CORS Configuration

gsutil cors set cors-config.json gs://YOUR_BUCKET_NAME
Security Note:
  • The "origin": ["*"] setting allows requests from any domain. For production, restrict it to your Admin Panel domain (e.g., https://admin.parkez.com).

All secret-key payment operations — Stripe, PayPal, Flutterwave, Paystack, Xendit, Midtrans, Mercado Pago, and PayFast — are processed server-side by Firebase Cloud Functions included in the cloud_functions/ folder of the package. The mobile apps call these functions instead of contacting the gateways directly, so the gateway secret keys never leave your server and are never shipped inside the APK / IPA. Deploying them is a one-time step — these gateways will not work until you do. (Razorpay needs no function: its checkout uses only the public key id.)

The functions deployed are: stripeCreatePaymentIntent, paypalCreateOrder, paypalAuthorizeOrder, flutterwaveInitiatePayment, flutterwaveVerifyTransaction, paystackInitializeTransaction, paystackVerifyTransaction, xenditCreateInvoice, xenditCheckInvoice, midtransCreatePaymentLink, mercadopagoCreatePreference, and payfastGetPaymentHtml. They read the gateway keys from the settings/payment Firestore document — the same one managed from Admin Panel → Settings → Payment Settings — so after deployment there is no extra configuration: whatever keys you save in the Admin Panel are used automatically.

Prerequisites

Deploy Steps

  1. Install the Firebase CLI (skip if already installed):
    npm install -g firebase-tools
  2. Sign in with the Google account that owns your Firebase project:
    firebase login
  3. Open a terminal in the cloud_functions/ folder of the package and link it to your Firebase project (pick your project when prompted and give it any alias, e.g. default):
    cd cloud_functions
    firebase use --add
  4. Install the function dependencies:
    cd functions
    npm install
    cd ..
  5. Deploy the functions and the Firestore security rules & indexes together:
    firebase deploy --only functions,firestore
  6. Open Admin Panel → Settings → Payment Settings and press Save once. This migrates your gateway secret keys into the admin-only settings/payment_secrets document — the apps can only read the sanitized settings/payment document, which contains no secrets.

When the command finishes with Deploy complete!, all functions are live. You can verify them under Firebase Console → Build → Functions. The mobile apps discover them automatically through their Firebase configuration (google-services.json / GoogleService-Info.plist) — nothing to change in the app code.

Security model

Troubleshooting the deploy
  • “Your project must be on the Blaze plan” — upgrade the project in Firebase Console → Settings → Usage and billing, then re-run the deploy.
  • Payments fail with “internal” / “not found” in the apps — the functions are not deployed, or were deployed to a different Firebase project than the apps use.
  • “Payment settings are not configured” — enter and save the gateway keys in Admin Panel → Settings → Payment Settings first.
  • To inspect runtime errors, run firebase functions:log from the cloud_functions/ folder.

The ParkEZ Admin Panel is a Flutter Web application located in /admin_panel. The sections below walk through configuring and deploying it.

Note:

The Admin Panel runs entirely on Flutter Web and talks directly to Firestore, Firebase Auth, Storage, and FCM. No backend server is required.

Update HTML Title

<head>
  ...
  <title>ParkEZ Admin</title>
  ...
</head>

Update Manifest

{
  "name": "ParkEZ Admin",
  "short_name": "ParkEZ Admin",
  ...
}

1. Install Firebase CLI Tools

npm install -g firebase-tools

2. Authenticate Firebase CLI

firebase login

3. Create a Firebase Project

firebase projects:create

OR

1 Add Firebase to your Flutter App:
  • On the Firebase Project Overview page, click the Flutter icon.
  • Firebase will show the "Add Firebase to your Flutter app" wizard with two commands.
2 Run the FlutterFire CLI commands:
$ dart pub global activate flutterfire_cli
$ flutterfire configure --project=YOUR_PROJECT_ID

This registers per-platform apps with Firebase and adds lib/firebase_options.dart to your project.

4. Initialize Firebase in the Admin Panel

dart pub global activate flutterfire_cli
flutterfire configure --project=YOUR_PROJECT_ID
Platform Selection (Admin Panel = Web only):
? Which platforms should your configuration support?
  ( ) android
  ( ) ios
  ( ) macos
  (*) web        <-- Select ONLY this
  ( ) windows

Use arrow keys and Space to select web only. Press Enter to confirm.

Update the Web Config Files (index.html & Service Worker)

flutterfire configure only generates lib/firebase_options.dart. Two web files also contain a firebaseConfig object that must point to your Firebase project (used for web push notifications):

  1. Open Firebase Console → ⚙ Project settings → General → Your apps → Web app and copy the firebaseConfig object.
  2. Open admin_panel/web/index.html, find window.firebaseConfig = { ... }, and replace the placeholder values (YOUR_FIREBASE_API_KEY, YOUR_PROJECT_ID, …) with your own.
  3. Do the same in admin_panel/web/firebase-messaging-sw.js (the push-notification service worker).
  4. Rebuild the panel: flutter build web.

1. Generate a Google Maps API Key

2. Add API Key to Flutter Web

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
Tip:

The Admin Panel uses Google Maps for the Parkings module (showing all parking locations on a single map) and for the Owners "live view" screen.

Admin Panel (Flutter Web)

The favicon represents the Admin Panel in the browser tab.

Replace Favicon:
Tip:

Use online tools like favicon.io or realfavicongenerator.net to generate the full favicon set from a single ParkEZ logo image.

ParkEZ is designed so you can rebrand the entire platform without touching any code. Log in to the Admin Panel and open SettingsApp Settings — the name, colors, and every logo/icon for all four apps are managed from this one screen. Click Save at the bottom to apply. (See the annotated App Settings screenshot.)

1. Brand Name

2. Color Scheme (per app)

App Settings has a dedicated primary color field for each app — enter a HEX code (e.g. #5D54F2) or use the color swatch, then press Change:

The new color is stored in Firebase and each app applies it on next launch — no rebuild required. Light and dark mode are handled automatically from your chosen color.

Every image is uploaded from the same screen using its Change / Upload button (PNG or JPG, square recommended):

FieldWhere it appears
FaviconBrowser tab icon
App Icon (Light) / App Icon (Dark)Mobile app logo for light & dark themes
Admin Panel LogoLogo in the admin sidebar/header
Landing Page LogoLogo shown on the marketing landing page

4. Other App Settings

Advanced — changing the built-in default colors (optional)

The values above override everything at runtime, so most buyers never need code changes. If you also want to change the fallback colors compiled into the apps (used before the panel value loads), edit primary4 in each app's lib/app/utils/app_colors.dart and rebuild.

Note:

Run all commands from inside the admin_panel/ folder.

1. Create the Build for Server Upload

flutter clean
flutter pub get
flutter build web --release
Compiling lib/main.dart for the Web...
✓ Built build\web                                          24.7s
admin_panel/build/web/
Note:

Run all commands from inside the admin_panel/ folder.

1. Build and Deploy Using Firebase Hosting

npm install -g firebase-tools
firebase login
firebase init
Follow the Firebase Init prompts:
? Are you ready to proceed? (Y/n) y

? Which Firebase features do you want to set up?
  (*) Hosting: Configure files for Firebase Hosting

=== Project Setup
? Please select an option: Use an existing project
i  Using project parkez-xxxxx

=== Hosting Setup
? What do you want to use as your public directory? build/web
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No
flutter build web --release
firebase deploy --only hosting
Expected Deployment Output:
=== Deploying to 'parkez-xxxxx'...

i  deploying hosting
i  hosting[parkez-xxxxx]: beginning deploy...
i  hosting[parkez-xxxxx]: found 489 files in build/web
+  hosting[parkez-xxxxx]: file upload complete
+  hosting[parkez-xxxxx]: version finalized
+  hosting[parkez-xxxxx]: release complete

+  Deploy complete!

Project Console: https://console.firebase.google.com/project/parkez-xxxxx/overview
Hosting URL: https://parkez-xxxxx.web.app
Success:

Your ParkEZ Admin Panel is live! Copy the Hosting URL (e.g., https://parkez-xxxxx.web.app) to access it.

After the Admin Panel build is uploaded (see Deploy on Server or Deploy on Firebase), map your domain to it and secure it with HTTPS. Follow the path that matches your hosting.

1. Domain Mapping

A. Firebase Hosting — custom domain

  1. Open Firebase ConsoleHostingAdd custom domain.
  2. Enter your domain (e.g. admin.yourdomain.com) and continue.
  3. Add the TXT record Firebase shows to your DNS to verify ownership.
  4. Then add the provided A records at your DNS provider.
  5. Wait for DNS propagation — Firebase provisions the SSL certificate automatically.
Note

Firebase Hosting includes free, auto-renewing SSL. Once the domain shows Connected, HTTPS works with no extra steps.

B. VPS / dedicated server — DNS

Type: A     Host: admin (or @)     Value: 203.0.113.10     TTL: 3600

C. cPanel / shared hosting

  1. DomainsCreate A New Domain (or Subdomain), e.g. admin.yourdomain.com.
  2. Set its Document Root to the folder where you uploaded build/web/.
  3. Upload the contents of build/web/ into that document root via File Manager or FTP.

2. SSL / HTTPS Configuration

cPanel — AutoSSL (free)

  1. Go to SecuritySSL/TLS Status.
  2. Select your domain and click Run AutoSSL — a free Let's Encrypt certificate is issued and auto-renews.

Nginx + Certbot (Let's Encrypt)

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d admin.yourdomain.com
Example Nginx server block (HTTPS redirect + SPA routing):
server {
    listen 80;
    server_name admin.yourdomain.com;
    return 301 https://$host$request_uri;   # force HTTPS
}

server {
    listen 443 ssl;
    server_name admin.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/admin.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/admin.yourdomain.com/privkey.pem;

    root /var/www/parkez-admin;   # your build/web folder
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;   # SPA fallback
    }
}

Apache + Certbot

sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d admin.yourdomain.com
.htaccess (place in the build/web root) — force HTTPS + SPA routing:
RewriteEngine On

# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# SPA fallback (all routes -> index.html)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
Auto-renewal

Certbot installs a renewal timer automatically. Verify it with sudo certbot renew --dry-run.

3. Landing Page — Custom Domain

The landing page is a lightweight static site (plain HTML/JS in landing_page/) — it can be hosted anywhere. Typical setup with the Admin Panel on a subdomain:

SiteSuggested domainWhat to upload
Landing Pageyourdomain.com (root)Contents of landing_page/
Admin Paneladmin.yourdomain.comContents of admin_panel/build/web/
  1. At your DNS provider, point the root domain to your host: an A record (@ → your server IP) for a VPS/cPanel, or the records your static host provides (Firebase Hosting, Netlify, etc.).
  2. Upload the landing_page/ files (index.html, app.js, legal.js, assets) to that site's document root. No build step is needed.
  3. Enable SSL for the root domain the same way as the Admin Panel (AutoSSL / Certbot — see the section above). No SPA rewrite is required for the landing page.
  4. Make sure the landing page's firebaseConfig (in app.js / legal.js) points to your Firebase project, and add yourdomain.com to Firebase → Authentication → Authorized domains.
  5. Its content (hero text, steps, links, logo) is edited from Admin Panel → Landing Page — changes appear without redeploying.

4. Post-Deployment Checklist

Common gotcha

A blank page after deploy is almost always a missing SPA rewrite (all routes → index.html) or a stale browser cache. Apply the rewrite rules above and hard-refresh.

The steps below apply to all three mobile apps shipped with ParkEZ: customer/, owner/, and watchman/. Repeat each step for each app folder, using a different package name / bundle ID per app.

Suggested package names:
  • Customer: com.parkez.customer
  • Owner: com.parkez.owner
  • Watchman: com.parkez.watchman

Run these steps separately for each mobile app folder (customer/, owner/, watchman/).

Android

Update the applicationId in build.gradle:
defaultConfig {
    applicationId "com.parkez.customer"
}
Rename Java Package Directories:
Update AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.parkez.customer">
</manifest>
Update Other Manifest Files:

iOS

Update PRODUCT_BUNDLE_IDENTIFIER in Xcode:
Update Bundle Identifier in Info.plist:

Android

Update AndroidManifest.xml:
<application
    android:name="${applicationName}"
    android:icon="@mipmap/ic_launcher"
    android:label="ParkEZ"
    ...>
</application>
Update strings.xml:
<string name="app_name">ParkEZ</string>

iOS

Update Info.plist:
<key>CFBundleDisplayName</key>
<string>ParkEZ</string>
Update Display Name in Xcode:
Note:

Repeat these steps once per app folder (customer/, owner/, watchman/) so each gets its own lib/firebase_options.dart while pointing to the same Firebase project.

1. Install Firebase CLI Tools

npm install -g firebase-tools

2. Authenticate Firebase CLI

firebase login

3. Configure FlutterFire

On the Firebase Project Overview page, click the Flutter icon. The wizard shows two commands:
$ dart pub global activate flutterfire_cli
$ flutterfire configure --project=YOUR_PROJECT_ID

Run both inside the app folder. Then click NextContinue to console.

4. Initialize Firebase in Each App

cd customer
dart pub global activate flutterfire_cli
flutterfire configure --project=YOUR_PROJECT_ID
Platform Selection (Mobile apps = Android & iOS):
? Which platforms should your configuration support?
  (*) android    <-- Select this
  (*) ios        <-- Select this
  ( ) macos
  ( ) web
  ( ) windows

Use arrow keys and Space to select android and ios. Press Enter.

1. Create a Firebase Project

2. Add Each App to Firebase

Android Setup (repeat for Customer / Owner / Watchman):
iOS Setup (repeat for Customer / Owner / Watchman):

3. Add Firebase Packages

flutter pub get

4. Configure DefaultFirebaseOptions

flutterfire configure

5. Initialize Firebase in main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

1. Generate a Google Maps API Key

2. Add API Key - Android (each app)

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="YOUR_API_KEY"/>

3. Add API Key - iOS (each app)

import GoogleMaps
GMSServices.provideAPIKey("YOUR_API_KEY")
Security Tip:
  • Restrict your API key in the Google Cloud Console to specific apps (using each app's package name / bundle ID) and the specific APIs they need.

SHA keys are required for Firebase services like Google Sign-In and Phone Authentication on Android. Generate and register keys for each of the three mobile apps (customer, owner, watchman).

1. Generate SHA Keys

For Windows (Debug Keystore):
keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
For macOS/Linux (Debug Keystore):
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

2. Add SHA Keys to Firebase Console

🔎 Navigation Path: Firebase Console → ⚙ → Project Settings → General → Your apps → Android app → Add Fingerprint
Important Notes:
  • For release builds, use your custom release keystore (not the default debug one).
  • Keep your keystore file secure and never expose it publicly.
  • You must add new SHA keys if you change your signing configuration or keystore.

A JKS (Java KeyStore) holds the private key that signs Android release builds. Google Play requires every APK/AAB to be signed with the same key for every update — create it once for each ParkEZ app and keep it safe.

Note:

Uses keytool from the JDK (bundled with Android Studio / Flutter). Run from your terminal.

Repeat these steps for every Android app you ship: customer/, owner/, watchman/. The Admin Panel is web-only and doesn't need a JKS.

1. Generate the Keystore with keytool

Run this in the terminal to create the JKS for Android signing only. Replace (Project_name) with the app name (e.g. parkez-customer) — the file lands in the current folder.

keytool -genkey -v -keystore (Project_name).jks -keyalg RSA -keysize 2048 -validity 10000 -alias (Project_name)

2. Answer the Prompts

Example prompts:
Enter keystore password:  ********
Re-enter new password:    ********
What is your first and last name?
  [Unknown]:  Your Name
What is the name of your organizational unit?
  [Unknown]:  ParkEZ
...
Is CN=Your Name, OU=ParkEZ, ... correct?
  [no]:  yes

Enter key password for <(Project_name)>
        (RETURN if same as keystore password):

3. Move the Keystore into Your Project

Important:
  • Add the keystore and passwords to .gitignorenever commit them.

4. Create a key.properties File

# android/key.properties
storePassword=YOUR_KEYSTORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=(Project_name)
storeFile=(Project_name).jks

5. Reference the Keystore in build.gradle

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

6. Build the Signed Release

flutter build apk --release
# or
flutter build appbundle --release
Important Notes:
  • Back up the JKS and passwords. Lose them and you can't ship updates to Play.
  • Use the same keystore for every release of a given app; a new key = a new app identity.
  • Keep the keystore and key.properties out of Git.
  • Each ParkEZ app (customer / owner / watchman) needs its own keystore — they are separate apps on Play.
Note:

Run all commands from inside the specific app folder (customer/, owner/, or watchman/).

1. Generate APK (for direct installation)

flutter clean
flutter pub get
flutter build apk --release
build/app/outputs/flutter-apk/app-release.apk

2. Generate AAB (for Google Play Store)

flutter clean
flutter pub get
flutter build appbundle --release
build/app/outputs/bundle/release/app-release.aab
APK vs AAB:
  • APK: Universal package for direct installation or testing.
  • AAB: Optimized format required by Google Play Store.

Using Command-Line Interface (CLI)

Open Terminal and navigate to the specific app:
cd path/to/ParkEZ/customer   # or owner, watchman, admin_panel
Run the Flutter App:
flutter run

Using Android Studio

Using Visual Studio Code

Useful Flutter Commands:
  • flutter devices — List all connected devices.
  • flutter run -d chrome — Run the Admin Panel in Chrome.
  • flutter run -d <device_id> — Run on a specific device.
  • flutter run --release — Run in release mode.

Everything needed to run and publish the Customer, Owner, and Watchman apps on iOS. Repeat the steps for each app folder. A Mac with Xcode 15+ and an Apple Developer account ($99/year) are required.

1. Prerequisites & CocoaPods

  1. Install CocoaPods: sudo gem install cocoapods.
  2. The Podfile platform is already set to platform :ios, '15.0' (required by the current Maps and Firebase pods).
  3. In each app: cd ios && pod install && cd ... Always open ios/Runner.xcworkspace (not .xcodeproj).

2. Bundle ID & Signing Team

  1. Open ios/Runner.xcworkspace in Xcode → select RunnerSigning & Capabilities.
  2. Set your own Bundle Identifier per app (e.g. com.yourbrand.customer / .owner / .watchman) and select your Team.
  3. Register the same three App IDs in the Apple Developer portal.

3. Capabilities (already wired in code)

Each app ships with ios/Runner/Runner.entitlements pre-configured for Push Notifications (aps-environment) and Sign in with Apple. You only need to enable both capabilities on each App ID in the Apple Developer portal, then refresh provisioning profiles in Xcode.

4. Firebase & Google Sign-In Files

  1. Download each app's GoogleService-Info.plist from Firebase and replace the placeholder file in ios/Runner/.
  2. In ios/Runner/Info.plist, set the CFBundleURLSchemes entry to your file's REVERSED_CLIENT_ID — Google Sign-In will not return to the app without it.
  3. Customer & Owner only: put your Maps key in ios/Runner/AppDelegate.swiftGMSServices.provideAPIKey("…").

5. Info.plist Permissions

Usage descriptions are already included per app — camera & photo library (all apps), location (Customer, Owner, Watchman), plus background remote-notification mode. Edit the strings in Info.plist if you want your own wording; App Review reads them.

6. APNS for Push Notifications

  1. Developer portal → Keys → create an APNs Auth Key (.p8) and download it once.
  2. Firebase → Project Settings → Cloud Messaging → Apple app configuration → upload the .p8 with its Key ID and Team ID (for each iOS app).
  3. Test on a real device — the iOS Simulator cannot receive push notifications.

7. Build & Archive

flutter build ipa --release

Or archive from Xcode: Product → Archive. When distributing, you may see “Upload Symbols Failed — dSYM for RazorpayStandard.framework” — this is a harmless Razorpay limitation; untick “Upload your app's symbols” in the distribute dialog (see Troubleshooting).

8. TestFlight & App Store

  1. Create the three apps in App Store Connect with matching bundle ids.
  2. Upload the build (Xcode Organizer or flutter build ipa + Transporter), add internal testers in TestFlight.
  3. For review: provide demo logins, screenshots, and privacy details (camera, photos, location, notifications usage).

ParkEZ uses Firebase Cloud Messaging (FCM) to deliver booking, check-in, and admin notifications across all four apps.

1. Get Notification Sender ID

🔎 Navigation Path: ⚙ → Project Settings → Cloud Messaging → Sender ID

2. Get Web Client ID (for Google Sign-In)

3. Get Firebase Service Account JSON File

4. Add All Keys in the ParkEZ Admin Panel

Admin Panel → Settings → Notification Settings:
Field What to Enter
Notification Sender Key The Sender ID from the Cloud Messaging tab
Client ID for Google Login The Web client ID from Authentication → Google
Upload JSON File The Service Account JSON downloaded from Firebase
Also in General Settings:

You can also enter your Google Maps Key and platform-wide settings (currency, commission %, support phone/email) under Settings → General Settings in the Admin Panel.

The Landing Page is a standalone marketing site under landing_page/. Edit all copy and images from the admin panel (Settings → Landing Page). Content loads from Firestore at runtime — no redeploy needed.

Folder Structure

landing_page/
  index.html              Main marketing page
  privacy-policy.html     Privacy Policy (reads settings/constant.privacyPolicy)
  terms.html              Terms & Conditions (reads settings/constant.termsAndConditions)
  app.js                  Hydrates index.html from Firestore
  legal.js                Hydrates both legal pages from Firestore
  styles.css              Shared stylesheet (indigo / lavender theme)
  assets/                 Logo, illustrations, step images and feature SVGs
  README.md               Quick start notes

1. Update Firebase Credentials

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID"
};

2. Get Firebase Credentials

No Web App shown?

Click "Add app" → the Web icon (</>) → enter a nickname (e.g. parkez-landing) → "Register app". The firebaseConfig will appear.

3. Edit Content from Admin Panel

Open the admin panel → Settings → Landing Page. You can edit:

Click Save. Changes show on the next page load — no deploy needed.

4. Privacy Policy & Terms & Conditions

privacy-policy.html and terms.html read from settings/constant — the same document the mobile apps use. Edit them in the admin's Settings → Policy Settings screen.

One source of truth — the apps and website always show the same legal text.

5. Running Locally

Two ways to preview the page:

  1. Double-click index.html. The browser opens it via file://. The compat SDK still loads. If your browser blocks network requests from file://, the page falls back to built-in copy.
  2. Serve it over HTTP (recommended):
    cd landing_page
    python -m http.server 8000
    # or
    npx serve .
    Then open http://localhost:8000/.

Open DevTools → Console. A [ParkEZ] Loaded document log lists every field that was read — use it to verify your Firestore data loaded.

6. Firestore Read Rules

The landing page only reads from Firestore, so add these rules so visitors can see it without signing in:

match /settings/landing_page { allow read: if true; }
match /settings/constant     { allow read: if true; }

Writes are still gated behind the Admin Panel's auth-required rules — the landing page never writes anything.

7. Deploying to Production

It's a plain static site. Deploy to any static host:

Option A — Firebase Hosting (recommended)
npm install -g firebase-tools
cd landing_page
firebase login
firebase init hosting
# - Use existing project → your Firebase project
# - Public directory: .  (this current folder)
# - Configure as SPA? → No
# - Set up automatic builds? → No
# - Overwrite index.html? → No

firebase deploy --only hosting

You'll get a live URL like https://your-project.web.app.

Option B — Any other static host

Upload the whole landing_page/ folder to Netlify, Vercel, GitHub Pages, AWS S3 + CloudFront, or your own server. No build step needed.

8. Troubleshooting

Footer or contact info shows default values instead of mine
Image uploads show "Upload failed"
Logo looks too small or duplicated in the header
Setup Complete: Firebase config → Admin content → Deploy. The landing page is live and admin-driven.

Multi-language support spans the whole system. The Admin Panel is where you register languages, and the mobile apps (Customer, Owner, Watchman) show their UI in whichever language the user picks. It uses GetX and has three pieces that must stay in sync:

Important — how the two halves connect:

Adding a language in the Admin Panel only adds it to the list. The strings aren't downloaded from Firestore. If you add code de in the panel but the app has no matching app_de.dart, selecting it shows raw English keys. Both steps are required.

1. Add a Language in the Admin Panel

Admin Panel → Settings → Languages → + Add Language:
Field What to Enter Example
Language name Display name shown in the app's language list English / हिंदी / العربية / Français / Português
Code ISO language code — must match exactly the key in the app's strings file en / hi / ar / fr / pt
Active Whether users can see and pick this language true / false
Default Fallback for first-time users (only one allowed) true / false
// Firestore: languages/{autoId}
{
  "id":              "LzSABjMohyW3MA0CaxVH",
  "name":            "English",
  "code":            "en",
  "active":          true,
  "defaultLanguage": true
}
🔎 Navigation Path: Admin Panel → Settings → Languages → + Add Language → enter Name + Code + Active + Default → Save

2. App-Side Files You Need

Every app keeps its translations under a lang/ folder and registers them in LocalizationService. The structure is the same across all apps:

customer/   (also owner/, watchman/)
  lib/
    lang/
      app_en.dart                  English strings     -> const Map enUS
      app_hi.dart                  Hindi strings       -> const Map hiIN
      app_ar.dart                  Arabic strings      -> const Map lnAr
      app_fr.dart                  French strings      -> const Map frFR
      app_pt.dart                  Portuguese strings  -> const Map ptBR
    services/
      localization_service.dart    Registers the maps with GetX

admin_panel/  (note the different path)
  lib/
    app/
      lang/
        app_en.dart, app_ar.dart, app_hi.dart, app_fr.dart, app_pt.dart
      services/
        localization_service.dart

3. How Strings Are Stored in Those Files

Each file is a Dart const Map<String, String> of key → translated value. The key is the English text; the value is the translation.

// lib/lang/app_en.dart
const Map<String, String> enUS = {
  "Skip"           : "Skip",
  "Welcome"        : "Welcome",
  "Home"           : "Home",
  "Bookings"       : "Bookings",
  "Wallet"         : "Wallet",
  "Profile"        : "Profile",
  "Search a place or address" : "Search a place or address",
  "Book This Spot" : "Book This Spot",
  "Spot Reserved"  : "Spot Reserved",
  // ... hundreds more key-value pairs
};
// lib/lang/app_ar.dart  -> SAME keys, translated values
const Map<String, String> lnAr = {
  "Skip"           : "تخطي",
  "Welcome"        : "أهلاً",
  "Home"           : "الرئيسية",
  "Bookings"       : "الحجوزات",
  "Wallet"         : "المحفظة",
  "Profile"        : "الملف الشخصي",
  "Book This Spot" : "احجز هذا الموقع",
  // ... same keys, Arabic translations
};
Golden rule — keep the keys identical:

Every language map must use the same keys. The key is what the UI looks up. If a language is missing a key, GetX shows the raw key text for that one string. Add new keys to every app_xx.dart file.

Variable naming convention across all apps:

LanguageCodeVariableFile
EnglishenenUSapp_en.dart
HindihihiINapp_hi.dart
ArabicarlnArapp_ar.dart
FrenchfrfrFRapp_fr.dart
PortugueseptptBRapp_pt.dart
SpanishesesESapp_es.dart

4. Register the Maps in LocalizationService

lib/services/localization_service.dart extends GetX's Translations class and maps each code to its strings map. The code here must match the Code from the Admin Panel (Step 1).

// lib/services/localization_service.dart
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import '../lang/app_en.dart';
import '../lang/app_hi.dart';
import '../lang/app_ar.dart';
import '../lang/app_fr.dart';
import '../lang/app_pt.dart';

class LocalizationService extends Translations {
  static const locale = Locale('en', 'US');      // default locale
  static final locales = [
    const Locale('en'),
    const Locale('hi'),
    const Locale('ar'),
    const Locale('fr'),
    const Locale('pt'),
  ];

  @override
  Map<String, Map<String, String>> get keys => {
    'en': enUS,   // from app_en.dart
    'hi': hiIN,   // from app_hi.dart
    'ar': lnAr,   // from app_ar.dart
    'fr': frFR,   // from app_fr.dart
    'pt': ptBR,   // from app_pt.dart
  };

  void changeLocale(String lang) {
    Get.updateLocale(Locale(lang));  // switches the active language at runtime
  }
}

This file exists in all four apps:

5. Wire It Into the App (main.dart)

In each app's main.dart, pass the translations and default/fallback locale to GetMaterialApp:

GetMaterialApp(
  translations: LocalizationService(),         // the maps from Step 4
  locale: LocalizationService.locale,          // Locale('en', 'US')
  fallbackLocale: LocalizationService.locale,  // used if a key/locale is missing
  // ...
);

6. Using a String in the UI

Call .tr on the key anywhere. GetX looks it up in the active language map and returns the translation.

// Simple string
Text("Home".tr)
AppButton(text: "Book This Spot".tr)

// Parameterized string -> .trParams replaces @placeholders
Text("spots_open".trParams({ "count": "12" }))
// active locale = ar  ->  resolves against lnAr map

7. How the User Selects a Language

// 1) switch the active language immediately
LocalizationService().changeLocale(selectedLanguage.code);   // Get.updateLocale(...)

// 2) remember the choice for next launch (SharedPreferences)
Preferences.setString(
  Preferences.languageCodeKey,        // key: "languageCodeKey"
  jsonEncode(selectedLanguage.toJson()),  // value: full LanguageModel as JSON
);
// SplashScreenController
Future<void> redirectScreen() async {
  final savedLang = Preferences.getString(Preferences.languageCodeKey);
  if (savedLang == "null" || savedLang.isEmpty) {
    Get.offAll(() => const LanguageScreenView(isFirstTime: true));
    return;
  } else {
    final lang = LanguageModel.fromJson(jsonDecode(savedLang));
    LocalizationService().changeLocale(lang.code ?? "en");
  }
  // ... continue to main screen
}

Controller & preference file locations:

AppLanguage Screen ControllerPreferences File
customer customer/lib/app/modules/language_screen/controllers/language_screen_controller.dart customer/lib/utils/preferences.dart
owner owner/lib/app/modules/language_screen/controllers/language_screen_controller.dart owner/lib/utils/preferences.dart
watchman watchman/lib/app/modules/language_screen/controllers/language_screen_controller.dart watchman/lib/utils/preferences.dart
admin_panel admin_panel/lib/app/modules/language/controllers/language_controller.dart admin_panel/lib/app/services/shared_preferences/app_preference.dart

The Complete Flow (Admin → App UI)

End-to-end:
  1. Admin adds a language (Name + Code + Active + Default) under Admin Panel → Settings → Languages → saved to the languages collection.
  2. Developer creates lib/lang/app_<code>.dart in each app with the translated key/value map (same keys as English). For admin_panel: lib/app/lang/app_<code>.dart.
  3. Developer registers that map in localization_service.dart under the matching code.
  4. App starts → splash screen checks SharedPreferences → if no saved language, redirects to Language screen → fetches the language list from Firestore → the new language now appears.
  5. User selects it → Get.updateLocale() switches the active map → choice saved to SharedPreferences.
  6. Every "key".tr / .trParams() in the UI instantly resolves against the new language map → the whole app re-renders translated.
Quick checklist when adding a new language:

☐ Add the language (Name + Code) in Admin Panel → Settings → Languages.
☐ Create app_<code>.dart in all four apps (admin_panel, customer, owner, watchman) with every key translated.
☐ Import it + add the Locale and keys entry in each app's localization_service.dart.
☐ Make sure the Code in the panel === the code key in LocalizationService (e.g. fr).
☐ Remember: admin_panel uses lib/app/lang/, all others use lib/lang/.
☐ Rebuild and run — the new language appears and translates the UI.

This chapter collects everything you can customize in ParkEZ in one place — what can be changed from the Admin Panel with no code at all, and what is changed in the source with a simple find-and-edit. Each topic links to its detailed step-by-step section.

1. App Name, Package ID, Logo & Icons

What to changeHow
App display name (Android & iOS)Changing Application Nameandroid:label, strings.xml, CFBundleDisplayName.
Admin Panel name & browser tab titleChanging Application Name (Admin Panel) and Admin Panel → Settings → App Settings → Brand Name.
Package name / bundle id (rebranding)Changing Package Name in FlutterapplicationId + PRODUCT_BUNDLE_IDENTIFIER. Afterwards re-download google-services.json (see Troubleshooting).
App logo, launcher icons & splashChanging Application Logo — Android mipmaps, iOS AppIcon set, and assets/images/logo.png per app.
Admin Panel favicon & web iconsChange the Faviconfavicon.png + web/icons/.
Landing page brand, hero & linksAdmin Panel → Landing Page (CMS — no rebuild needed) — see Landing Page Setup.

2. Color Schemes

3. Fonts

All apps use the bundled Figtree font family. To swap in your own font:

  1. Copy your .ttf files into assets/fonts/ in each app (keep one file per weight: Regular, Medium, SemiBold, Bold…).
  2. In pubspec.yaml, update the fonts: block — each family: entry points to one asset file. Keep the family names or rename them consistently.
  3. If you renamed the families, update the constants in lib/app/utils/font_family.dart (the single place the font names are referenced).
  4. Run flutter pub get and rebuild.

4. Core Configuration Settings

White-label in under a day

A full rebrand — name, package id, logo, colors, font, currency — touches only the files listed above and requires no code restructuring.

A visual reference for every screen across the ParkEZ apps — these are actual, high-quality screenshots captured from the running Customer, Owner, Watchman, and Admin applications. Click any image to view it full-screen.

Customer App

Onboarding & Sign In

Onboarding 1
Onboarding 1
Onboarding 2
Onboarding 2
Onboarding 3
Onboarding 3
Sign Up
Sign Up
Login
Login
Language
Language

Find & Book

Home / Map
Home / Map
Search Results
Search Results
Parking List
Parking List
Parking Details
Parking Details
Select Date & Time
Select Date & Time
Select Slot
Select Slot
Select Vehicle
Select Vehicle
Payment Options
Payment Options
Booking Success
Booking Success

Bookings & Tickets

My Bookings
My Bookings
E-Ticket
E-Ticket

Penalties

Penalty Notice
Penalty Notice
Pay Penalty
Pay Penalty

Wallet & Account

Wallet
Wallet
Notifications
Notifications
Profile
Profile
Edit Profile
Edit Profile

Owner App

Onboarding & Sign In

Onboarding 1
Onboarding 1
Onboarding 2
Onboarding 2
Onboarding 3
Onboarding 3
Sign Up
Sign Up
Login
Login
Language
Language

Parking Management

Home / Dashboard
Home / Dashboard
My Parkings
My Parkings
Parking Details
Parking Details
Add Parking (1)
Add Parking (1)
Add Parking (2)
Add Parking (2)
Add Parking (3)
Add Parking (3)
Slot Management
Slot Management

Team & Documents

Add Watchman
Add Watchman
Watchmen
Watchmen
Upload Document
Upload Document
Document Approval
Document Approval

Bookings & Verification

Bookings
Bookings
Booking Details
Booking Details
QR Verify (Scan)
QR Verify (Scan)
Booking Action (1)
Booking Action (1)
Booking Action (2)
Booking Action (2)
Booking Action (3)
Booking Action (3)
Booking Action (4)
Booking Action (4)
Booking Action (5)
Booking Action (5)
Booking Action (6)
Booking Action (6)

Wallet & Account

Wallet / Earnings
Wallet / Earnings
Wallet — Filter
Wallet — Filter
Notifications
Notifications
Profile
Profile

Watchman App

Onboarding & Sign In

Onboarding 1
Onboarding 1
Onboarding 2
Onboarding 2
Onboarding 3
Onboarding 3
Login
Login
Language
Language

Operations

Bookings / Scan QR
Bookings / Scan QR
Parking Slots
Parking Slots
QR Verify (Scan)
QR Verify (Scan)
Booking View
Booking View
Booking Action (1)
Booking Action (1)
Booking Action (2)
Booking Action (2)
Booking Action (3)
Booking Action (3)
Booking Action (4)
Booking Action (4)
Booking Action (5)
Booking Action (5)
Booking Action (6)
Booking Action (6)

Parking Details

Parking Details
Parking Details
Facilities & Reviews
Facilities & Reviews

Account

Notifications
Notifications
Profile
Profile

Admin Panel

Overview

Dashboard
Dashboard

Management

Customers
Customers
Owners
Owners
Owner Verification
Owner Verification
Owner Documents
Owner Documents
Roles & Permissions
Roles & Permissions
Parking Bookings
Parking Bookings

Content

Onboarding Screens
Onboarding Screens
Landing Page
Landing Page

Admin Panel Settings — Annotated Screenshots

Every configuration screen in the Admin Panel, with a numbered legend explaining each control. The numbers read top-to-bottom against the screenshot.

App Settings

App Settings
  1. Country Code — default phone country code applied to new users.
  2. Default Currency — currency used across bookings, wallet and payouts.
  3. App Name — the name displayed throughout all apps.
  4. Browser Tab Title — title shown on the admin browser tab.
  5. Favicon & App Icons (Light / Dark) — upload branding images, then Save.

Map Settings

Map Settings
  1. Google Map Key — API key that powers maps in the Customer & Owner apps.
  2. Map Type — choose the map provider / style (e.g. Google Map).
  3. Save — pushes the map config to all apps.

Payment Settings

Payment Settings
  1. Cash On Delivery — enable/disable and set its display name.
  2. Wallet — enable in-app wallet payments.
  3. Stripe / Razorpay / PayPal — toggle each gateway, switch Sandbox vs Live, and enter API keys.
  4. Save — publishes the enabled payment options to the apps.

Notification Settings

Notification Settings
  1. Notification Sender Key — FCM server/sender key used for push delivery.
  2. Client Id For Google Login — OAuth client id used for Google Sign-In.
  3. Upload JSON File — Firebase service-account JSON for server-side notifications.

SMTP / Email Settings

SMTP / Email Settings
  1. SMTP Host & Port — your mail server address and port.
  2. User Name & Password — SMTP credentials (app password recommended).
  3. Encryption Type — SSL / TLS.
  4. Save — enables transactional email sending.

Email Templates

Email Templates
  1. Template list — Booking Cancelled, Completed, Confirmation, and more (cannot be deleted).
  2. Subject & body — edit the content sent to users per template.
  3. Merge variables — {{name}}, {{booking_id}}, {{amount}}… injected into the message.
  4. Toggle / preview / edit — enable a template or open the editor.

Currency

Currency
  1. Currency rows — Name, Symbol, Code and Decimals.
  2. Symbol At Right — place the symbol before or after the amount.
  3. Status — enable/disable a currency.
  4. + Add Currency / edit / delete.

Tax

Tax
  1. Tax rows — Name and Amount (percentage).
  2. Status — enable/disable each tax.
  3. + Add Tax / edit / delete — applied at checkout.

Policy Settings

Policy Settings
  1. Tabs — About App, Privacy Policy, Terms & Conditions.
  2. Rich-text editor — format the content shown inside the apps.
  3. Save — publishes the policy content to all apps.

Broadcast Notifications

Broadcast Notifications
  1. Filter / search — by type or by title / message.
  2. History table — Type, target Users, Title, Message, Sent By / At.
  3. Resend — re-push a previous notification.
  4. + Send Notification — compose and push to an audience (Customers / Owners / Watchmen).

ParkEZ is serverless: there is no custom REST API to host. All four apps talk directly to Firebase — Firestore for data, Firebase Auth for identity, Storage for images and Cloud Messaging for push. This section explains how the backend is structured and how to extend it.

Architecture Overview

Customer / Owner / Watchman (Flutter)          Admin Panel (Flutter Web)
        |            |            |                       |
        +------------+------------+-----------+-----------+
                                   |
                          Firebase Auth  (identity: email, phone, Google, Apple)
                          Cloud Firestore (all application data)
                          Firebase Storage (profile images, parking photos, documents)
                          Cloud Messaging  (push notifications)

Firestore Collections

CollectionPurpose
customers / owners / watchmanUser profiles per role (FCM token, wallet balance, status).
parkingParking lots — location, price, floors, facilities, owner id.
slotsIndividual slots per parking / floor with availability.
bookingsReservations — customer, parking, slot, times, amount, status.
wallet_transaction / transaction_logWallet top-ups, payments and refunds.
review_customerRatings and review text per parking.
coupons / country_tax / currenciesPricing helpers managed from the Admin Panel.
settingsGlobal runtime config read by every app at startup (see Configuration Guide).
notification / broadcast_notificationsPer-user and admin-broadcast notifications.
email_templateTransactional email templates edited in the Admin Panel.
onboarding_screen / languages / parking_facilitiesApp content managed from the Admin Panel.
admin / role_permissions / documentsAdmin users, role-based access and owner KYC documents.

Data-Access Layer

Adding a New Field or Collection

  1. Add the field to the model class (lib/app/models/…) in every app that uses it — include it in fromJson/toJson with a safe default.
  2. Add read/write methods to fire_store_utils.dart (copy an existing method as a template).
  3. If you add a new collection, register its name in collection_name.dart in each app.
  4. If admins must manage it, add a module under admin_panel/lib/app/modules/ (view + controller, GetX pattern).
  5. If queries filter on multiple fields, add a composite index (Firestore will print a direct index-creation link in the console error).

Push Notification Flow

  1. On login/signup each app stores its FCM token on the user document (fcmToken).
  2. When an event happens (new booking, wallet credit), the initiating app sends an FCM message to the receiver's token using the server credentials configured in the Admin Panel → Notification Settings.
  3. lib/app/utils/notifications/notification_service.dart handles permission, foreground display and tap-routing in each app.
Security Rules

Firestore and Storage rules ship with the package (see Storage Setup). If you add collections, extend the rules too — data is protected by rules, not by the API keys.

Flutter has no .env file by default — configuration lives in a few well-defined places. This table is the single reference for every value you must supply and exactly where it goes.

Build-Time Configuration (files in the source)

ValueWhere to put it
Firebase config (all apps)lib/firebase_options.dart — generated by flutterfire configure. Never edit by hand.
Firebase Android configandroid/app/google-services.json (download from Firebase Console per app).
Firebase iOS configios/Runner/GoogleService-Info.plist (download per app).
Google Maps key (Android)android/app/src/main/AndroidManifest.xmlcom.google.android.geo.API_KEY meta-data (customer & owner).
Google Maps key (iOS)ios/Runner/AppDelegate.swiftGMSServices.provideAPIKey("…") (customer & owner).
Google Sign-In redirect (iOS)ios/Runner/Info.plistCFBundleURLSchemes = the REVERSED_CLIENT_ID from GoogleService-Info.plist.
Release signing (Android)android/key.properties + your .jks keystore — see Generate JKS File.
Admin/landing web Firebase configadmin_panel/web/index.html, admin_panel/web/firebase-messaging-sw.js, landing_page/app.js + legal.js (the firebaseConfig object).

Runtime Configuration (Firestore settings collection, managed in Admin Panel)

SettingAdmin Panel screen
App name, default currency, country code, brandingSettings → App Settings
Google Maps key used at runtime + map typeSettings → Map Settings
Payment gateways (Stripe / Razorpay / PayPal keys, sandbox toggle)Settings → Payment Settings
FCM sender key + Google Login client id + service-account JSONSettings → Notification Settings
SMTP host / port / credentials for transactional emailSettings → SMTP / Email
Taxes, currencies, email templates, policiesSettings → respective screens

See the annotated screenshots of every settings screen for a field-by-field walkthrough.

Key safety

Restrict your Google Maps key (by package name / bundle id / HTTP referrer) in Google Cloud Console, and protect data with Firestore Security Rules. Firebase web API keys are identifiers, not secrets — but payment gateway secret keys must only ever be entered in the Admin Panel, never committed to source.

Cloud Functions (payments, wallets & security)

All secret-key payment operations and every wallet/balance mutation run in Firebase Cloud Functions — the secret keys and wallet writes never happen on a device. The functions read the gateway keys from the admin-only settings/payment_secrets Firestore document, which the Admin Panel maintains automatically: whatever you save in Admin Panel → Settings → Payment Settings is picked up by the functions, while the apps only see the sanitized settings/payment document. See Cloud Functions for the one-time deployment steps — online payments will not work until the functions and security rules are deployed.

Solutions to common issues you might hit while setting up ParkEZ.

Flutter Issues

"flutter" is not recognized as a command
flutter doctor shows issues

GetX / Routing Issues

"GetX controller not found" exception at runtime

Firebase Issues

Firebase CLI not found
Permission denied during firebase deploy
Firestore "Missing or insufficient permissions" / [cloud_firestore/permission-denied]
"No matching client found for package name" (Android build fails)
"flutterfire: command not found"
export PATH="$PATH":"$HOME/.pub-cache/bin"
Firestore "The query requires an index" / [cloud_firestore/failed-precondition]

Google Maps Issues

Map shows grey / blank

Dependency & iOS Issues

Error: intl 0.20.2 is required / version solving failed

flutter_localizations pins intl to an exact version. In pubspec.yaml set intl: 0.20.2 (no caret), then run flutter pub get.

Error: [firebase_messaging/apns-token-not-set] on iOS

The APNS token arrives asynchronously after launch. Ensure Push Notifications capability is enabled on the App ID, an APNS key (.p8) is uploaded to Firebase → Cloud Messaging, and test on a real device — the iOS Simulator cannot receive an APNS token.

Warning: Upload Symbols Failed — dSYM for RazorpayStandard.framework

Razorpay ships closed-source binaries without dSYMs; the App Store Connect warning is harmless. Untick “Upload your app's symbols” in the Xcode distribute dialog (or set uploadSymbols: false in ExportOptions.plist) to silence it.

Error: CocoaPods could not find compatible versions

In ios/ run pod repo update then pod install. Ensure the Podfile platform is platform :ios, '15.0' (required by current Maps/Firebase pods).

Build Issues

Gradle errors on Android
iOS build fails
Admin Panel images do not load

Authentication Issues

Google Sign-In fails / immediately closes (Android)
Google / Apple Sign-In fails on iOS
Phone OTP not received

No Data / Empty Screens

Apps open but show no parkings, settings, or content

Notification Issues

Push notifications not received

Payment Gateway Issues

Payment fails or gateway not showing

Admin Panel Deployment Issues

Blank white page after deploying the Admin Panel
Admin login fails / "no admin found"

Localization Issues

A new language does not appear or does not translate
Still stuck?

Run the app with flutter run --verbose and read the first red error line — it almost always names the exact file, key, or permission at fault. For anything unresolved, contact support with that log (see Support).

Q: Do I really need four separate apps?

Yes. ParkEZ is intentionally split so each role (Customer, Owner, Watchman, Admin) ships as a focused product, can be branded / priced independently, and can be released to stores on its own schedule. All four share one Firebase backend.

Q: Do I need a Mac to build the iOS apps?

Yes. Xcode is required for building iOS apps and runs only on macOS. You can develop and test the Android and Web (Admin Panel) versions on Windows or Linux without a Mac.

Q: Which Firebase plan do I need?

The free Spark plan is enough to get started. If you plan to use Phone OTP authentication heavily or expect large Storage usage, upgrade to the Blaze plan (pay-as-you-go); Blaze still includes a generous free usage tier.

Q: Can I use my own backend instead of Firebase?

ParkEZ is built around Firebase (Firestore, Auth, Storage, and FCM). Switching to a custom backend is possible but requires significant changes — mainly replacing the repository / service layer in each app.

Q: How do I update the apps after making changes?

Rebuild the modified app with flutter build apk, flutter build appbundle, or flutter build web and redeploy / upload to the relevant store or hosting target.

Q: An app crashes on startup. What should I check?
Q: How are commissions calculated?

The Admin Panel → SettingsGeneral Settings screen lets you set a platform commission percent. The Customer and Owner apps read this value when settling each booking and split the amount between the platform wallet and the owner wallet via secure Firestore transactions.

Q: How do owners get paid?

Owners accumulate balance in the in-app wallet. They can submit a Withdraw Request from the Owner App; admins approve / reject it from the Admin Panel, then mark it as paid after the bank transfer.

Q: What ongoing / external costs should I expect?

The item price covers the source code only. Firebase and Google Maps Platform are external Google services billed to your own account once you exceed their free tiers; payment gateways charge per-transaction fees; and app store accounts (Google Play one-time, Apple Developer yearly) are separate. See the cost warning in Prerequisites.

Q: Can I rebrand / white-label the apps?

Yes. Changing the app name, package / bundle id, logo, colours, and default currency is covered step-by-step in Mobile Apps Setup and Admin Panel → App Settings. No code restructuring is required.

Q: How do I add another language?

Add a new language map file in each app's lib/app/lang/, register it, and enable the language from the Admin Panel. The full walkthrough is in Localization (Multi-Language).

Q: Which payment methods are supported out of the box?

Stripe, Razorpay, PayPal, in-app Wallet, and Cash on Delivery. Each gateway is toggled and configured (keys, sandbox/live) from Admin Panel → Payment Settings — no rebuild needed.

Q: Is the source code readable and customisable?

Yes — the package ships full, unobfuscated Dart source for all four apps using a consistent GetX module structure (view / controller / binding per screen), a central data layer, and typed models. See the Developer Guide for how to extend it.

Q: How do I contact support?

Refer to the support details provided with your ParkEZ purchase (email / WhatsApp on the product page).

Want to pull future ParkEZ releases into your customised project without losing your changes? Keep the project in Git and merge each release in as a branch. Below are the prep notes and the limits of our support.

Note
  1. Back up the project before any update — merges can't be undone for you.
  2. Run the procedure separately for each app folder: customer/, owner/, watchman/, admin_panel/, landing_page/.
  3. Don't change package name, bundle ID, or version metadata during the merge — they're tied to your Play / App Store builds and breaking them blocks in-place updates.
Warning

Manual upgrades need solid Flutter and Firebase experience. Back up and test in a staging Firebase project first. We aren't liable for damage from manual upgrades — when stuck, check the official Flutter and Firebase docs.

Note — Support Scope

Per Envato policy, installation, customisation, and upgrade help aren't covered by free item support — only bugs in the delivered code are.

The steps below are informational, to help you self-serve.

Don't open support tickets for upgrade issues — they're out of scope.

Upgrade via GitHub Desktop — Step by Step

GitHub Desktop lets you keep your customisations on a private branch and merge each release in. Follow the steps in order.

Step 1 — Download and install GitHub Desktop

  1. Open https://github.com/apps/desktop and download the installer for your OS (Windows or macOS).
  2. Run the installer and complete the standard setup.
  3. Launch GitHub Desktop.
  4. GitHub Desktop installed and launched

Step 2 — Sign in to GitHub

  1. On first launch GitHub Desktop prompts you to sign in. Click Sign in to GitHub.com.
  2. Complete the browser-based sign-in flow and return to GitHub Desktop. Confirm your name and email on the configure-git screen and click Finish.
  3. GitHub Desktop sign-in screen

Step 3 — Open the “Add” menu

  1. Click the Current repository dropdown at the top-left.
  2. In the panel that opens, click the Add dropdown at the top-right.
  3. Current repository dropdown with Add menu open

Step 4 — Create a new repository

  1. From the Add dropdown, choose Create new repository…
  2. Create new repository option

Step 5 — Fill in the repository form

  1. Name: e.g. parkez-customised.
  2. Local path: browse to the folder that contains your existing ParkEZ project.
  3. Leave the other options at their defaults and click Create repository.
  4. Create a new repository form filled in

Step 6 — Commit your existing project as the baseline

  1. GitHub Desktop lists every file in your project under Changes.
  2. Type a commit summary — for example Initial commit — my customised v1.0 build.
  3. Click Commit to main.
  4. Initial commit on main

Step 7 — Publish the repository to GitHub

  1. Click Publish repository at the top of the window.
  2. Tick Keep this code private.
  3. Click Publish Repository.
  4. Publish repository dialog with private ticked

Step 8 — Create a v1.1 branch

  1. Click Current branchNew branch.
  2. Name it v1.1 and click Create branch.
  3. Confirm you are now on the v1.1 branch (shown in the top bar).
  4. Create branch dialog with v1.1

Step 9 — Download the latest ParkEZ release

1
Open CodeCanyon Downloads

Go to your CodeCanyon Downloads page and find the ParkEZ item.

2
Download & extract v1.1

Pull the ParkEZ v1.1 package and unzip it to a temporary folder — do not extract it over your existing project.

Step 10 — Replace the code folders on the v1.1 branch

  1. Make sure the v1.1 branch is still active in GitHub Desktop.
  2. From the extracted v1.1 package, copy these folders into your project, replacing the existing ones:
    • customer/lib/
    • owner/lib/
    • watchman/lib/
    • admin_panel/lib/
    • landing_page/ (only if you haven't customised it)
  3. Leave everything else untouched — branding, Firebase config files, signing keystore, and platform projects.
  4. Replacing lib folders from v1.1 package

Step 11 — Commit the v1.1 changes

  1. Switch back to GitHub Desktop — the updated files appear under Changes.
  2. Enter the commit summary Upgrade to v1.1.
  3. Click Commit to v1.1.
  4. Commit v1.1 changes

Step 12 — Push the v1.1 branch to GitHub

  1. Click Push origin at the top of the window.
  2. Wait for the push to complete.
  3. Push origin to upload v1.1 branch

Step 13 — Merge v1.1 into main

  1. Switch Current branch back to main.
  2. From the top menu choose BranchMerge into current branch…
  3. Select v1.1 and click Create a merge commit.
  4. Merge v1.1 into main

Step 14 — Resolve conflicts (if any)

  1. If GitHub Desktop reports conflicts, click Open in your editor.
  2. In each conflicted file, look for the markers <<<<<<<, =======, >>>>>>> and keep the correct lines.
  3. Save each file and return to GitHub Desktop — the conflict count drops as files are resolved.
  4. When the count reaches zero, click Continue merge, then Push origin.
  5. Resolve merge conflicts

Step 15 — Apply the changes to your real project

1
Review the merge

Open GitHub Desktop's History tab and walk through each commit from the v1.1 merge.

2
Compare side-by-side

For every change, open the file in your editor next to the matching file in your real project.

3
Copy & paste

Apply each change to your real project, one at a time, keeping the customisations you already had.

Manual Upgrade — Replace the lib Folder

To skip Git, replace each app's lib/ folder with the one from the new release. Faster, but any custom code inside lib/ is lost — diff before overwriting.

Important note

Do not overwrite firebase_options.dart. Keep your existing Firebase config.

  1. Back up the whole project (zip it).
  2. Download the latest package from CodeCanyon Downloads and extract it to a temp folder — not over your project.
  3. Locate the lib/ folder in each app:
    • customer/lib/
    • owner/lib/
    • watchman/lib/
    • admin_panel/lib/
  4. Back up your existing lib/firebase_options.dart, then delete the old lib/.
  5. Copy the new lib/ from the package into the same spot.
  6. Restore your lib/firebase_options.dart. flutterfire configure generates this against your Firebase project; the package ships a sample that would point you at the wrong project.
  7. Re-apply any other custom edits you made inside lib/ on top of the new files.
  8. Compare each pubspec.yaml with the new release and add any new dependencies.
  9. Refresh deps per app:
    flutter clean
    flutter pub get
    On macOS, also run pod install --repo-update inside customer/ios (and the other iOS app folders).
  10. Build and run each app to verify.
Warning

This overwrites every file under lib/. Custom changes are lost unless re-applied. If you've heavily customised lib/, use the GitHub Desktop method above — it merges instead of replacing.

Item Support — What's Covered

Support follows the Envato Item Support Policy:

CoveredNot covered
Bugs and defects in the delivered code Installation / server / store-publishing services
Questions about how a shipped feature works Customisation or adding new features
Help locating the right setting or file Issues caused by modified code or third-party plugins

Reporting a Bug — What to Include

  1. The app (Customer / Owner / Watchman / Admin) and screen where it happens.
  2. Steps to reproduce, expected vs actual behaviour.
  3. Platform + version (Android / iOS / browser) and Flutter version (flutter --version).
  4. The console output — run with flutter run --verbose and copy the error.
  5. Screenshots or a short screen recording if visual.

License

Credits

Version 1.0 Latest
  • Initial release of the ParkEZ ecosystem — Customer, Owner, Watchman, and Admin Panel.