xiaohongshu
xiaohongshu
Submitting, testing, and releasing mobile apps to app stores including App Store and Google Play submission, beta testing, OTA updates, and release management.
Full skill instructions
Current Level: Intermediate
Domain: Mobile Development / DevOps
App distribution covers the process of submitting, testing, and releasing mobile apps to app stores. This guide covers App Store and Google Play submission, beta testing, OTA updates, and release management for successful app launches and updates.
// Fastlane configuration for iOS
// fastlane/Fastfile
platform :ios do
desc "Submit to TestFlight"
lane :testflight do
increment_build_number
match(
type: "appstore",
readonly: true
)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
export_options: {
provisioningProfiles: {
"com.example.myapp" => "match AppStore com.example.myapp"
}
}
)
upload_to_testflight(
skip_waiting_for_build_processing: true,
changelog: "Bug fixes and improvements"
)
end
desc "Submit to App Store"
lane :release do
increment_build_number
match(
type: "appstore",
readonly: true
)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
export_options: {
provisioningProfiles: {
"com.example.myapp" => "match AppStore com.example.myapp"
}
}
)
upload_to_app_store(
submit_for_review: true,
automatic_release: false,
force: true,
submission_information: {
add_id_info_uses_idfa: false
},
app_rating_config_path: "./fastlane/metadata/app_rating_config.json"
)
end
end
# fastlane/Fastfile
platform :android do
desc "Submit to Google Play Internal Testing"
lane :internal do
gradle(
task: "assemble",
build_type: "Release",
project_dir: "android/"
)
upload_to_play_store(
track: "internal",
aab: "./android/app/build/outputs/bundle/release/app-release.aab",
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
skip_upload_changelogs: false
)
end
desc "Submit to Google Play Production"
lane :release do
gradle(
task: "bundle",
build_type: "Release",
project_dir: "android/"
)
upload_to_play_store(
track: "production",
aab: "./android/app/build/outputs/bundle/release/app-release.aab",
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
skip_upload_changelogs: false,
release_status: "completed",
rollout: "0.1" # 10% staged rollout
)
end
end
// fastlane/metadata/en-US/whats_new.txt
- Bug fixes and performance improvements
- New feature: Dark mode support
- Improved user experience
// fastlane/metadata/en-US/description.txt
MyApp is the best app for managing your daily tasks. With intuitive design and powerful features, you can stay organized and productive.
Features:
- Create and manage tasks
- Set reminders and notifications
- Sync across devices
- Beautiful dark mode
- Customizable themes
// fastlane/metadata/en-US/keywords.txt
task,manager,productivity,organize,reminders
// fastlane/metadata/en-US/name.txt
MyApp
// fastlane/metadata/en-US/short_description.txt
The best task manager app
<!-- android/app/src/main/res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="app_description">The best task manager app for staying organized and productive.</string>
<string name="app_short_desc">Manage your tasks efficiently</string>
</resources>
# fastlane/Fastfile
desc "Generate screenshots"
lane :screenshots do
capture_screenshots(
scheme: "MyApp",
devices: [
"iPhone 14 Pro Max",
"iPhone 14 Pro",
"iPhone 14",
"iPad Pro (12.9-inch) (6th generation)"
],
languages: ["en-US", "es-ES", "fr-FR"],
clear_previous_screenshots: true
)
frame_screenshots(
white: true
)
end
# fastlane/Fastfile
desc "Generate Android screenshots"
lane :android_screenshots do
capture_android_screenshots(
locales: ["en-US", "es-ES", "fr-FR"],
clear_previous_screenshots: true
)
end
// ASO Configuration
interface ASOConfig {
title: string;
subtitle: string;
keywords: string[];
description: string;
promotionalText: string;
screenshots: string[];
previewVideo?: string;
}
const asoConfig: ASOConfig = {
title: "MyApp - Task Manager",
subtitle: "Organize your life",
keywords: [
"task",
"manager",
"productivity",
"organize",
"reminders",
"to-do",
"planner",
"schedule"
],
description: `
MyApp is the best app for managing your daily tasks. With intuitive design and powerful features, you can stay organized and productive.
Features:
- Create and manage tasks
- Set reminders and notifications
- Sync across devices
- Beautiful dark mode
- Customizable themes
- Priority levels
- Due dates
- Categories and tags
- Search and filter
- Export and share
Download MyApp today and take control of your tasks!
`,
promotionalText: "New: Dark mode support!",
screenshots: [
"screenshots/iphone-1.png",
"screenshots/iphone-2.png",
"screenshots/iphone-3.png",
"screenshots/iphone-4.png",
"screenshots/iphone-5.png",
"screenshots/iphone-6.png",
],
previewVideo: "videos/app-preview.mp4",
};
// ASO Analyzer
class ASOAnalyzer {
/**
* Analyze ASO score
*/
analyzeASO(config: ASOConfig): {
score: number;
suggestions: string[];
} {
const suggestions: string[] = [];
let score = 0;
// Title analysis
if (config.title.length >= 10 && config.title.length <= 30) {
score += 20;
} else {
suggestions.push("Title should be between 10-30 characters");
}
// Subtitle analysis
if (config.subtitle.length >= 10 && config.subtitle.length <= 30) {
score += 20;
} else {
suggestions.push("Subtitle should be between 10-30 characters");
}
// Keywords analysis
if (config.keywords.length >= 5 && config.keywords.length <= 10) {
score += 20;
} else {
suggestions.push("Use 5-10 relevant keywords");
}
// Description analysis
if (config.description.length >= 100 && config.description.length <= 4000) {
score += 20;
} else {
suggestions.push("Description should be between 100-4000 characters");
}
// Screenshots analysis
if (config.screenshots.length >= 5 && config.screenshots.length <= 10) {
score += 20;
} else {
suggestions.push("Include 5-10 screenshots");
}
return { score, suggestions };
}
/**
* Generate keywords
*/
generateKeywords(description: string): string[] {
const words = description
.toLowerCase()
.replace(/[^\w\s]/g, '')
.split(/\s+/);
const stopWords = new Set([
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through',
'during', 'before', 'after', 'above', 'below', 'between', 'under',
'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where',
'why', 'how', 'all', 'each', 'few', 'more', 'most', 'other', 'some',
'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than',
'too', 'very', 'can', 'will', 'just', 'should', 'now'
]);
const wordCount = new Map<string, number>();
for (const word of words) {
if (!stopWords.has(word) && word.length > 2) {
wordCount.set(word, (wordCount.get(word) || 0) + 1);
}
}
return Array.from(wordCount.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([word]) => word);
}
}
# fastlane/Fastfile
desc "Add beta testers"
lane :add_testers do
testflight(
api_key_path: "./fastlane/api_key.json",
skip_waiting_for_build_processing: true,
changelog: "Bug fixes and improvements",
groups: ["Internal Testers", "External Testers"],
distribute_external: true,
notify_external_testers: true
)
end
desc "Add tester"
lane :add_tester do
testflight_add_tester(
email: "[email protected]",
first_name: "John",
last_name: "Doe",
groups: ["External Testers"]
)
end
# fastlane/Fastfile
desc "Add internal testers"
lane :add_internal_testers do
upload_to_play_store(
track: "internal",
aab: "./android/app/build/outputs/bundle/release/app-release.aab",
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
skip_upload_changelogs: true
)
end
desc "Add closed testers"
lane :add_closed_testers do
upload_to_play_store(
track: "beta",
aab: "./android/app/build/outputs/bundle/release/app-release.aab",
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
skip_upload_changelogs: false
)
end
// npm install react-native-code-push
import CodePush from 'react-native-code-push';
// Wrap your root component
const App = () => {
return (
<YourApp />
);
};
// Configure CodePush
const codePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
installMode: CodePush.InstallMode.IMMEDIATE,
updateDialog: {
appendReleaseDescription: true,
descriptionPrefix: "\n\nChange log:\n",
title: 'A new update is available!',
mandatoryUpdateMessage: 'A mandatory update is available.',
optionalIgnoreButtonLabel: 'Later',
optionalInstallButtonLabel: 'Install',
optionalUpdateMessage: 'An update is available. Would you like to install it?',
mandatoryContinueButtonLabel: 'Continue',
},
};
export default CodePush(codePushOptions)(App);
// app.json
{
"expo": {
"updates": {
"url": "https://u.expo.dev/YOUR_PROJECT_ID"
},
"runtimeVersion": {
"policy": "appVersion"
}
}
}
// app.config.ts
import { ExpoConfig, ConfigContext } from '@expo/config';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
updates: {
url: 'https://u.expo.dev/YOUR_PROJECT_ID',
},
runtimeVersion: {
policy: 'appVersion',
},
});
// Release Manager
class ReleaseManager {
/**
* Create release
*/
async createRelease(params: {
version: string;
buildNumber: number;
platform: 'ios' | 'android';
environment: 'testflight' | 'beta' | 'production';
changelog: string;
}): Promise<void> {
// 1. Increment version
await this.incrementVersion(params.version, params.buildNumber);
// 2. Build app
await this.buildApp(params.platform, params.environment);
// 3. Upload to store
await this.uploadToStore(params.platform, params.environment, params.changelog);
// 4. Create release notes
await this.createReleaseNotes(params.version, params.changelog);
}
/**
* Increment version
*/
private async incrementVersion(
version: string,
buildNumber: number
): Promise<void> {
// Update version in package.json
const packageJson = await fs.readJson('package.json');
packageJson.version = version;
await fs.writeJson('package.json', packageJson, { spaces: 2 });
// Update iOS version
if (Platform.OS === 'ios') {
const plistPath = 'ios/MyApp/Info.plist';
const plist = await fs.readXml(plistPath);
plist.plist.dict[0].string[1] = version; // CFBundleShortVersionString
plist.plist.dict[0].integer[0] = buildNumber; // CFBundleVersion
await fs.writeXml(plistPath, plist);
}
// Update Android version
if (Platform.OS === 'android') {
const gradlePath = 'android/app/build.gradle';
let gradle = await fs.readFile(gradlePath, 'utf-8');
gradle = gradle.replace(
/versionName ".*"/,
`versionName "${version}"`
);
gradle = gradle.replace(
/versionCode \d+/,
`versionCode ${buildNumber}`
);
await fs.writeFile(gradlePath, gradle);
}
}
/**
* Build app
*/
private async buildApp(
platform: 'ios' | 'android',
environment: 'testflight' | 'beta' | 'production'
): Promise<void> {
if (platform === 'ios') {
await exec('fastlane ios build');
} else {
await exec('fastlane android build');
}
}
/**
* Upload to store
*/
private async uploadToStore(
platform: 'ios' | 'android',
environment: 'testflight' | 'beta' | 'production',
changelog: string
): Promise<void> {
if (platform === 'ios') {
if (environment === 'testflight') {
await exec('fastlane ios testflight');
} else {
await exec('fastlane ios release');
}
} else {
if (environment === 'beta') {
await exec('fastlane android internal');
} else {
await exec('fastlane android release');
}
}
}
/**
* Create release notes
*/
private async createReleaseNotes(
version: string,
changelog: string
): Promise<void> {
const releaseNotes = `
# Release ${version}
## Changes
${changelog}
## Date
${new Date().toISOString()}
`;
await fs.writeFile(`RELEASE_NOTES/${version}.md`, releaseNotes);
}
}
// Version Manager
class VersionManager {
/**
* Bump version
*/
bumpVersion(
currentVersion: string,
type: 'major' | 'minor' | 'patch'
): string {
const [major, minor, patch] = currentVersion.split('.').map(Number);
switch (type) {
case 'major':
return `${major + 1}.0.0`;
case 'minor':
return `${major}.${minor + 1}.0`;
case 'patch':
return `${major}.${minor}.${patch + 1}`;
default:
return currentVersion;
}
}
/**
* Get next version
*/
getNextVersion(
currentVersion: string,
changes: {
breaking: number;
features: number;
fixes: number;
}
): string {
if (changes.breaking > 0) {
return this.bumpVersion(currentVersion, 'major');
} else if (changes.features > 0) {
return this.bumpVersion(currentVersion, 'minor');
} else if (changes.fixes > 0) {
return this.bumpVersion(currentVersion, 'patch');
}
return currentVersion;
}
/**
* Compare versions
*/
compareVersions(v1: string, v2: string): number {
const [major1, minor1, patch1] = v1.split('.').map(Number);
const [major2, minor2, patch2] = v2.split('.').map(Number);
if (major1 !== major2) {
return major1 - major2;
} else if (minor1 !== minor2) {
return minor1 - minor2;
} else {
return patch1 - patch2;
}
}
}
// Rollout Manager
class RolloutManager {
/**
* Create staged rollout
*/
async createStagedRollout(params: {
version: string;
platform: 'ios' | 'android';
initialPercentage: number;
rolloutSchedule: Array<{
percentage: number;
delay: number; // in hours
}>;
}): Promise<void> {
// 1. Initial rollout
await this.rollout(params.platform, params.initialPercentage);
// 2. Schedule subsequent rollouts
for (const schedule of params.rolloutSchedule) {
setTimeout(() => {
this.rollout(params.platform, schedule.percentage);
}, schedule.delay * 60 * 60 * 1000);
}
}
/**
* Rollout to percentage
*/
async rollout(
platform: 'ios' | 'android',
percentage: number
): Promise<void> {
if (platform === 'android') {
await exec(`fastlane android rollout rollout:${percentage / 100}`);
} else {
// iOS doesn't support staged rollouts
console.log('iOS staged rollout not supported');
}
}
/**
* Monitor rollout
*/
async monitorRollout(params: {
version: string;
platform: 'ios' | 'android';
}): Promise<{
crashRate: number;
userRating: number;
activeUsers: number;
}> {
// Fetch metrics from analytics
const metrics = await this.fetchMetrics(params);
return metrics;
}
/**
* Rollback if needed
*/
async rollback(params: {
version: string;
platform: 'ios' | 'android';
}): Promise<void> {
if (platform === 'android') {
await exec('fastlane android rollback');
} else {
// iOS rollback requires app rejection
console.log('iOS rollback requires app rejection');
}
}
/**
* Fetch metrics
*/
private async fetchMetrics(params: {
version: string;
platform: 'ios' | 'android';
}): Promise<{
crashRate: number;
userRating: number;
activeUsers: number;
}> {
// Implement metrics fetching
return {
crashRate: 0.01,
userRating: 4.5,
activeUsers: 1000,
};
}
}
// Pre-Release Checklist
const preReleaseChecklist = [
{
task: 'Test on all supported devices',
category: 'Testing',
required: true,
},
{
task: 'Test on all supported OS versions',
category: 'Testing',
required: true,
},
{
task: 'Run automated tests',
category: 'Testing',
required: true,
},
{
task: 'Perform manual testing',
category: 'Testing',
required: true,
},
{
task: 'Review app metadata',
category: 'Metadata',
required: true,
},
{
task: 'Review screenshots',
category: 'Metadata',
required: true,
},
{
task: 'Update changelog',
category: 'Metadata',
required: true,
},
{
task: 'Check app size',
category: 'Performance',
required: false,
},
{
task: 'Check battery usage',
category: 'Performance',
required: false,
},
{
task: 'Check memory usage',
category: 'Performance',
required: false,
},
{
task: 'Review privacy policy',
category: 'Compliance',
required: true,
},
{
task: 'Check permissions',
category: 'Compliance',
required: true,
},
{
task: 'Review terms of service',
category: 'Compliance',
required: true,
},
{
task: 'Test with beta testers',
category: 'Beta Testing',
required: true,
},
{
task: 'Review feedback',
category: 'Beta Testing',
required: false,
},
];
// Release Checklist
const releaseChecklist = [
{
task: 'Increment version number',
category: 'Versioning',
required: true,
},
{
task: 'Update build number',
category: 'Versioning',
required: true,
},
{
task: 'Build production bundle',
category: 'Build',
required: true,
},
{
task: 'Sign with production certificate',
category: 'Build',
required: true,
},
{
task: 'Upload to app store',
category: 'Distribution',
required: true,
},
{
task: 'Submit for review',
category: 'Distribution',
required: true,
},
{
task: 'Monitor review status',
category: 'Distribution',
required: true,
},
{
task: 'Monitor crash rates',
category: 'Monitoring',
required: true,
},
{
task: 'Monitor user feedback',
category: 'Monitoring',
required: true,
},
{
task: 'Prepare rollback plan',
category: 'Monitoring',
required: false,
},
];
// Post-Release Checklist
const postReleaseChecklist = [
{
task: 'Monitor crash reports',
category: 'Monitoring',
required: true,
},
{
task: 'Monitor user feedback',
category: 'Monitoring',
required: true,
},
{
task: 'Monitor app ratings',
category: 'Monitoring',
required: true,
},
{
task: 'Monitor download numbers',
category: 'Monitoring',
required: true,
},
{
task: 'Review analytics',
category: 'Analytics',
required: true,
},
{
task: 'Address critical issues',
category: 'Support',
required: true,
},
{
task: 'Plan next release',
category: 'Planning',
required: false,
},
];
# Build iOS app
xcodebuild -workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-archivePath MyApp.xcarchive \
archive
# Export IPA
xcodebuild -exportArchive \
-archivePath MyApp.xcarchive \
-exportPath ./build \
-exportOptionsPlist ExportOptions.plist
# Build Android app bundle
./gradlew bundleRelease
# Upload to Play Console
# Use Google Play Console web interface or API
# ❌ Bad - Direct to production
Build → Submit → Release
# No testing!
# ✅ Good - Beta testing first
Build → TestFlight/Internal Testing → Fix issues → Release
# ❌ Bad - Incomplete metadata
Name: "My App"
Description: "App"
# No screenshots, no keywords
# ✅ Good - Complete metadata
Name: "My App - Task Manager"
Description: "Powerful task management app..."
Keywords: "task, todo, productivity"
Screenshots: All device sizes
31-mobile-development/mobile-ci-cd/) - Automated builds31-mobile-development/react-native-patterns/) - App patterns01-foundations/versioning/) - Version strategyxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience