Overview
The MND mobile app is built with Flutter, supporting both Android and iOS platforms. This guide covers building, signing, and submitting to app stores.
Prerequisites
- Flutter SDK 3.0+
- Dart SDK 3.0+
- Android Studio (for Android builds)
- Xcode (for iOS builds, macOS only)
- Backend API running and accessible
Initial Setup
1. Install Flutter
Follow the official Flutter installation guide for your platform.
Verify installation:
2. Install Dependencies
cd mnd_flutter
flutter pub get
Key dependencies:
provider - State management
http - Networking
shared_preferences - Local storage
google_maps_flutter - Map integration
flutter_dotenv - Environment configuration
Create .env file:
API_BASE_URL=http://192.168.1.100:3000
GOOGLE_MAPS_API_KEY=your_mobile_maps_api_key
For production:
API_BASE_URL=https://api.yourdomain.com
GOOGLE_MAPS_API_KEY=your_production_maps_api_key
See Environment Variables for details.
Development Testing
Run on Emulator/Simulator
Android:
iOS:
Run on Physical Device
Android:
- Enable Developer Options and USB Debugging on your Android device
- Connect via USB
- Run:
flutter devices
flutter run -d <device-id>
iOS:
- Connect iPhone/iPad via USB
- Trust the computer on your device
- Run:
flutter run -d <device-id>
Hot Reload
While app is running:
- Press
r - Hot reload
- Press
R - Hot restart
- Press
q - Quit
Android Deployment
Edit android/app/build.gradle:
android {
defaultConfig {
applicationId "com.yourcompany.mnd_flutter"
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0.0"
}
}
Update:
applicationId - Unique app identifier (reverse domain notation)
versionCode - Integer version (increment for each release)
versionName - Display version (e.g., “1.0.0”)
Edit android/app/src/main/AndroidManifest.xml:
<application>
<!-- ... -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_ANDROID_MAPS_API_KEY" />
</application>
3. Create App Icon
Replace android/app/src/main/res/mipmap-*/ic_launcher.png with your app icons:
mipmap-mdpi/ic_launcher.png (48x48)
mipmap-hdpi/ic_launcher.png (72x72)
mipmap-xhdpi/ic_launcher.png (96x96)
mipmap-xxhdpi/ic_launcher.png (144x144)
mipmap-xxxhdpi/ic_launcher.png (192x192)
Or use the flutter_launcher_icons package:
dev_dependencies:
flutter_launcher_icons: ^0.13.1
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon/app_icon.png"
flutter pub run flutter_launcher_icons
4. Generate Signing Key
Create keystore:
keytool -genkey -v -keystore ~/mnd-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias mnd
Answer the prompts and save the password securely.
Create key.properties:
Create android/key.properties:
storePassword=your_store_password
keyPassword=your_key_password
keyAlias=mnd
storeFile=/Users/yourname/mnd-release-key.jks
Add key.properties to .gitignore - never commit this file!
Edit android/app/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
minifyEnabled true
shrinkResources true
}
}
}
6. Build APK
Standard APK:
flutter build apk --release
Output: build/app/outputs/flutter-apk/app-release.apk
Split APK by ABI (smaller size):
flutter build apk --split-per-abi --release
Creates separate APKs for:
app-armeabi-v7a-release.apk (32-bit ARM)
app-arm64-v8a-release.apk (64-bit ARM)
app-x86_64-release.apk (64-bit Intel)
7. Build App Bundle (for Play Store)
Recommended format for Google Play:
flutter build appbundle --release
Output: build/app/outputs/bundle/release/app-release.aab
Benefits:
- Smaller download size (Google Play generates optimized APKs)
- Required for apps over 150MB
- Supports Dynamic Delivery
8. Test Release Build
Install APK on device:
flutter install build/app/outputs/flutter-apk/app-release.apk
Or:
adb install build/app/outputs/flutter-apk/app-release.apk
9. Submit to Google Play Store
- Create account at Google Play Console
- Pay one-time $25 registration fee
- Create new app
- Fill in app details:
- App name
- Short description
- Full description
- Screenshots (required: at least 2)
- Feature graphic (1024x500)
- App icon (512x512)
- Privacy policy URL
- Upload AAB file
- Complete Content Rating questionnaire
- Set pricing (free/paid)
- Select countries
- Submit for review
Review time: 1-7 days typically
iOS Deployment
Edit ios/Runner/Info.plist:
<key>CFBundleIdentifier</key>
<string>com.yourcompany.mndFlutter</string>
<key>CFBundleName</key>
<string>MND</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
Edit ios/Runner/AppDelegate.swift:
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR_IOS_MAPS_API_KEY")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
3. Create App Icon
Replace icons in ios/Runner/Assets.xcassets/AppIcon.appiconset/
Or use flutter_launcher_icons (see Android section).
4. Open in Xcode
open ios/Runner.xcworkspace
Always open .xcworkspace, not .xcodeproj
In Xcode:
- Select Runner project
- Select Runner target
- Go to Signing & Capabilities
- Check Automatically manage signing
- Select your Team (requires Apple Developer account)
- Set Bundle Identifier (must match App ID)
6. Register App ID
- Go to Apple Developer Portal
- Certificates, Identifiers & Profiles
- Identifiers → Click +
- Select App IDs → Continue
- Select App → Continue
- Enter:
- Description: MND Bus Routing
- Bundle ID: com.yourcompany.mndFlutter
- Capabilities: (select as needed)
- Register
7. Build Release
Command line:
flutter build ios --release
Or in Xcode:
- Select Any iOS Device (arm64) as target
- Product → Archive
- Wait for archive to complete
- Organizer window opens
8. Distribute via Xcode
In Xcode Organizer:
- Select your archive
- Click Distribute App
- Choose distribution method:
- App Store Connect - for App Store submission
- Ad Hoc - for testing on specific devices
- Enterprise - for internal distribution
- Development - for development testing
- Follow the wizard
- Upload to App Store Connect
9. Submit to App Store
- Go to App Store Connect
- Create new app:
- Platform: iOS
- Name: MND Bus Routing
- Primary Language: English
- Bundle ID: (select registered ID)
- SKU: unique identifier
- Fill in app information:
- Subtitle
- Description
- Keywords
- Support URL
- Privacy Policy URL
- Upload screenshots:
- 6.5” display (required)
- 5.5” display (optional)
- iPad Pro (if supporting iPad)
- Select build uploaded from Xcode
- Set pricing
- Submit for review
Review time: 1-3 days typically
App Store Requirements
Screenshots
Android (Google Play):
- Minimum 2 screenshots
- Format: JPEG or 24-bit PNG
- Minimum dimension: 320px
- Maximum dimension: 3840px
- Recommended: 1080x1920 (portrait) or 1920x1080 (landscape)
iOS (App Store):
- Required for 6.5” display (iPhone 14 Pro Max)
- Optional for 5.5” and iPad Pro
- Format: JPEG or PNG
- Sizes:
- 6.5”: 1284x2778 or 2778x1284
- 5.5”: 1242x2208 or 2208x1242
- iPad Pro: 2048x2732 or 2732x2048
Generate screenshots:
# Run app on device/emulator
flutter run --release
# Take screenshots from device
# Android: Power + Volume Down
# iOS: Side Button + Volume Up
Or use automated screenshot tools:
Privacy Policy
Both stores require a privacy policy URL. Include:
- What data you collect
- How you use it
- Third-party services (Google Maps, Analytics)
- Data retention
- User rights
Example privacy policy generators:
Environment Configuration
Production vs Development
Development (.env):
API_BASE_URL=http://192.168.1.100:3000
GOOGLE_MAPS_API_KEY=dev_key
Production (.env.production):
API_BASE_URL=https://api.yourdomain.com
GOOGLE_MAPS_API_KEY=production_key
Load based on build mode:
import 'package:flutter_dotenv/flutter_dotenv.dart';
Future<void> main() async {
// Load environment based on build mode
String envFile = const String.fromEnvironment('ENV', defaultValue: '.env');
await dotenv.load(fileName: envFile);
runApp(MyApp());
}
Build with specific environment:
flutter build apk --dart-define=ENV=.env.production
Version Management
Semantic Versioning
Use format: MAJOR.MINOR.PATCH
- MAJOR: Breaking changes
- MINOR: New features
- PATCH: Bug fixes
Update in pubspec.yaml:
1.2.3 = versionName (Android) / CFBundleShortVersionString (iOS)
4 = versionCode (Android) / CFBundleVersion (iOS)
Incrementing Versions
Before each release:
-
Update
pubspec.yaml:
version: 1.0.1+2 # was 1.0.0+1
-
Android auto-updates from pubspec
-
iOS: Update in Xcode or
Info.plist
App Signing Best Practices
Android Keystore
Losing your keystore means you can never update your app again!
Backup your keystore:
cp ~/mnd-release-key.jks ~/Dropbox/backups/
Store passwords securely:
- Use a password manager
- Don’t commit to git
- Share securely with team (encrypted)
iOS Certificates
- Certificates expire after 1 year (auto-renewed by Xcode)
- Provisioning profiles expire after 1 year
- Distribution certificates limited to 3 per team
Testing Before Release
Beta Testing
Android (Google Play Console):
- Create Internal/Closed/Open testing track
- Upload AAB
- Add testers by email
- Share opt-in URL
iOS (TestFlight):
- Upload build via Xcode
- Go to App Store Connect → TestFlight
- Add internal testers (up to 100)
- Add external testers (up to 10,000)
- Testers install TestFlight app and opt-in
Pre-Launch Checklist
Troubleshooting
Android Build Fails
# Clean build
flutter clean
cd android && ./gradlew clean && cd ..
flutter pub get
flutter build apk
iOS Build Fails
# Clean build
flutter clean
cd ios
rm -rf Pods Podfile.lock
pod install
cd ..
flutter build ios
App Crashes on Launch
Check logs:
# Android
adb logcat | grep flutter
# iOS
flutter logs
Common issues:
- Missing .env file
- Invalid API URL
- Missing permissions
Google Maps Not Showing
- Verify API key is correct
- Check API key restrictions (bundle ID/package name)
- Ensure Maps SDK enabled in Google Cloud Console
- Check device has internet connection
Next Steps