react-native-sop-docs/10-release-process/android-release.md

9.2 KiB

Android Release Process

Keystore Generation

Create Release Keystore

# Generate a new keystore
keytool -genkeypair -v -storetype PKCS12 -keystore saayam-release-key.keystore -alias saayam-key-alias -keyalg RSA -keysize 2048 -validity 10000

# Verify keystore
keytool -list -v -keystore saayam-release-key.keystore

Keystore Security

# Store keystore securely
mkdir -p ~/.android/keystores
mv saayam-release-key.keystore ~/.android/keystores/

# Set proper permissions
chmod 600 ~/.android/keystores/saayam-release-key.keystore

Gradle Configuration

Configure Signing

android/gradle.properties:

# Keystore configuration
SAAYAM_UPLOAD_STORE_FILE=saayam-release-key.keystore
SAAYAM_UPLOAD_KEY_ALIAS=saayam-key-alias
SAAYAM_UPLOAD_STORE_PASSWORD=your_keystore_password
SAAYAM_UPLOAD_KEY_PASSWORD=your_key_password

# Build optimization
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.daemon=true

# Android configuration
android.useAndroidX=true
android.enableJetifier=true
android.enableR8.fullMode=true

android/app/build.gradle:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion

    defaultConfig {
        applicationId "com.saayam.app"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0.0"
        multiDexEnabled true
    }

    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
        release {
            if (project.hasProperty('SAAYAM_UPLOAD_STORE_FILE')) {
                storeFile file(SAAYAM_UPLOAD_STORE_FILE)
                storePassword SAAYAM_UPLOAD_STORE_PASSWORD
                keyAlias SAAYAM_UPLOAD_KEY_ALIAS
                keyPassword SAAYAM_UPLOAD_KEY_PASSWORD
            }
        }
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
            applicationIdSuffix ".debug"
            debuggable true
            minifyEnabled false
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
            debuggable false
            zipAlignEnabled true
        }
    }

    splits {
        abi {
            reset()
            enable true
            universalApk false
            include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
        }
    }
}

ProGuard Configuration

android/app/proguard-rules.pro:

# React Native
-keep class com.facebook.react.** { *; }
-keep class com.facebook.hermes.** { *; }
-keep class com.facebook.jni.** { *; }

# Keep our application class
-keep class com.saayam.** { *; }

# Keep native methods
-keepclassmembers class * {
    native <methods>;
}

# Keep enums
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Keep Parcelable implementations
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# Keep serializable classes
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# Firebase
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase

# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

# Gson
-keepattributes Signature
-keepattributes *Annotation*
-dontwarn sun.misc.**
-keep class com.google.gson.** { *; }
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

Build Process

Build Commands

# Clean build
cd android && ./gradlew clean

# Build debug APK
./gradlew assembleDebug

# Build release APK
./gradlew assembleRelease

# Build release AAB (recommended for Play Store)
./gradlew bundleRelease

# Install debug APK
./gradlew installDebug

# Install release APK
./gradlew installRelease

Build Variants

android {
    flavorDimensions "version"
    
    productFlavors {
        development {
            dimension "version"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            buildConfigField "String", "API_BASE_URL", '"https://dev-api.saayam.com"'
            resValue "string", "app_name", "Saayam Dev"
        }
        
        staging {
            dimension "version"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            buildConfigField "String", "API_BASE_URL", '"https://staging-api.saayam.com"'
            resValue "string", "app_name", "Saayam Staging"
        }
        
        production {
            dimension "version"
            buildConfigField "String", "API_BASE_URL", '"https://api.saayam.com"'
            resValue "string", "app_name", "Saayam"
        }
    }
}

Google Play Console Setup

Create Application

  1. Go to Google Play Console

  2. Create Application

    • App name: Saayam
    • Default language: English
    • App or game: App
    • Free or paid: Free
  3. App Content

    • Privacy Policy URL
    • App category
    • Content rating questionnaire
    • Target audience
    • Data safety form

Upload Release

# Build release AAB
cd android && ./gradlew bundleRelease

# AAB file location
# android/app/build/outputs/bundle/release/app-release.aab

Release Tracks

  1. Internal Testing

    • Upload AAB
    • Add internal testers
    • Release to internal testing
  2. Closed Testing

    • Create closed testing track
    • Add test users via email lists
    • Release to closed testing
  3. Open Testing

    • Create open testing track
    • Set country availability
    • Release to open testing
  4. Production

    • Review release
    • Set rollout percentage
    • Release to production

Version Management

Automated Version Bumping

scripts/bump-android-version.sh:

#!/bin/bash

VERSION_TYPE=${1:-patch}  # major, minor, patch
BUILD_GRADLE="android/app/build.gradle"

# Get current version
CURRENT_VERSION=$(grep "versionName" $BUILD_GRADLE | sed 's/.*versionName "\(.*\)"/\1/')
CURRENT_CODE=$(grep "versionCode" $BUILD_GRADLE | sed 's/.*versionCode \(.*\)/\1/')

echo "Current version: $CURRENT_VERSION ($CURRENT_CODE)"

# Calculate new version
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}

case $VERSION_TYPE in
    major)
        MAJOR=$((MAJOR + 1))
        MINOR=0
        PATCH=0
        ;;
    minor)
        MINOR=$((MINOR + 1))
        PATCH=0
        ;;
    patch)
        PATCH=$((PATCH + 1))
        ;;
esac

NEW_VERSION="$MAJOR.$MINOR.$PATCH"
NEW_CODE=$((CURRENT_CODE + 1))

echo "New version: $NEW_VERSION ($NEW_CODE)"

# Update build.gradle
sed -i "s/versionCode $CURRENT_CODE/versionCode $NEW_CODE/" $BUILD_GRADLE
sed -i "s/versionName \"$CURRENT_VERSION\"/versionName \"$NEW_VERSION\"/" $BUILD_GRADLE

echo "Version updated successfully!"

Git Tagging

# Create and push tag
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0

# List tags
git tag -l

# Delete tag
git tag -d v1.0.0
git push origin --delete v1.0.0

Release Checklist

Pre-Release Checklist

  • Update version number
  • Update changelog
  • Run all tests
  • Test on multiple devices
  • Check ProGuard configuration
  • Verify signing configuration
  • Test release build locally
  • Update app store metadata
  • Prepare release notes

Post-Release Checklist

  • Monitor crash reports
  • Check app store reviews
  • Monitor performance metrics
  • Update documentation
  • Create git tag
  • Notify team of release
  • Plan next release

Troubleshooting

Common Build Issues

# Clean and rebuild
cd android
./gradlew clean
./gradlew assembleRelease

# Clear React Native cache
npx react-native start --reset-cache

# Clear Metro cache
npx react-native start --reset-cache

# Reset Android build
cd android
rm -rf build/
rm -rf app/build/
./gradlew clean

Keystore Issues

# Verify keystore
keytool -list -v -keystore your-keystore.keystore

# Check keystore alias
keytool -list -keystore your-keystore.keystore

# Export certificate
keytool -export -alias your-alias -keystore your-keystore.keystore -file certificate.crt