OviFlo Docs
Flutter + Laravel

Documentation

Everything you need to install, configure, and customise your OviFlo period tracking application.

Product Overview

OviFlo is a Laravel + Flutter period tracking, fertility, mood, symptom, and wellness product. Everything you need to run a branded cycle-tracking app is included.

Laravel Backend

REST API, Sanctum auth, admin dashboard, public landing page, and a web installer. No command line required.

Flutter App

Native Android (iOS-ready) app with Health Connect, AdMob, biometric lock, cycle predictions, and Google Play Billing.

Authentication

Email/password and Google Sign-In via Firebase. Password reset by email. Sanctum token management.

Billing & Ads

Google Play subscription verification with Play Integrity. AdMob with remote test/live switching from the admin panel.

What's Included

  • Laravel API backend with Sanctum authentication
  • Tailwind-based admin dashboard
  • Public landing page with admin-editable content
  • Web installer (no SSH or command line required)
  • Flutter mobile app (Android + iOS-ready source)
  • App configuration API for ads, site content, and managed pages
  • Google Play and Apple App Store billing verification endpoints
  • English and Spanish localisation

Server Requirements

OviFlo requires a standard PHP hosting environment. Most cPanel/Plesk shared hosts meet these requirements.

RequirementMinimum
PHP8.2 or higher
MySQL / MariaDBMySQL 5.7+ or MariaDB 10.3+
Composer2.x
StorageWritable storage/, bootstrap/cache/, public/uploads/

Required PHP Extensions

BCMath Ctype cURL DOM Fileinfo JSON Mbstring OpenSSL PDO PDO MySQL Tokenizer XML ZIP
Shared hosting note: The backend sets Laravel's default string column length to 191 in AppServiceProvider. This prevents migration failures on older MySQL/cPanel servers where indexed VARCHAR(255) columns can exceed the key limit.

Laravel Installation

OviFlo ships with a browser-based installer. No SSH or command line access is required for the initial setup.

1
Upload backend files
Upload the Laravel backend folder contents to your server using FTP, SFTP, or cPanel File Manager.
2
Set document root
Point the domain's document root to the public folder, not the project root.
3
Create MySQL database
Create an empty MySQL database and note the host, name, username, and password.
4
Visit /install
Open https://yourdomain.com/install in your browser.
5
Complete the installer
Walk through the requirements check, database setup, app configuration, and admin account creation.

What the Installer Does

  • Writes .env with your database and app settings
  • Generates APP_KEY
  • Runs all database migrations
  • Creates the first admin account
  • Seeds default site settings
  • Creates required upload and storage folders
  • Creates .installed to lock the installer
Do not delete .installed on a live server. It prevents the installer from running again and wiping your database.

Demo Credentials

After running the demo seeder, these accounts are available for review:

RoleEmailPassword
Adminadmin@demo.comdemo1234
User (Sofia)sofia@demo.comdemo1234
User (Mei)mei@demo.comdemo1234

Admin Dashboard

After installation, log in to the admin panel at:

https://yourdomain.com/admin/login

Use the email and password you chose during the installer's admin setup step.

Available Sections

Dashboard
Overview stats: users, cycles, logs
Users
View, search, and manage user accounts
Cycles
Browse all tracked cycles
Daily Logs
Mood, symptom, and wellness entries
Reminders
User notification settings
Pregnancies
Pregnancy tracking entries
Home Page
Edit hero content, CTA, and features
Pages
About, Privacy Policy, Terms of Service
Ad Settings
AdMob IDs and test/live toggle

Public Site

The public-facing website is hosted at your Laravel domain root. It includes:

  • / — Landing page (content editable from Admin → Home Page)
  • /about — About page (editable from Admin → Pages)
  • /privacy-policy — Privacy policy
  • /terms — Terms of service

API Configuration

On startup the Flutter app fetches configuration from a public endpoint:

GET /api/app-config

This returns ad settings, site home content, logo/icon URLs, and managed page slugs. The app caches this response so it works offline after the first load.

A valid JSON response at https://yourdomain.com/api/app-config confirms the backend is reachable from the Flutter app. Use it as a connectivity test.

Connect Flutter App to Backend

Before building the app, verify the backend is reachable by visiting these two URLs in a browser:

https://yourdomain.com/
https://yourdomain.com/api/app-config

/api/app-config must return JSON. A valid response confirms the API is live and accessible.

Use the Laravel root domain as the API base URL. Do not include /api or /api/v1. The app derives those paths automatically.
✓ Correct
https://yourdomain.com
✗ Incorrect
https://yourdomain.com/api
https://yourdomain.com/api/v1

Build With Your Backend URL

APK (for direct install / testing):

cd oviflo_app

flutter build apk --release \
  --dart-define=API_BASE_URL="https://yourdomain.com"

App Bundle (for Play Store upload):

cd oviflo_app

flutter build appbundle --release \
  --dart-define=API_BASE_URL="https://yourdomain.com"

The app automatically derives all sub-paths:

PathDerived from root
API v1https://yourdomain.com/api/v1
App confighttps://yourdomain.com/api/app-config
Billinghttps://yourdomain.com/api/v1/billing/google/verify

Permanent Source Default

If you prefer not to pass --dart-define every time, edit the default directly in source:

oviflo_app/lib/core/constants/app_constants.dart

Change the defaultValue:

static const String baseUrl = String.fromEnvironment(
  'API_BASE_URL',
  defaultValue: 'https://yourdomain.com', // ← set your domain here
);

Then build normally without --dart-define.

App Setup

The Flutter project is in the oviflo_app/ folder. Each buyer should customise the app identity before building release apps.

Change App Name

OviFlo includes rename_app for changing the visible app name across all platforms:

cd oviflo_app
flutter pub get
dart run rename_app:main all="My Cycle App"

Platform-specific rename:

dart run rename_app:main android="My Cycle App" ios="My Cycle App"

Change Android Package Name

cd oviflo_app
flutter pub get
dart run change_app_package_name:main com.company.mycycle

After changing the package name:

  • Replace android/app/google-services.json with one generated for the new package in Firebase Console.
  • Update any Play Console package references.
  • Update lib/core/constants/app_constants.dart default googlePlayPackage if needed.

Change iOS Bundle Identifier

  1. Open oviflo_app/ios/Runner.xcworkspace in Xcode.
  2. Select Runner in the project navigator.
  3. Open Signing & Capabilities.
  4. Change Bundle Identifier to e.g. com.company.mycycle.
  5. Replace ios/Runner/GoogleService-Info.plist with the Firebase iOS file for the new identifier.

Change App Icon

  1. Replace oviflo_app/assets/images/app_icon.png with your icon (1024×1024 PNG, no transparent background for iOS, centred mark with padding).
  2. Run the icon generator:
cd oviflo_app
dart run flutter_launcher_icons

Runtime Build Overrides

The app supports --dart-define for multi-customer builds. All common identifiers can be overridden without editing source:

flutter build appbundle --release \
  --dart-define=APP_NAME="My Cycle App" \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=ANDROID_PACKAGE="com.company.mycycle" \
  --dart-define=GOOGLE_SERVER_CLIENT_ID="your-web-client-id.apps.googleusercontent.com" \
  --dart-define=PREMIUM_MONTHLY_ID="mycycle_premium_monthly" \
  --dart-define=PREMIUM_ANNUAL_ID="mycycle_premium_annual" \
  --dart-define=ADMOB_APP_ID="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" \
  --dart-define=PLAY_INTEGRITY_PROJECT_NUMBER="123456789012"
Use rename_app, change_app_package_name, and flutter_launcher_icons for native package/name/icon changes. Use --dart-define for build-specific runtime values.

App Name vs Admin Site Name

There are two separate concepts buyers often confuse:

WhatHow to change
Flutter app name (on device home screen)Use rename_app or --dart-define=APP_NAME
Website / backend brand nameAfter Laravel install: Admin → Home Page

Flutter Requirements

Minimum environment for building OviFlo:

RequirementMinimumNotes
Flutter SDK3.32.0Check with flutter --version
Dart SDK3.10.7Bundled with Flutter
Java17Required for Android builds; set JAVA_HOME
Android minSdk26 (Android 8.0)Required for Health Connect
Android targetSdk35
Android minSdk 26 is required because OviFlo integrates with Health Connect, which only supports Android 8.0 and above.
flutter --version

Local Development

To run the Flutter app against a local Laravel backend during development:

