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.
REST API, Sanctum auth, admin dashboard, public landing page, and a web installer. No command line required.
Native Android (iOS-ready) app with Health Connect, AdMob, biometric lock, cycle predictions, and Google Play Billing.
Email/password and Google Sign-In via Firebase. Password reset by email. Sanctum token management.
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.
| Requirement | Minimum |
|---|---|
| PHP | 8.2 or higher |
| MySQL / MariaDB | MySQL 5.7+ or MariaDB 10.3+ |
| Composer | 2.x |
| Storage | Writable storage/, bootstrap/cache/, public/uploads/ |
Required PHP Extensions
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.
public folder, not the project root.https://yourdomain.com/install in your browser.What the Installer Does
- Writes
.envwith 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
.installedto lock the installer
.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:
| Role | Password | |
|---|---|---|
| Admin | admin@demo.com | demo1234 |
| User (Sofia) | sofia@demo.com | demo1234 |
| User (Mei) | mei@demo.com | demo1234 |
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
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.
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.
/api or /api/v1. The app derives those paths automatically.
https://yourdomain.com
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:
| Path | Derived from root |
|---|---|
| API v1 | https://yourdomain.com/api/v1 |
| App config | https://yourdomain.com/api/app-config |
| Billing | https://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.jsonwith one generated for the new package in Firebase Console. - Update any Play Console package references.
- Update
lib/core/constants/app_constants.dartdefaultgooglePlayPackageif needed.
Change iOS Bundle Identifier
- Open
oviflo_app/ios/Runner.xcworkspacein Xcode. - Select Runner in the project navigator.
- Open Signing & Capabilities.
- Change Bundle Identifier to e.g.
com.company.mycycle. - Replace
ios/Runner/GoogleService-Info.plistwith the Firebase iOS file for the new identifier.
Change App Icon
- Replace
oviflo_app/assets/images/app_icon.pngwith your icon (1024×1024 PNG, no transparent background for iOS, centred mark with padding). - 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"
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:
| What | How to change |
|---|---|
| Flutter app name (on device home screen) | Use rename_app or --dart-define=APP_NAME |
| Website / backend brand name | After Laravel install: Admin → Home Page |
Flutter Requirements
Minimum environment for building OviFlo:
| Requirement | Minimum | Notes |
|---|---|---|
| Flutter SDK | 3.32.0 | Check with flutter --version |
| Dart SDK | 3.10.7 | Bundled with Flutter |
| Java | 17 | Required for Android builds; set JAVA_HOME |
| Android minSdk | 26 (Android 8.0) | Required for Health Connect |
| Android targetSdk | 35 |
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"
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:
| Endpoint | Action |
|---|---|
POST /api/v1/register | Create account |
POST /api/v1/login | Sign in, returns Sanctum token |
POST /api/v1/logout | Revoke token (requires auth) |
POST /api/v1/forgot-password | Send reset link by email |
POST /api/v1/reset-password | Reset password with link token |
DELETE /api/v1/account | Delete account and all data |
MAIL_* settings in .env to be configured with a working SMTP server.Google Sign-In
Google Sign-In requires Firebase Authentication. The full flow:
Google Sign-In will fail if:
google-services.jsonhas 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_IDhas 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
- Open console.firebase.google.com
- Click Add project and enter the app name
- Analytics is optional. Enable only if you plan to use Firebase/Google Analytics.
- Finish project creation
2. Enable Google Authentication
- Open the Firebase project
- Go to Build → Authentication → Get started
- Open Sign-in method
- 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
- Go to Project settings → Your apps → Android icon
- Enter the Android package name (must match
applicationIdinbuild.gradle.ktsexactly) - Add SHA fingerprints (see step 4 below)
- 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
5. Download Firebase Config Files
Download from Firebase and replace the placeholder files:
| Platform | File to replace |
|---|---|
| Android | oviflo_app/android/app/google-services.json |
| iOS | oviflo_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"
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
| Plan | Default ID |
|---|---|
| Monthly | oviflo_premium_monthly |
| Annual | oviflo_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:
- Open Google Cloud Console and create a service account
- Download a JSON key for that service account
- Go to Play Console → Setup → API access
- Link the Google Cloud project
- Grant the service account access with minimum financial/subscription permissions
- Store the JSON file outside the public web root (e.g.
/home/user/secure/) - 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.
- Enable the Play Integrity API in Google Cloud Console
- Go to Play Console → Setup → App integrity → Play Integrity API
- 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
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.
Uses Google's official test ad unit IDs. No real ads are served and no revenue is generated. Keep enabled during development.
Uses your real AdMob ad unit IDs. Enable only after the app is published and reviewed.
1. Create an AdMob Account
- Go to admob.google.com
- Create an Android app in AdMob
- Copy the AdMob App ID (format:
ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX) - 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
- Log in to the admin panel at
/admin/login - Go to Ad Settings
- Enter your live ad unit IDs
- Turn off Test Mode
- Save. The Flutter app picks up the change on next launch.
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
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.
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
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.
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
| File | Language |
|---|---|
oviflo_app/lib/l10n/app_en.arb | English (template) |
oviflo_app/lib/l10n/app_es.arb | Spanish |
Adding a New Language
- Copy
app_en.arbtoapp_XX.arb(whereXXis the BCP 47 language code, e.g.frfor French) - Translate the string values. Do not change the keys.
- Add the new locale to
supportedLocalesinlib/main.dart:
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'), // ← add your language here
],
cd oviflo_app
flutter gen-l10n
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/loginwith 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_IDin.envif using Google Sign-In - Set Google Play billing credentials in
.envif using Premium subscriptions - Configure
MAIL_*settings in.envfor password reset emails - Build the Flutter app with
--dart-define=API_BASE_URLpointing to your domain - Test the API connectivity: visit
https://yourdomain.com/api/app-configand 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.
- Confirm the domain document root points to the
publicfolder, not the project root. - Check that all required PHP extensions are enabled (see Server Requirements).
- Set folder permissions:
chmod -R 755 storage bootstrap/cache - Check
storage/logs/laravel.logfor the specific error message.
Database Connection Failed During Install
Cause: Wrong DB credentials, MySQL not running, or database does not exist.
- Create an empty MySQL database before running the installer.
- Confirm the DB host, port, name, username, and password are correct.
- On shared hosting, the DB host is usually
127.0.0.1orlocalhost. - Confirm MySQL is running:
mysql -u your_user -p
Storage Permissions Error
Cause: The storage and bootstrap/cache folders are not writable.
chmod -R 775 storage bootstrap/cachechown -R www-data:www-data storage bootstrap/cache(Linux servers)- On shared hosting,
755is usually sufficient. Contact your host if errors persist.
Admin Cannot Log In
Cause: Wrong credentials or the installer did not create the admin account.
- The admin email and password are what you entered during installation step 4.
- If forgotten, reset via Artisan:
php artisan tinkerthen\App\Models\User::where('email','your@email.com')->update(['password' => bcrypt('newpassword')]); - Confirm the user has
role = adminin theuserstable.
Flutter App Cannot Connect to API
Cause: SSL issue, wrong APP_URL, API_BASE_URL mismatch, or CORS.
- SSL: The app requires
https://. Visithttps://yourdomain.com/api/app-configin a browser and confirm it returns JSON. - APP_URL: In Laravel
.env, confirmAPP_URL=https://yourdomain.comexactly matches your domain. - API_BASE_URL: Confirm the Flutter build used the correct root domain with no trailing slash.
- CORS: If the API rejects requests from the app, check
config/cors.php. - 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.
- Confirm
google-services.jsonis from your own Firebase project (not the placeholder). - Confirm the Android package name in Firebase matches the app's
applicationIdinbuild.gradle.kts. - Add the debug SHA-1 for testing and the release SHA-1 for production to Firebase Console.
- Confirm
FIREBASE_PROJECT_IDis set correctly in Laravel.env. - Confirm
GOOGLE_SERVER_CLIENT_IDis the Web OAuth client ID, not the Android client ID. - After changing any Firebase settings, download a fresh
google-services.jsonand rebuild.
Billing Product Not Found
Cause: Product ID mismatch, inactive subscription, or app not installed from Play testing track.
- Confirm the product IDs in Play Console exactly match the Flutter build defines.
- Confirm the subscriptions are active in Play Console (not draft).
- The app must be installed from a Play Console testing track, not sideloaded.
- 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.
- Confirm
ADMOB_APP_IDinbuild.gradle.ktsmatches your AdMob App ID. - The AdMob App ID format uses a tilde:
ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX. Do not use a slash. - 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.
- Health Connect requires Android 9.0 (API 28) or higher.
- On Android 13 and below, Health Connect must be installed separately from the Play Store.
- Confirm all
uses-permissiondeclarations for Health Connect are inAndroidManifest.xml. - Confirm
health_permissions.xmlexists atres/xml/health_permissions.xml. - 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.
- Confirm
POST /api/v1/billing/google/verifyis accessible. - Confirm
GOOGLE_PLAY_SERVICE_ACCOUNT_JSONin.envpoints to a valid, readable service account JSON file. - Confirm
GOOGLE_PLAY_PACKAGE_NAMEmatches the app's published package name. - Confirm
GOOGLE_PLAY_PREMIUM_PRODUCT_IDScontains the correct product IDs (comma-separated). - Check
storage/logs/laravel.logfor 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.
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
- 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