Flutter Play Store Deployment in 2026: targetSdk 35, AAB, CI/CD & Zero-Rejection Checklist.
If your Flutter app still follows a 2023 or 2024 deployment tutorial, it will likely get rejected on Google Play in 2026.
Google now enforces targetSdk 35 (Android 15), mandatory Android App Bundles (AAB), stricter permission reviews, and Play Integrity checks. Miss even one requirement, and your release stalls.
I’ve deployed multiple Flutter apps to production in 2025–2026, and this guide is the exact checklist I use to get apps approved on the first attempt—no outdated advice, no trial-and-error.
TL;DR — Flutter Play Store Deployment Checklist (2026)
✅ Flutter 3.38+
✅ compileSdk = 35, targetSdk = 35
✅ Android App Bundle (AAB only — APK not allowed)
✅ Upload keystore + enable Play App Signing
✅ Hosted privacy policy URL
✅ Data Safety & Permissions Declaration completed
✅ Internal testing before production release
✅ Crashlytics + Play Integrity configured
⏱️ Prep time: ~2–4 hours
⏳ Google review: 1–7 days
Here's the express route:
# Update your build.gradle to target API 35
# Generate keystore
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
-keysize 2048 -validity 10000 -alias upload
# Build release AAB with obfuscation
flutter build appbundle --release --obfuscate \
--split-debug-info=build/app/outputs/symbols
# Upload to Play Console → Internal Testing → Production
Essential Links:
Timeline: 2-4 hours prep + 1-7 days Google review
Why Deploy in 2026? (The Stakes Are Higher Than Ever) 🚀
Google Play just dropped the hammer. As of August 31, 2025, all new apps and updates must target Android 15 (API level 35). Existing apps need API 34 minimum or they'll become invisible to users on newer Android devices. This isn't a suggestion it's a hard deadline that's already causing rejections.
I've deployed 5+ Flutter apps to production this year alone for clients across Tamil Nadu and South Asia, and let me tell you: the 2026 deployment landscape is very different from 2024.
Here's what changed:
- AAB (Android App Bundle) is mandatory APKs won't cut it anymore
- targetSdk 35 required for new submissions (API 34 for existing apps)
- 16KB memory page support for Android 15 compatibility
- Play Integrity API replacing SafetyNet for security checks
- Stricter privacy policy requirements (must be hosted, not PDF)
Flutter continues to be one of the most widely used cross-platform frameworks on Google Play, powering a large and growing number of production apps across startups and enterprises. But here's the kicker: many indie devs are getting rejected because they're using outdated deployment workflows from 2022-2023 tutorials. Don't be that dev.
This guide covers the complete 2026-compliant workflow with real code, common gotchas, and the exact configuration I use for client projects that go live in 48 hours.
Prerequisites ✅
Before we dive in, make sure you have:
| Requirement | Version/Details | Why You Need It |
|---|---|---|
| Flutter SDK | 3.38.6+ (latest stable) | Supports latest Android APIs |
| Android Studio | Ladybug 2024.2.1+ | For SDK tools & emulators |
| Java/JDK | 17+ | Required for Gradle 8+ |
| Google Play Console Account | $25 one-time fee | To publish apps |
| Privacy Policy URL | Hosted online | Play Store requirement |
| App Assets | Icon (512×512), Banner (1024×500) | Store listing |
Who This Guide Is For
This guide is ideal if you are:
- A Flutter developer preparing your first Play Store release
- An indie dev tired of Play Store rejections
- A freelancer shipping apps for clients
- Migrating an existing app to targetSdk 35
- Setting up CI/CD for Play Store deployments
If you just want a basic APK build — this is not that guide.
Quick Check:
flutter --version
# Flutter 3.38.6 • channel stable
# Dart 3.9.0 • DevTools 2.37.3
java --version
# openjdk 17.0.2 2022-01-18
If your Flutter version is below 3.24, upgrade immediately:
flutter upgrade
flutter doctor -v
Step 1: Prepare Your Flutter Project 📱
1.1 Update pubspec.yaml
Open pubspec.yaml and verify your version:
name: my_awesome_app
description: A production-ready Flutter app
version: 1.0.0+1 # Format: version_name+build_number
environment:
sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# Your other dependencies
Version scheme explained:
-
1.0.0= version name (shown to users) -
+1= build number (internal version code) - Increment
+1→+2→+3for each upload - Major updates:
1.0.0→1.1.0→2.0.0
1.2 Configure build.gradle for API 35
Navigate to android/app/build.gradle and update:
android {
namespace = "com.yourcompany.appname" // Must match package
compileSdk = 35 // ⚡ Required for 2026
ndkVersion = "27.0.12077973" // For native code
defaultConfig {
applicationId = "com.yourcompany.appname"
minSdk = 24 // Supports 94%+ of devices
targetSdk = 35 // 🔥 CRITICAL: Must be 35 for new apps
versionCode = 1
versionName = "1.0.0"
multiDexEnabled = true
}
buildTypes {
release {
minifyEnabled = true // Shrink code
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.release
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.7.0")
}
Pro tip: If you see "Namespace not specified" errors, add the namespace line inside the android block. This replaces the old package attribute in AndroidManifest.xml.
1.3 Update AndroidManifest.xml Permissions
Open android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ⚠️ Declare only permissions you actually use -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- For camera (only if needed) -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- For location (use COARSE first, FINE only if necessary) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- For Bluetooth on API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<application
android:label="My Awesome App"
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true"> <!-- API 33+ -->
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Common gotcha: Google now rejects apps that request permissions they don't use. If you ask for ACCESS_FINE_LOCATION but only need city-level data, you'll get flagged. Remove unused permissions!
1.4 Test on API 35 Emulator
Create an emulator targeting Android 15:
# List available system images
flutter emulators
# Create API 35 emulator
avdmanager create avd -n Pixel_7_API_35 -k "system-images;android-35;google_apis;x86_64" -d "pixel_7"
# Launch and test
flutter emulators --launch Pixel_7_API_35
flutter run
Test critical flows: permissions, deep links, background services.
Step 2: Generate Signing Keys 🔐
Android requires cryptographic signing for release apps. Never lose these keys you can't update your app without them.
2.1 Generate Upload Keystore
On Mac/Linux:
keytool -genkey -v -keystore ~/upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
On Windows (PowerShell):
keytool -genkey -v -keystore $HOME\upload-keystore.jks `
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
Follow the prompts:
Enter keystore password: [create a strong password]
Re-enter password: [same password]
What is your first and last name? [Your name or company]
What is the name of your organizational unit? [Engineering]
What is the name of your organization? [Your Company]
What is the name of your City or Locality? [Hosur]
What is the name of your State or Province? [Tamil Nadu]
What is the two-letter country code for this unit? [IN]
🔒 Security: Store the .jks file and password in a password manager (1Password, Bitwarden). Add to .gitignore:
# Keystore files
*.jks
*.keystore
key.properties
2.2 Create key.properties
In your Flutter project root, create android/key.properties:
storePassword=YourStrongPassword123!
keyPassword=YourStrongPassword123!
keyAlias=upload
storeFile=/Users/yourusername/upload-keystore.jks
⚠️ Warning: Never commit this file to Git! Add to .gitignore.
For CI/CD, use environment variables:
export KEYSTORE_PASSWORD="YourStrongPassword123!"
export KEY_PASSWORD="YourStrongPassword123!"
export KEY_ALIAS="upload"
export KEYSTORE_FILE="$HOME/upload-keystore.jks"
2.3 Configure Gradle Signing (Kotlin DSL)
Edit android/app/build.gradle:
// Add above the android block
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
// ... existing config ...
signingConfigs {
create("release") {
storeFile = file(keystoreProperties.getProperty("storeFile") ?: "")
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
// Enable R8 optimization
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
For older Groovy syntax (if using build.gradle not .gradle.kts):
def keystorePropertiesFile = rootProject.file('key.properties')
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
Step 3: Build App Bundle (AAB) 🚀
Time to create the production build. AABs are superior to APKs because Play Store dynamically optimizes downloads for each device.
3.1 Basic Release Build
# Clean previous builds
flutter clean
# Get dependencies
flutter pub get
# Build AAB (without obfuscation)
flutter build appbundle --release
Your AAB will be at: build/app/outputs/bundle/release/app-release.aab
Bundle size: Typically 15-30MB for a basic app. Check with:
du -h build/app/outputs/bundle/release/app-release.aab
3.2 Build with Obfuscation (Recommended)
Obfuscation makes reverse-engineering harder by scrambling class/method names. Critical for production apps.
flutter build appbundle --release \
--obfuscate \
--split-debug-info=build/app/outputs/symbols
What happens:
--obfuscate: Renames Dart code identifiers
--split-debug-info: Saves symbol maps for crash reports
Symbols go to build/app/outputs/symbols/ (keep these for Firebase Crashlytics)
Upload symbols to Firebase:
# After setting up Firebase Crashlytics
firebase crashlytics:symbols:upload \
--app=YOUR_APP_ID \
build/app/outputs/symbols
3.3 Optimize Bundle Size
Enable tree-shaking for icons:
# pubspec.yaml
flutter:
uses-material-design: true
# Only include icons you use
fonts:
- family: MaterialIcons
fonts:
- asset: fonts/MaterialIcons-Regular.otf
Or use this flag:
flutter build appbundle --release \
--tree-shake-icons \
--obfuscate \
--split-debug-info=build/app/outputs/symbols
Advanced: Analyze bundle:
# Install bundletool
curl -LO https://github.com/google/bundletool/releases/download/1.15.6/bundletool-all-1.15.6.jar
# Analyze what's in your AAB
java -jar bundletool-all-1.15.6.jar build-apks \
--bundle=build/app/outputs/bundle/release/app-release.aab \
--output=app.apks \
--connected-device
java -jar bundletool-all-1.15.6.jar get-size total \
--apks=app.apks
Result example:
MIN_SDK=24 MAX_SDK=35
Total: 18.2 MB (arm64-v8a: 12.1 MB, armeabi-v7a: 6.1 MB)
3.4 Flavor Builds (Optional)
For dev/staging/prod environments:
# Build specific flavor
flutter build appbundle --release \
--flavor production \
--dart-define=API_URL=https://api.myapp.com \
--dart-define=ENV=production
Step 4: Play Console Setup 🎨
Your app bundle is ready. Now let's create a compelling store listing.
4.1 Create App in Play Console
- Go to Play Console
- Click Create app
- Fill in:
- App name: Max 30 characters (appears in store)
- Default language: English (United States) or your target market
- App or game: Select appropriate category
- Free or paid: Free (you can add in-app purchases later)
- Accept Developer Program Policies and US Export Laws
- Click Create app
4.2 App Content Declarations
Before uploading your AAB, complete these required sections:
Privacy Policy:
- Must be a hosted URL (not PDF)
- Use App Privacy Policy Generator
- Or Termly for professional docs
- Host on: GitHub Pages, your website, or Notion
Data Safety Section:
Navigate: Policy → App content → Data safety
Answer honestly:
- Does your app collect user data? (Yes/No)
- What types: Location, Personal info, Financial, etc.
- Do you share data with third parties?
- Is data encrypted in transit?
Pro tip: Most Flutter apps with Firebase collect at least device ID and crash logs. Disclose it!
Content Rating:
Policy → App content → Content rating
Complete the IARC questionnaire:
- Violence: Does your app depict blood/gore?
- Sexual content: Any romantic/sexual themes?
- Profanity: Does it contain strong language?
- Controlled substances: References to drugs/alcohol? Takes 5 minutes, generates ratings for ESRB, PEGI, USK, etc.
Target Audience:
Policy → App content → Target audience and content
- Select age groups: 18+, 13-17, etc.
- If targeting kids (under 13), stricter rules apply (COPPA)
4.3 Store Listing Assets
App Icon (512×512px):
- PNG format, 32-bit with alpha
- No rounded corners (Google adds them)
- Tools: App Icon Generator
Feature Graphic (1024×500px):
- Required for Play Store listing header
- PNG/JPEG, max 1MB
- No text that duplicates your app name
- Canva template
Screenshots (Phone & Tablet):
| Device Type | Minimum Required | Max Upload | Recommended Size |
|---|---|---|---|
| Phone | 2 | 8 | 1080×2340 (9:16) or 1440×2560 |
| 7" Tablet | 1 (if targeting tablets) | 8 | 1920×1200 (16:10) |
| 10" Tablet | 1 (if targeting tablets) | 8 | 2560×1600 (16:10) |
Technical requirements:
- Format: PNG or JPEG (24-bit, no alpha)
- Min dimension: 320px
- Max dimension: 3840px
- Max dimension cannot be >2× min dimension
- File size: 8MB max per image
Screenshot best practices (from 500+ apps analyzed):
- First 3 screenshots are critical — shown in search results
- Show actual UI, not marketing fluff
- Add text overlays highlighting key features
- Use device frames (optional but looks professional)
- Portrait mode for phone screenshots
- Showcase 5-7 core features across 6-8 screenshots
Tools for creating screenshots:
# Method 1: Android Emulator
# Run your app, then:
# Toolbar → Camera icon → Save screenshot
# Method 2: Physical device
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png
# Method 3: Automated (flutter_screenshot package)
flutter pub add flutter_screenshot
flutter test test/screenshot_test.dart --update-goldens
Add text overlays with App Mockup or Previewed
4.4 Store Listing Text
Short Description (80 chars max):
Boost productivity with AI-powered task management. Try it free!
Full Description (4000 chars max):
Structure it like this (tested on 100+ apps):
[Hook] Struggling to stay organized? Meet TaskFlow – the smart to-do app that learns your habits.
[Key Features]
✅ AI-powered task prioritization
✅ Seamless calendar sync (Google, Outlook)
✅ Voice commands & quick capture
✅ Cross-platform: Android, iOS, Web
✅ Offline mode with cloud sync
[Social Proof]
"TaskFlow doubled my productivity!" – ★★★★★ Review from Sarah M.
Featured in TechCrunch, Product Hunt #1 Product of the Day.
[Use Cases]
Perfect for:
- Students managing assignments
- Freelancers tracking client projects
- Busy parents juggling family schedules
[CTA]
Download now and reclaim your time. Free forever with premium upgrades.
[SEO Keywords]
todo app, task manager, productivity, GTD, project management
SEO tip: Include keywords naturally. Google Play indexes:
- App title (30 chars) most important
- Short description (80 chars)
- Full description (4000 chars)
Localization:
If targeting India/South Asia:
- Add Hindi, Tamil, Telugu translations
- Often leads to significantly higher discoverability and conversion, especially in India and Southeast Asia, based on Play Console benchmarks and real-world launches.
- Use Crowdin or hire on Upwork
4.5 Pricing & Distribution
Grow → Store presence → Pricing & distribution
Countries: Select carefully
- Tier 1: USA, UK, Canada (high ARPU but competitive)
- Tier 2: India, Brazil, Indonesia (massive user base)
- Tip: Start with 50-100 countries, expand later
For Indian market targeting:
- Include: India, UAE, Singapore, Malaysia
- Optimize for 4G networks (bundle size <20MB)
- Support UPI payments for in-app purchases
Step 5: Upload & Review 🎯
5.1 Create Internal Testing Track
Why internal testing first:
- Faster review (~30 minutes vs 3-7 days)
- Test with real users before public launch
- Required for first-time Google Play apps (new developer accounts)
Steps:
- Navigate:
Release → Testing → Internal testing - Click Create new release
-
Upload AAB:
- Drag
app-release.aabfrombuild/app/outputs/bundle/release/ - Google scans for malware, API violations (~5 min)
- Drag
- Release notes:
Version 1.0.0
- Initial release
- Core features: X, Y, Z
- Supports Android 7.0+ (API 24)
- Add testers (email addresses or Google Groups)
- Click Review release → Start rollout to Internal testing
Testing link:
https://play.google.com/apps/internaltest/[YOUR_PACKAGE_ID]
Share with 5-10 beta testers. Collect feedback for 2-3 days.
5.2 Promote to Production
Once internal testing is stable:
- Navigate:
Release → Production - Click Create new release
- Choose release type:
- Full rollout: 100% of users immediately
- Staged rollout: Start with 5% → 10% → 50% → 100% (safer)
- Upload same AAB (or a newer build with +2 version code)
- Release notes (user-facing):
What's new in v1.0.0:
🎉 Launch! Welcome to TaskFlow
✨ AI task suggestions
📱 Clean, intuitive interface
🔒 End-to-end encryption
- Click Save → Review release
Automated checks run:
- Security scan (malware, trojans)
- API level compliance (targetSdk 35?)
- Permissions review (do you justify camera access?)
- Content rating match (does app match declared rating?)
If all green, click Start rollout to Production
5.3 Managed Publishing (Optional)
Release → Setup → Managed publishing
Enable this to control when updates go live:
- Upload AAB
- Google reviews it
- Once approved, you choose the exact launch time
Great for coordinating with marketing campaigns.
5.4 Review Timeline & Common Rejections
Typical review times:
- Internal testing: 30 minutes - 2 hours
- Production (established accounts): 1-3 days
- Production (new accounts): 3-7 days (stricter scrutiny)
Rejection reasons I've seen (and how to fix):
| Issue | Reason | Fix |
|---|---|---|
| Content policy violation | App description mentions "coronavirus" without context | Remove health claims or link to WHO guidelines |
| Permissions misuse | Requested ACCESS_FINE_LOCATION for non-location feature |
Remove unused permissions from AndroidManifest |
| Crashes on startup | Tested on Pixel 6 (API 33), app crashed | Test on multiple emulators; check logs with adb logcat
|
| Misleading icon/screenshots | Icon resembles Google Photos app | Redesign to avoid brand confusion |
| Missing privacy policy | URL returns 404 | Host privacy policy on GitHub Pages or website |
| API level too low | targetSdk 33 (needs 35 in 2026) | Update build.gradle to targetSdk 35
|
| App not designed for families | Targets kids but has ads | Either remove ads or don't target kids under 13 |
Pro tip: If rejected, respond within 7 days with fixes. Use the "Appeal" button in Play Console if you think the rejection is wrong.
5.5 Post-Launch Monitoring
Once live, track these metrics:
Play Console Dashboard:
- Installs: Daily downloads
- Uninstalls: High rate = bad onboarding
- Crashes: ANR (App Not Responding) rate should be <0.5%
- Ratings: Aim for 4.0+ average
Firebase Crashlytics:
# Add to pubspec.yaml
firebase_core: ^3.9.0
firebase_crashlytics: ^4.5.0
# In main.dart
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
Set up alerts:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Pass all uncaught errors to Crashlytics
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
// Catch async errors
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(MyApp());
}
Pro Tips & Gotchas ⚠️
Bundle Size Optimization
Problem: AAB is 45MB (Play Store recommends <20MB for emerging markets)
Solutions:
1. Compress images:
# Install imagemagick
brew install imagemagick # Mac
sudo apt install imagemagick # Linux
# Compress all PNGs in assets
mogrify -resize 1024x1024 -quality 85 assets/images/*.png
2. Use WebP format:
# pubspec.yaml
flutter:
assets:
- assets/images/hero.webp # 75% smaller than PNG
3. Lazy load fonts:
# Don't bundle all Material Icons
uses-material-design: true
# Only include custom fonts you actually use
4. Analyze bundle breakdown:
flutter build appbundle --analyze-size
# Output: app-release-size-analysis.json
5. Enable app bundle deobfuscation:
// android/app/build.gradle
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true // Removes unused resources
}
}
}
Real example: Reduced client app from 38MB → 18MB using these techniques.
64-bit Requirement (Already Default)
All Flutter apps compile for both ARM32 and ARM64 by default since Flutter 2.x. Verify with:
flutter build appbundle --release
unzip -l build/app/outputs/bundle/release/app-release.aab | grep "lib/"
Should see:
lib/arm64-v8a/libflutter.so
lib/armeabi-v7a/libflutter.so
Play Integrity API (Replaces SafetyNet)
SafetyNet is deprecated. Use Play Integrity for anti-tampering:
# pubspec.yaml
dependencies:
play_integrity: ^0.1.0
import 'package:play_integrity/play_integrity.dart';
Future<void> checkIntegrity() async {
final token = await PlayIntegrity.requestIntegrityToken(
nonce: 'YOUR_NONCE',
);
// Send token to your backend for verification
}
Backend verification (Node.js example):
const { google } = require('googleapis');
async function verifyIntegrity(token) {
const playIntegrity = google.playintegrity('v1');
const response = await playIntegrity.v1.decodeIntegrityToken({
packageName: 'com.yourcompany.app',
requestBody: { integrityToken: token }
});
// Check deviceIntegrity, appIntegrity, accountDetails
return response.data.tokenPayloadExternal.deviceIntegrity.deviceRecognitionVerdict;
}
CI/CD with GitHub Actions
Automate deployments with GitHub Actions. Create .github/workflows/deploy.yml:
name: Deploy to Play Store
on:
push:
tags:
- 'v*' # Triggers on version tags like v1.0.0
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.6'
channel: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/upload-keystore.jks
- name: Create key.properties
run: |
cat > android/key.properties << EOF
storePassword=${{ secrets.STORE_PASSWORD }}
keyPassword=${{ secrets.KEY_PASSWORD }}
keyAlias=upload
storeFile=upload-keystore.jks
EOF
- name: Build AAB
run: |
flutter build appbundle --release \
--obfuscate \
--split-debug-info=build/app/outputs/symbols \
--build-number=${{ github.run_number }}
- name: Upload to Play Console
uses: r0adkll/upload-google-play@v1.1.3
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.yourcompany.app
releaseFiles: build/app/outputs/bundle/release/app-release.aab
track: production
status: completed
inAppUpdatePriority: 3
Setup secrets in GitHub:
# Encode keystore to base64
base64 -i android/app/upload-keystore.jks | pbcopy
# Add to GitHub repo:
# Settings → Secrets and variables → Actions → New repository secret
# KEYSTORE_BASE64: [paste base64 string]
# STORE_PASSWORD: YourStrongPassword123!
# KEY_PASSWORD: YourStrongPassword123!
# SERVICE_ACCOUNT_JSON: [Google Cloud service account JSON]
Create Google Cloud service account:
- Go to Google Cloud Console
- Create new service account
- Grant role: Service Account User
- Create JSON key, download it
- In Play Console:
Settings → API access → Link service account
Version Management Strategy
Semantic versioning for Flutter:
1.2.3+45
│ │ │ └─ build number (increment every build)
│ │ └──── patch (bug fixes)
│ └────── minor (new features, backward compatible)
└──────── major (breaking changes)
Update script (scripts/bump_version.sh):
#!/bin/bash
# Usage: ./scripts/bump_version.sh patch
VERSION_TYPE=$1 # major, minor, or patch
PUBSPEC="pubspec.yaml"
CURRENT=$(grep "version:" $PUBSPEC | sed 's/version: //')
VERSION=$(echo $CURRENT | cut -d'+' -f1)
BUILD=$(echo $CURRENT | cut -d'+' -f2)
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
case $VERSION_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_BUILD=$((BUILD + 1))
NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD"
sed -i "" "s/version: .*/version: $NEW_VERSION/" $PUBSPEC
echo "Version bumped to $NEW_VERSION"
Usage:
chmod +x scripts/bump_version.sh
./scripts/bump_version.sh patch # 1.0.0+1 → 1.0.1+2
Handling Native Code & Plugins
If using plugins with native Android code (camera, location, etc.):
Check plugin compatibility:
flutter pub outdated
Update all plugins:
flutter pub upgrade --major-versions
For plugins with native code, rebuild:
cd android
./gradlew clean
cd ..
flutter clean
flutter pub get
flutter build appbundle --release
Common plugin issues on API 35:
- image_picker: Update to 1.0.7+
- geolocator: Requires runtime permission requests
- firebase_messaging: Update to 15.0.0+ for Android 13+ notifications
Testing with Firebase App Distribution
Before Play Store, distribute to QA team via Firebase:
# Install Firebase CLI
npm install -g firebase-tools
# Login
firebase login
# Deploy to App Distribution
firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \
--app 1:123456789:android:abcdef \
--groups "qa-team" \
--release-notes "Bug fixes for login flow"
My Portfolio Example 💼
Last month, I deployed a Flutter SaaS app for a Tamil Nadu-based logistics client went live in 48 hours from first commit to Play Store approval. The app handles real-time GPS tracking for 500+ delivery agents across Chennai, Bangalore, and Hosur.
Tech stack:
- Flutter 3.38.6 + Riverpod for state management
- Firebase (Auth, Firestore, Cloud Messaging)
- Google Maps API with geofencing
- Laravel backend with REST API
Challenges solved:
- Optimized bundle size from 42MB → 19MB (critical for 4G networks)
- Implemented offline-first sync with Hive
- Passed Play Store review first try (no rejections!)
Client testimonial:
"Deployed faster than expected. The app handles peak traffic smoothly. Highly recommend!" — Ravi Kumar, CTO, SwiftLogistics
Need help with your Flutter app? I specialize in:
- 📱 End-to-end Flutter development (iOS + Android)
- 🚀 Play Store & App Store deployment
- 🤖 AI integration (ChatGPT, Gemini APIs)
- ☁️ Firebase + Laravel backends
- 🔧 DevOps & CI/CD setup
Based in Hosur, Tamil Nadu (serving clients across India, UAE, and USA).
Check my portfolio | Book a consultation
Conclusion & CTA 🎯
Flutter Play Store deployment in 2026 isn’t harder it’s less forgiving.
If you follow outdated tutorials, Google will reject your app.
If you follow this checklist, approval is usually boringly smooth.
Bookmark this guide before your next release — it will save you hours.
Follow this checklist:
✅ Update to Flutter 3.38.6+ and targetSdk 35
✅ Generate and secure signing keys
✅ Build optimized AAB with obfuscation
✅ Create compelling store listing (2-8 screenshots, privacy policy)
✅ Test on internal track first
✅ Monitor post-launch metrics (crashes, ratings)
Expected timeline:
- Development: Varies (1 week - 6 months)
- Deployment prep: 2-4 hours
- Google review: 1-7 days
- Total: ~48 hours for experienced devs
Resources:
👏 Found this helpful?
- Clap (or ❤️) if this guide saved you hours of debugging
- Follow me for more Flutter, Laravel, and AI integration tutorials
- Share with your dev friends struggling with Play Store rejections
If you’re stuck with Play Store rejections or want a second review before publishing, I help teams ship Flutter apps without approval back-and-forth. Links below if useful. Let's chat! I'm available for:
- Freelance projects (mobile apps, web apps)
- Technical consulting (architecture, code reviews)
- Training sessions (Flutter, Dart, Firebase)
📧 Contact: [dtechdigitalsolution@gmail.com]
🌐 Portfolio: [dtechsolutions]
💼 LinkedIn: [linkedin.com/in/yourprofile]
🐦 X(Twitter): [@dtech_solution]
Last updated: January 2026. Tested with Flutter 3.38.6, Android Studio Ladybug, targetSdk 35.
Keywords: flutter deployment, play store, android app, AAB, targetSdk 35, flutter tutorial, indie dev, freelance developer, Hosur developer, Tamil Nadu, app publishing 2026
💬 What part of Play Store deployment has caused you the most trouble?
Permissions? targetSdk upgrades? Rejections?
Drop a comment I reply to every serious dev question.
FAQs
Q: Can I use an APK instead of AAB?
A: For Play Store, AAB is mandatory. APKs only work for direct distribution (website downloads, enterprise apps).
Q: How long does Play Store review take for first-time developers?
A: 3-7 days (stricter scrutiny). Established accounts: 1-3 days.
Q: Do I need a Mac for Android deployment?
A: No! Android deployment works on Windows, Mac, or Linux. (iOS needs Mac + Xcode.)
Q: What if I lose my keystore file?
A: You cannot update your app. You'd have to publish as a new app with a new package name. Back it up!
Q: Can I change my app's package name after publishing?
A: No. Package name (com.company.appname) is permanent. Choose wisely!
Q: How do I add in-app purchases?
A: Use the in_app_purchase plugin. Configure products in Play Console → Monetize → Products.
Q: My app was rejected for "Permissions Declaration Form". What now?
A: For sensitive permissions (location, camera, contacts), Google requires a form explaining why you need them. Fill it in Play Console → App content → Permissions declaration.
Enjoy this guide? More Flutter & Laravel content coming soon. Stay tuned! 🚀
Top comments (0)