1. Start the Laravel Dev Server

php artisan serve

This listens on http://127.0.0.1:8000 by default.

2. Run Flutter Against the Local Server

On Android Emulator (uses 10.0.2.2 as alias for the host machine's 127.0.0.1):

cd oviflo_app
flutter run --dart-define=API_BASE_URL="http://10.0.2.2:8000"

On a physical device connected to the same network (replace with your machine's local IP):

flutter run --dart-define=API_BASE_URL="http://192.168.1.10:8000"
The local dev server uses http://. The production server must use https://. SSL is enforced by the app in production builds.

Authentication

OviFlo supports two independent sign-in methods. Either works without the other.

Email and Password

Email/password uses the Laravel API directly with no Firebase dependency. This works in any market, including regions without Google Play:

EndpointAction
POST /api/v1/registerCreate account
POST /api/v1/loginSign in, returns Sanctum token
POST /api/v1/logoutRevoke token (requires auth)
POST /api/v1/forgot-passwordSend reset link by email
POST /api/v1/reset-passwordReset password with link token
DELETE /api/v1/accountDelete account and all data
Password reset emails require the MAIL_* settings in .env to be configured with a working SMTP server.

Google Sign-In

Google Sign-In requires Firebase Authentication. The full flow:

1
User taps "Sign in with Google" in the app
2
Firebase verifies the Google identity on-device
3
App sends the Firebase ID token to Laravel
4
POST /api/v1/auth/firebase
5
Laravel creates or logs in the matching user
6
Sanctum token returned to app

Google Sign-In will fail if:

  • google-services.json has not been replaced with the buyer's own Firebase project file
  • The app's SHA-1 fingerprint has not been added to Firebase Console
  • FIREBASE_PROJECT_ID has not been set in the Laravel .env

Firebase Setup

Firebase is required only for Google Sign-In. Email/password login works without it. You must create your own Firebase project before publishing. Do not ship production apps using the placeholder Firebase config included in the package.

1. Create a Firebase Project

  1. Open console.firebase.google.com
  2. Click Add project and enter the app name
  3. Analytics is optional. Enable only if you plan to use Firebase/Google Analytics.
  4. Finish project creation

2. Enable Google Authentication

  1. Open the Firebase project
  2. Go to Build → Authentication → Get started
  3. Open Sign-in method
  4. Enable Google, set a public support email, and save

3. Register the Android App

Change the Flutter package name first, then in Firebase:

cd oviflo_app
dart run change_app_package_name:main com.company.mycycle
  1. Go to Project settings → Your apps → Android icon
  2. Enter the Android package name (must match applicationId in build.gradle.kts exactly)
  3. Add SHA fingerprints (see step 4 below)
  4. Register the app

4. Add SHA Fingerprints

Firebase needs Android signing certificate fingerprints so Google Sign-In trusts requests from the installed app. Add both SHA-1 and SHA-256.

For debug builds:

cd oviflo_app/android
./gradlew signingReport

Copy SHA-1 and SHA-256 for the debug variant into Firebase Console.

For release builds, use the release keystore:

keytool -list -v \
  -keystore /path/to/release-key.jks \
  -alias your_key_alias
If using Google Play App Signing, also add the fingerprints from Play Console → Setup → App integrity → App signing key certificate.

5. Download Firebase Config Files

Download from Firebase and replace the placeholder files:

PlatformFile to replace
Androidoviflo_app/android/app/google-services.json
iOSoviflo_app/ios/Runner/GoogleService-Info.plist

After changing fingerprints, always download a fresh google-services.json.

6. Regenerate Flutter Firebase Options

npm install -g firebase-tools
dart pub global activate flutterfire_cli
firebase login

cd oviflo_app
flutterfire configure

This regenerates oviflo_app/lib/firebase_options.dart.

7. Set the Google Server Client ID

Find the Web OAuth client ID in:

  • Firebase Console → Project settings → General → Your apps → Web client ID
  • Or Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client IDs → Web client
flutter build appbundle --release \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=GOOGLE_SERVER_CLIENT_ID="your-web-client-id.apps.googleusercontent.com"
Use the Web OAuth client ID, not the Android client ID. Using the Android client ID is a common mistake that causes Google Sign-In to fail silently.

8. Backend Config

Set FIREBASE_PROJECT_ID in the Laravel .env to your Firebase project ID (found in Firebase Console → Project settings → General):

FIREBASE_PROJECT_ID=your-firebase-project-id

The Laravel API endpoint that handles the Firebase token is already included:

POST /api/v1/auth/firebase

Google Play Billing

OviFlo uses Flutter's in_app_purchase package to query Play products, open the purchase sheet, listen for updates, and restore purchases. Server-side verification prevents fraud.

Default Product IDs

PlanDefault ID
Monthlyoviflo_premium_monthly
Annualoviflo_premium_annual

These must match Play Console exactly (case-sensitive). Override at build time:

flutter build appbundle --release \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=PREMIUM_MONTHLY_ID="buyer_premium_monthly" \
  --dart-define=PREMIUM_ANNUAL_ID="buyer_premium_annual"

1. Create Subscription Products in Play Console

Go to Monetize with Play → Products → Subscriptions and create both products. Each subscription needs an active base plan. If you want the app's "14-day free trial" text to be accurate, configure a trial offer on the annual subscription.

2. Google Play Developer API Credentials

Laravel needs a service account to verify purchases server-side:

  1. Open Google Cloud Console and create a service account
  2. Download a JSON key for that service account
  3. Go to Play Console → Setup → API access
  4. Link the Google Cloud project
  5. Grant the service account access with minimum financial/subscription permissions
  6. Store the JSON file outside the public web root (e.g. /home/user/secure/)
  7. Add to Laravel .env:
GOOGLE_PLAY_PACKAGE_NAME=com.company.mycycle
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON=/home/user/secure/google-play-service-account.json
GOOGLE_PLAY_PREMIUM_PRODUCT_IDS=buyer_premium_monthly,buyer_premium_annual

3. Play Integrity Setup

OviFlo sends a Play Integrity token with purchase verification requests to reduce fraud.

  1. Enable the Play Integrity API in Google Cloud Console
  2. Go to Play Console → Setup → App integrity → Play Integrity API
  3. Link the Google Cloud project and copy the project number
flutter build appbundle --release \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=ANDROID_PACKAGE="com.company.mycycle" \
  --dart-define=PLAY_INTEGRITY_PROJECT_NUMBER="123456789012"

4. Add License Testers

To test paid subscriptions without real charges:

  • Add tester Gmail accounts in Play Console → Settings → License testing
  • Also add those emails to Testing → Internal testing → Testers
  • The tester must install the app from the Play Store testing link (not sideloaded)

Verification Request

The Flutter app posts completed purchases to:

POST /api/v1/billing/google/verify
{
  "product_id": "oviflo_premium_monthly",
  "purchase_token": "google-play-purchase-token",
  "package_name": "com.company.mycycle"
}

On success, the response includes premium_until and an updated user object.

Common Failure Causes

Product not found
Product ID mismatch, inactive subscription, or app installed from sideload instead of Play testing track.
Purchase succeeds but Premium not active
Laravel verification endpoint missing, unreachable, or returning an error. Check laravel.log.
Integrity token fails
Wrong Cloud project number, or Play Integrity API not linked in Play Console.
Trial text is wrong
Annual subscription offer/trial was not configured in Play Console.

AdMob Ads Setup

Ad settings are managed entirely from the admin panel. No code changes are needed to switch between test and live mode.

How Ad Configuration Works

On startup, the Flutter app fetches ad configuration from /api/app-config. This returns the current ad settings including test_mode, enabled placements, and ad unit IDs. The app caches this response locally so it works offline after the first load.

Test Mode (default)

Uses Google's official test ad unit IDs. No real ads are served and no revenue is generated. Keep enabled during development.

Live Mode

Uses your real AdMob ad unit IDs. Enable only after the app is published and reviewed.

1. Create an AdMob Account

  1. Go to admob.google.com
  2. Create an Android app in AdMob
  3. Copy the AdMob App ID (format: ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX)
  4. Create 4 ad units: Banner, Interstitial, Rewarded, Native

2. Replace the AdMob App ID

The AdMob App ID must be set in two places:

Flutter source: Edit lib/core/constants/app_constants.dart and replace the default value:

static const String admobAppId = String.fromEnvironment(
  'ADMOB_APP_ID',
  defaultValue: 'ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX', // ← replace
);

Or pass at build time (no source edit needed):

flutter build apk --release \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=ADMOB_APP_ID="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX"

Android manifest: In android/app/build.gradle.kts, update the fallback value:

val admobId = System.getProperty("ADMOB_APP_ID")
    ?: project.findProperty("ADMOB_APP_ID") as String?
    ?: "ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" // ← replace this fallback

3. Replace Ad Unit IDs

Pass all ad unit IDs at build time:

flutter build apk --release \
  --dart-define=API_BASE_URL="https://yourdomain.com" \
  --dart-define=ADMOB_APP_ID="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" \
  --dart-define=ADMOB_BANNER_ID="ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX" \
  --dart-define=ADMOB_INTERSTITIAL_ID="ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX" \
  --dart-define=ADMOB_REWARDED_ID="ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX" \
  --dart-define=ADMOB_NATIVE_ID="ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX"

4. Enable Live Ads

  1. Log in to the admin panel at /admin/login
  2. Go to Ad Settings
  3. Enter your live ad unit IDs
  4. Turn off Test Mode
  5. Save. The Flutter app picks up the change on next launch.
The AdMob App ID format uses a tilde: ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX. Ad unit IDs use a slash: ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX. Mixing these up causes an SDK crash on launch.

Health Connect Setup

OviFlo reads sleep, steps, heart rate, and activity data from Android Health Connect.

Requirements

  • Android 9.0 (API 28) or higher
  • Health Connect app installed (pre-installed on Android 14+; available on the Play Store for older versions)
  • User must grant permissions inside the Health Connect app

Permissions Requested

READ_HEART_RATE READ_RESTING_HEART_RATE READ_SLEEP READ_STEPS READ_ACTIVE_CALORIES_BURNED READ_EXERCISE

Health data is read-only, stored locally on the device, and never sent to the backend or third parties.

How It Works

The first time a user taps a Health Connect feature, the app checks if Health Connect is installed. If not, it shows a prompt to install from the Play Store. Once installed, the user is taken to the Health Connect permission screen for the specific permissions OviFlo requests.

Testing

On Android Emulator (API 34+), Health Connect is pre-installed. On older emulators, install the Health Connect APK from the Play Store. Use the Health Connect app to manually enter test data and it will appear in OviFlo within seconds.

The health_permissions.xml rationale file at android/app/src/main/res/xml/health_permissions.xml is already included and wired into AndroidManifest.xml. No changes needed unless you add custom permissions.

Release Keystore

You must create your own Android release keystore before publishing. Do not publish apps signed with the placeholder key.properties included in the package.

1. Generate a Keystore

keytool -genkey -v \
  -keystore /path/to/your-release-key.jks \
  -alias your_key_alias \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000

You will be prompted for a password, name, and organisation details. Store this file securely. If you lose it you cannot update the app on the Play Store.

2. Update key.properties

Edit oviflo_app/android/key.properties:

storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=your_key_alias
storeFile=/absolute/path/to/your-release-key.jks
Keep key.properties out of version control. It is listed in .gitignore by default. Never commit your keystore password.

3. Add SHA-1 to Firebase

Release builds require the release keystore SHA-1 in Firebase Console for Google Sign-In to work.

keytool -list -v \
  -keystore /path/to/your-release-key.jks \
  -alias your_key_alias

Copy the SHA1 value and add it in Firebase Console → Project settings → Your apps → Android app → SHA certificate fingerprints.

Download a fresh google-services.json after adding the fingerprint.

Splash Screen Customisation

OviFlo has two splash layers that together cover the app startup experience.

Layer 1: Native Android Frame

Shown immediately when the app launches, before Flutter loads.

File: oviflo_app/android/app/src/main/res/drawable/launch_background.xml

To change the background colour, edit:

oviflo_app/android/app/src/main/res/values/colors.xml
<color name="app_background">#EAE2FF</color>

To add a centred logo on the native frame, add a <bitmap> item inside launch_background.xml:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/app_background"/>
    <item>
        <bitmap
            android:gravity="center"
            android:src="@drawable/splash_logo"/>
    </item>
</layer-list>

Then place your splash_logo.png in res/drawable/. Repeat for drawable-v21/launch_background.xml.

Layer 2: Flutter Splash Screen

Shown after the Flutter engine loads. Contains the animated logo, app name, and tagline.

File: oviflo_app/lib/features/onboarding/screens/splash_screen.dart

Edit this file to change the logo widget, tagline, or colours shown during the Flutter initialisation phase.

The app name and tagline shown in the Flutter splash screen come from AppConstants.appName and AppConstants.tagline, which are controlled by --dart-define=APP_NAME at build time.

Localisation

OviFlo ships with English and Spanish translations. The app automatically uses the device language if it matches a supported locale, falling back to English otherwise.

Translation Files

FileLanguage
oviflo_app/lib/l10n/app_en.arbEnglish (template)
oviflo_app/lib/l10n/app_es.arbSpanish

Adding a New Language

  1. Copy app_en.arb to app_XX.arb (where XX is the BCP 47 language code, e.g. fr for French)
  2. Translate the string values. Do not change the keys.
  3. Add the new locale to supportedLocales in lib/main.dart:
supportedLocales: const [
  Locale('en'),
  Locale('es'),
  Locale('fr'), // ← add your language here
],
  • Regenerate the localisation classes:
  • cd oviflo_app
    flutter gen-l10n
    The app name and tagline are not in the ARB files. They come from AppConstants.appName and AppConstants.tagline, controlled via --dart-define at build time.

    Post-Install Notes

    After the web installer completes, work through this checklist before going live:

    • Log in to /admin/login with the credentials you set during installation
    • Update Home Page content: hero text, features, CTA button, logo
    • Update About, Privacy Policy, and Terms of Service pages
    • Configure Ad Settings with your AdMob IDs (or leave test mode on)
    • Set FIREBASE_PROJECT_ID in .env if using Google Sign-In
    • Set Google Play billing credentials in .env if using Premium subscriptions
    • Configure MAIL_* settings in .env for password reset emails
    • Build the Flutter app with --dart-define=API_BASE_URL pointing to your domain
    • Test the API connectivity: visit https://yourdomain.com/api/app-config and confirm JSON response
    • Test sign in, sign up, and the main tracking features in the app

    Troubleshooting

    500 Error After Upload

    Cause: Missing PHP extensions, incorrect file permissions, or wrong document root.

    Fix:
    1. Confirm the domain document root points to the public folder, not the project root.
    2. Check that all required PHP extensions are enabled (see Server Requirements).
    3. Set folder permissions: chmod -R 755 storage bootstrap/cache
    4. Check storage/logs/laravel.log for the specific error message.
    Database Connection Failed During Install

    Cause: Wrong DB credentials, MySQL not running, or database does not exist.

    Fix:
    1. Create an empty MySQL database before running the installer.
    2. Confirm the DB host, port, name, username, and password are correct.
    3. On shared hosting, the DB host is usually 127.0.0.1 or localhost.
    4. Confirm MySQL is running: mysql -u your_user -p
    Storage Permissions Error

    Cause: The storage and bootstrap/cache folders are not writable.

    Fix:
    1. chmod -R 775 storage bootstrap/cache
    2. chown -R www-data:www-data storage bootstrap/cache (Linux servers)
    3. On shared hosting, 755 is usually sufficient. Contact your host if errors persist.
    Admin Cannot Log In

    Cause: Wrong credentials or the installer did not create the admin account.

    Fix:
    1. The admin email and password are what you entered during installation step 4.
    2. If forgotten, reset via Artisan: php artisan tinker then \App\Models\User::where('email','your@email.com')->update(['password' => bcrypt('newpassword')]);
    3. Confirm the user has role = admin in the users table.
    Flutter App Cannot Connect to API

    Cause: SSL issue, wrong APP_URL, API_BASE_URL mismatch, or CORS.

    Fix:
    1. SSL: The app requires https://. Visit https://yourdomain.com/api/app-config in a browser and confirm it returns JSON.
    2. APP_URL: In Laravel .env, confirm APP_URL=https://yourdomain.com exactly matches your domain.
    3. API_BASE_URL: Confirm the Flutter build used the correct root domain with no trailing slash.
    4. CORS: If the API rejects requests from the app, check config/cors.php.
    5. Clear app data: After changing the API URL, clear app data on device (Settings → Apps → OviFlo → Clear Data).
    Google Sign-In Fails

    Cause: SHA-1 mismatch, wrong OAuth client ID, or Firebase not configured.

    Fix:
    1. Confirm google-services.json is from your own Firebase project (not the placeholder).
    2. Confirm the Android package name in Firebase matches the app's applicationId in build.gradle.kts.
    3. Add the debug SHA-1 for testing and the release SHA-1 for production to Firebase Console.
    4. Confirm FIREBASE_PROJECT_ID is set correctly in Laravel .env.
    5. Confirm GOOGLE_SERVER_CLIENT_ID is the Web OAuth client ID, not the Android client ID.
    6. After changing any Firebase settings, download a fresh google-services.json and rebuild.
    Billing Product Not Found

    Cause: Product ID mismatch, inactive subscription, or app not installed from Play testing track.

    Fix:
    1. Confirm the product IDs in Play Console exactly match the Flutter build defines.
    2. Confirm the subscriptions are active in Play Console (not draft).
    3. The app must be installed from a Play Console testing track, not sideloaded.
    4. The tester Gmail account must be added to Play Console → Settings → License testing.
    AdMob SDK Crash on Launch

    Cause: Wrong or missing AdMob App ID in the Android manifest.

    Fix:
    1. Confirm ADMOB_APP_ID in build.gradle.kts matches your AdMob App ID.
    2. The AdMob App ID format uses a tilde: ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX. Do not use a slash.
    3. If passing via --dart-define, confirm the value is being picked up.
    Health Connect Permissions Not Working

    Cause: Health Connect not installed, wrong Android version, or missing manifest entries.

    Fix:
    1. Health Connect requires Android 9.0 (API 28) or higher.
    2. On Android 13 and below, Health Connect must be installed separately from the Play Store.
    3. Confirm all uses-permission declarations for Health Connect are in AndroidManifest.xml.
    4. Confirm health_permissions.xml exists at res/xml/health_permissions.xml.
    5. After granting permissions in Health Connect, restart the app.
    Purchase Succeeds but Premium Is Not Activated

    Cause: The Laravel billing verification endpoint is not reachable or returning an error.

    Fix:
    1. Confirm POST /api/v1/billing/google/verify is accessible.
    2. Confirm GOOGLE_PLAY_SERVICE_ACCOUNT_JSON in .env points to a valid, readable service account JSON file.
    3. Confirm GOOGLE_PLAY_PACKAGE_NAME matches the app's published package name.
    4. Confirm GOOGLE_PLAY_PREMIUM_PRODUCT_IDS contains the correct product IDs (comma-separated).
    5. Check storage/logs/laravel.log for the specific verification error.

    Support

    Need Help?

    For support after purchase, contact us via our Envato profile page. Please include your purchase code and a clear description of the issue.

    Response time: 1–2 business days
    Support channel: Envato profile page

    What to Include in Your Request

    • Your Envato purchase code
    • A description of the issue and steps to reproduce it
    • PHP version, Laravel version, and hosting environment
    • Any relevant error messages from storage/logs/laravel.log
    • Flutter and Dart versions (flutter --version)

    Changelog

    v1.0.0 Initial Release
    • Laravel API backend with Sanctum authentication
    • Email/password and Google Sign-In (Firebase) authentication
    • Password reset via email
    • Cycle, daily log, reminder, and pregnancy tracking API
    • Cycle predictions and insights endpoints
    • Google Play billing verification with Play Integrity support
    • Apple App Store billing verification endpoint
    • Web installer (no command line required)
    • Tailwind admin dashboard: Users, Cycles, Daily Logs, Reminders, Pregnancies, Home Page, Pages, Ad Settings
    • Public landing page with admin-editable content
    • Privacy Policy and Terms of Service pages (admin-editable)
    • AdMob integration with remote test/live mode switching from admin panel
    • Flutter app: dashboard, calendar, insights, daily log, pregnancy tracker, TTC mode, perimenopause mode, sleep tracking, reminders
    • Health Connect integration (sleep, steps, heart rate, calories, exercise)
    • Biometric and PIN lock screen
    • English and Spanish localisation
    • --dart-define build overrides for API URL, AdMob IDs, product IDs, package name, and Firebase client ID
    • rename_app, change_app_package_name, and flutter_launcher_icons included for buyer customisation
    OviFlo © 2026. All rights reserved.