5-qism · Senior daraja·13-bob
CI/CD, build boshqaruvi va deployment
Flutter ilovasini dev/staging/prod muhitlarga bo'lish, imzolash, versiyalash, avtomatik CI/CD quvur liniyasi qurish va do'konlarga xavfsiz chiqarish — Senior dasturchidan kutiladigan operatsion bilim.
Kod yozish — ishning yarmi, xolos. Senior Flutter dasturchidan ko'pincha "ilova qanday qilib xavfsiz, takrorlanuvchan tarzda foydalanuvchiga yetib boradi" degan savolga ham javob berish kutiladi. Intervyularda bu mavzu ko'pincha "keyingi darajaga o'tish" belgisi sifatida ko'riladi — junior dasturchi kodni yozadi, middle uni test qiladi, senior esa butun yetkazib berish jarayonini loyihalaydi va ishonchli qiladi. Bu bobda build muhitlaridan tortib do'konga chiqarishgacha bo'lgan butun yo'lni ko'rib chiqamiz.
1. Build flavor'lar va muhitlar (dev/staging/production)#
Har qanday jiddiy ilova kamida uchta muhitda ishlaydi: dev (dasturchilar uchun, tez-tez o'zgaradigan backend), staging (QA va mijoz demo uchun, production'ga o'xshash), production (haqiqiy foydalanuvchilar). Flutter'da buni ikki usulda qurish mumkin.
1-usul — --dart-define orqali muhit o'zgaruvchilari:
flutter build apk --release \
--dart-define=API_BASE_URL=https://api.prod.example.com \
--dart-define=ENV=production \
--dart-define=SENTRY_DSN=https://xxx@sentry.io/123class Env {
static const apiBaseUrl = String.fromEnvironment(
'API_BASE_URL',
defaultValue: 'https://api.dev.example.com',
);
static const name = String.fromEnvironment('ENV', defaultValue: 'development');
static bool get isProd => name == 'production';
}Bu usul oddiy, lekin barcha o'zgaruvchilarni har bir buyruqda qo'lda ko'rsatish kerak —
odatda --dart-define-from-file=config/prod.json bilan birga ishlatiladi, shunda
har bir muhit uchun alohida JSON konfiguratsiya fayli saqlanadi.
2-usul — native flavor'lar (Android productFlavors, iOS scheme'lar):
// android/app/build.gradle
android {
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev"
resValue "string", "app_name", "MyApp Dev"
}
staging {
dimension "env"
applicationIdSuffix ".staging"
resValue "string", "app_name", "MyApp Staging"
}
production {
dimension "env"
resValue "string", "app_name", "MyApp"
}
}
}flutter build apk --flavor production -t lib/main_production.dartNative flavor'lar afzalligi — har bir muhit alohida applicationId/bundle ID bilan
qurilishi mumkin, ya'ni dev va prod versiyalarini bir telefonda bir vaqtda o'rnatib,
solishtirib ko'rish mumkin. Katta jamoalarda ko'pincha ikkala usul birga ishlatiladi:
flavor — qaysi ilova ekanligini (ID, nom, ikonka) belgilaydi, dart-define — ichki
konfiguratsiyani (API URL, feature flag'lar) beradi.
2. Ilovani imzolash — Android va iOS#
Do'konlar imzosiz ilovani qabul qilmaydi. Imzolash — kodning haqiqatan ham sizdan kelganini va o'zgartirilmaganini kafolatlaydi.
Android — keystore:
keytool -genkey -v -keystore upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload# android/key.properties (Git'ga QO'SHILMAYDI, .gitignore'da)
storePassword=***
keyPassword=***
keyAlias=upload
storeFile=/absolute/path/upload-keystore.jkskey.properties'dagi maxfiy ma'lumotlar build.gradle'da o'qiladi va signingConfigs
orqali release build'ga ulanadi. Keystore faylini yo'qotish — falokat: Google Play
"App Signing"ga o'tgan bo'lsangiz, upload key'ni tiklash mumkin (Google master key'ni
saqlaydi), lekin eski usulda (legacy signing) keystore'ni yo'qotsangiz, o'sha ilovani
boshqa hech qachon yangilab bo'lmaydi — yangi paket sifatida qayta chiqarishga
to'g'ri keladi. Shuning uchun keystore backup'i — 1Password/sirlar menejeri yoki
kamida uchta joyda saqlanadigan zaxira fayl bo'lishi shart.
iOS — sertifikat va provisioning profile:
iOS imzolash uch qatlamdan iborat:
Xcode'da "Automatically manage signing" yoqilgan bo'lsa, bu jarayon avtomatlashadi, lekin CI serverida (inson interfeysi yo'q joyda) odatda Fastlane match ishlatiladi — u sertifikat va profillarni shifrlangan Git repo'da saqlaydi va har bir CI ishga tushganda avtomatik yuklab, o'rnatadi. Bu — jamoa a'zolari orasida sertifikatlarni qo'lda almashtirish tartibsizligining oldini oladi.
3. Versiyalash#
# pubspec.yaml
version: 2.4.1+372.4.1 — foydalanuvchiga ko'rinadigan versiya (semantic versioning: MAJOR.MINOR.PATCH),
37 — build number (versionCode Android'da, CFBundleVersion iOS'da). Build
number har bir do'konga yuborilgan build'da o'sishi SHART — bir xil build number
bilan ikkinchi marta yuklab bo'lmaydi, do'kon uni rad etadi. Versiya nomi esa
foydalanuvchi uchun — semantik ma'noga ega bo'lishi kerak:
| Qism | Qachon oshiriladi |
|---|---|
| MAJOR (2.x.x) | Katta, orqaga mos kelmaydigan o'zgarish (masalan, butunlay yangi UI, hisob tizimi almashtirilishi) |
| MINOR (x.4.x) | Yangi funksiya qo'shildi, eski funksiyalar buzilmadi |
| PATCH (x.x.1) | Xatolik tuzatildi, funksionallik o'zgarmadi |
CI pipeline'da build number'ni odatda avtomatik generatsiya qilish tavsiya etiladi (masalan, Git commit soni yoki CI run raqami asosida), shunda dasturchilar uni qo'lda oshirishni unutib qolmaydi.
4. CI/CD quvur liniyasi qurish#
Tipik pipeline bosqichlari:
Trigger
Git branch'ga push yoki Pull Request ochilganda pipeline avtomatik ishga tushadi (masalan,
main'ga push — staging build,release/*tag — production build).Bog'liqliklarni o'rnatish va tahlil
flutter pub get, so'ngflutter analyze— statik tahlil xatolarini ushlaydi, build boshlanishidan oldin tezkor signal beradi.Test
flutter test --coverage— barcha unit va widget testlar ishga tushadi. Muvaffaqiyatsiz test pipeline'ni to'xtatadi, build boshlanmaydi.Build
Platformaga mos flavor va muhit bilan release build yig'iladi (
flutter build appbundle,flutter build ipa).Imzolash va yuklash
Build imzolanadi (agar oldingi bosqichda qilinmagan bo'lsa) va TestFlight/Play Console Internal Testing'ga avtomatik yuklanadi (Fastlane
deliver/supplyorqali).Xabar
Slack/Telegram'ga "Build #37 staging'ga yuklandi" degan xabar yuboriladi — jamoa qo'lda tekshirib yurmasin.
GitHub Actions misoli (soddalashtirilgan):
name: CI
on:
push:
branches: [main]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.0"
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
- run: flutter build appbundle --release --flavor production
- name: Deploy to Play Console
run: bundle exec fastlane android deployFastlane — build/imzolash/do'konga yuklash qadamlarini bitta buyruqqa jamlaydigan
Ruby asosidagi vosita. Fastfile'da "lane" (yo'l) deb ataluvchi qadamlar zanjiri
yoziladi (fastlane android deploy, fastlane ios beta), bu esa turli CI tizimlari
(GitHub Actions, Bitrise, Codemagic, GitLab CI) orasida bir xil ishlaydi — mantiq CI
konfiguratsiyasidan ajratiladi. Codemagic — Flutter uchun maxsus qurilgan CI
xizmati, macOS build muhitini alohida sozlashga hojat qoldirmaydi va ko'pincha kichik
jamoalar uchun eng tez yo'l bo'ladi.
5. Do'konlarga topshirish va rad etilish sabablari#
| Sabab | Izoh |
|---|---|
| Metama'lumot yetishmasligi | Maxfiylik siyosati havolasi yo'q, skrinshotlar eskirgan yoki platformaga mos emas |
| Ruxsatlar asoslanmagan | Kamera/joylashuv ruxsati so'raladi, lekin ilova funksiyasida ishlatilmaydi (Apple buni qattiq tekshiradi) |
| Beqaror build | Ko'rib chiquvchi ilovani ochganda darhol qulaydi (crash) — har doim toza qurilmada sinab ko'rish shart |
| Test hisob ma'lumotlari yo'q | Login talab qiladigan ilova uchun sharh (review) uchun demo login/parol berilmagan |
| Guideline buzilishi (iOS) | Masalan, tashqi to'lov tizimiga havola (Apple'ning 30% komissiyasidan chetlab o'tish) |
| Spam/dublikat | Bir xil funksionallikka ega bir nechta ilova yuborish |
Production'ga chiqarishdan oldin kamida bitta "release candidate" build'ni real qurilmada, tarmoqdan uzilgan holatda ham sinab ko'rish — offline holatdagi xatolar ko'pincha faqat shu bosqichda ko'rinadi.
6. Staged rollout va "code push"#
Google Play va App Store ikkalasi ham bosqichma-bosqich chiqarishni qo'llaydi — yangi versiya darhol 100% foydalanuvchiga emas, dastlab 5-10% ga chiqadi, keyin crash-rate va reyting kuzatilib, asta-sekin 100% gacha oshiriladi. Agar kritik xato topilsa, rollout to'xtatiladi va eski versiyaga avtomatik qaytariladi — bu butun foydalanuvchi bazasini buzilgan build'dan himoya qiladi.
"Code push" (Dart kodi/asset'larni do'kon ko'rib chiqishisiz yangilash) Flutter'da rasmiy qo'llab-quvvatlanmaydi — bu Apple/Google qoidalariga zid bo'lishi mumkin, chunki native binar ichidagi ijro etiluvchi kodni chetlab o'tadi. Amalda faqat remote config (Firebase Remote Config kabi) orqali feature flag'larni yoqib/ o'chirish yoki matn/rasm kabi kontentni serverdan olib kelish qonuniy va xavfsiz yo'l hisoblanadi.
7. Production monitoring#
Ilova do'konga chiqqandan keyin ish tugamaydi — aksincha, shu yerdan boshlanadi.
- Crashlytics / Sentry — qo'lga olinmagan xatoliklarni (uncaught exceptions) avtomatik yig'adi, stack trace, qurilma modeli, OS versiyasi va foydalanuvchi harakatlari tarixini (breadcrumbs) beradi.
- Flutter'da
FlutterError.onErrorvaPlatformDispatcher.instance.onErrorikkalasini ham sozlash kerak — birinchisi widget qurish davridagi xatolarni, ikkinchisi esa asinxron/platform darajasidagi xatolarni ushlaydi:
void main() {
FlutterError.onError = (details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runZonedGuarded(() => runApp(const MyApp()), (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
});
}- Crash-free rate (masalan, 99.5%) — jamoaning asosiy sifat ko'rsatkichlaridan biri; har bir yangi release'dan keyin kuzatib boriladi.
- Analitika (Firebase Analytics, Amplitude) va performance monitoring (Firebase Performance) — funksional emas, lekin "ilova ishlayaptimi" degan savolga texnik javobni to'ldiradi.
Tez-tez so'raladigan intervyu savollari#
Savol: --dart-define va native flavor'lar orasidagi farq nima, va qachon qaysi birini tanlaysiz?
Javob: --dart-define — build vaqtida Dart kodiga o'zgaruvchi uzatish usuli; u faqat
String.fromEnvironment/bool.fromEnvironment orqali o'qiladigan qiymatlar beradi va
bitta binar ichida turlicha konfiguratsiya bilan ishlaydi. Native flavor — butunlay
alohida applicationId/bundle ID, ikonka va nom bilan alohida ilova yaratadi.
Faqat konfiguratsiya (API URL, feature flag) kerak bo'lsa — dart-define yetarli. Agar
dev va prod versiyalarini bitta qurilmada bir vaqtda o'rnatib solishtirish, yoki har
biriga alohida ikonka/nom berish kerak bo'lsa — flavor shart. Katta jamoalarda odatda
ikkalasi birga ishlatiladi.
Savol: Google Play "App Signing"ga o'tgan loyihada keystore yo'qolsa nima bo'ladi, va bu legacy signing'dan nimasi bilan farq qiladi?
Javob: App Signing'da Google sizning "app signing key"ingizni o'zida saqlaydi, siz esa faqat "upload key" bilan ishlaysiz — upload key yo'qolsa, Google Play Console orqali identifikatsiyadan o'tib, yangi upload key so'rash mumkin, ilova uzluksiz yangilanaveradi. Legacy (eski) signing'da esa faqat bitta keystore bor va u — ilovaning yagona haqiqiy identifikatori; yo'qolsa, o'sha paket nomi bilan boshqa hech qachon yangilab bo'lmaydi, ilova amalda "o'lik" bo'lib qoladi va foydalanuvchilar yangi (boshqa paket nomli) ilovaga qo'lda o'tishga majbur bo'ladi.
Savol: iOS'da "Automatically manage signing" bo'lsa-yu, nega CI serverida ko'pincha Fastlane match ishlatiladi?
Javob: Xcode'ning avtomatik boshqaruvi — GUI orqali, insonning Apple ID bilan tizimga kirgan holatidagi jarayon; CI serverida grafik interfeys va inson yo'q. Fastlane match sertifikat va provisioning profillarni shifrlangan holda alohida Git repo'da saqlaydi, CI run'da ularni avtomatik deshifrlab, keychain'ga o'rnatadi. Bu jamoaning har bir a'zosi (va har bir CI runner) bir xil, markazlashgan sertifikatdan foydalanishini kafolatlaydi — "kimdadir eski sertifikat, kimdadir yangisi" muammosi yo'qoladi.
Savol: Build number'ni CI'da avtomatik generatsiya qilishning qaysi usullari bor, va nega bu qo'lda qilishdan yaxshiroq?
Javob: Ikki keng tarqalgan usul: (1) git rev-list --count HEAD — commit sonini
build number sifatida ishlatish, u monoton o'sadi va takrorlanmaydi; (2) CI tizimining
o'z run raqami (masalan, GitHub Actions'da github.run_number). Qo'lda oshirishning
xavfi — dasturchi uni unutib qoladi, natijada bir xil build number bilan ikkinchi
marta yuklashga urinilganda do'kon build'ni rad etadi, va bu odatda release kuni,
eng noqulay vaqtda aniqlanadi.
Savol: Staged rollout paytida crash-rate ko'tarilib ketsa, jamoa qanday qaror qabul qilish jarayonidan o'tadi?
Javob: Avval oldingi versiya bilan solishtirilgan nisbiy crash-rate ko'riladi (mutlaq son emas — kichik foizli rollout'da kam sonli foydalanuvchi ham foizni "sakratishi" mumkin). Agar chindan ham sezilarli o'sish tasdiqlansa, rollout darhol to'xtatiladi (do'kon konsolida bir tugma bilan), yangi foydalanuvchilarga eski versiya tarqatiladi. Parallel ravishda xato Crashlytics/Sentry stack trace'i orqali diagnostika qilinadi, tuzatilgan build tayyorlanadi va qaytadan kichik foizdan boshlab rollout qilinadi — to'g'ridan-to'g'ri 100%ga chiqarilmaydi.
Savol: Nega Flutter'da "code push" (OTA — over-the-air kod yangilash) rasmiy qo'llab-quvvatlanmaydi?
Javob: Apple App Store Review Guidelines va Google Play qoidalari ilovaning asosiy funksionalligini o'zgartiruvchi ijro etiluvchi kodni do'kon tekshiruvidan tashqarida yangilashni taqiqlaydi (bu ayniqsa Apple uchun qattiq qoida — bunday ilova rad etilishi yoki hisobdan chiqarilishi mumkin). Flutter binar sifatida kompilyatsiya qilinadi va native ilova bilan bir xil qoidalarga bo'ysunadi. Shu sabab amaliyotda faqat kontent va konfiguratsiya darajasidagi o'zgarishlar (Remote Config orqali feature flag, matn, rasm) server orqali yangilanadi — kod mantig'i emas.
Savol: flutter analyze va flutter test pipeline'da qaysi tartibda, va nega build'dan oldin turishi kerak?
Javob: Odatda pub get → analyze → test → build tartibida. Analyze va test
— soniyalarda-daqiqalarda tugaydi, build esa (ayniqsa iOS) o'nlab daqiqa olishi mumkin.
Agar kodda oddiy statik xato yoki muvaffaqiyatsiz test bo'lsa, buni build'dan oldin
ushlash CI resurslarini behuda sarflamaydi va jamoaga tezroq signal beradi — "fail
fast" tamoyili.
Savol: App Store review'da "Guideline 3.1.1" (in-app purchase) bilan bog'liq rad etish nimani anglatadi, va Senior dasturchi buni qanday oldini oladi?
Javob: Bu qoida — raqamli tovar/xizmat (masalan, ilova ichidagi premium funksiya)
sotilayotgan bo'lsa, Apple'ning o'z In-App Purchase tizimidan (u 15-30% komissiya
oladi) foydalanish shart, tashqi to'lov havolasiga yo'naltirish taqiqlanadi degan
ma'noni bildiradi. Senior dasturchi buni arxitektura bosqichida — feature spec
yozilayotganda — aniqlaydi: agar mahsulot raqamli bo'lsa, in_app_purchase paketi
orqali qurish rejalashtiriladi, fizik tovar/xizmat (masalan, taksi, yetkazib berish)
bo'lsa, tashqi to'lov qonuniy hisoblanadi.
Savol: Ilovada Sentry yoki Crashlytics'dan tashqari nega Analytics/Performance monitoring ham kerak?
Javob: Crashlytics faqat qulagan holatlarni ko'rsatadi — lekin ilova qulamay turib ham yomon ishlashi mumkin (sekin ekran ochilishi, muvaffaqiyatsiz tarmoq so'rovlari, foydalanuvchi voronkasidan (funnel) chiqib ketish). Performance monitoring sekin ekranlarni, Analytics esa qaysi bosqichda foydalanuvchilar to'xtab qolayotganini ko'rsatadi — bular birgalikda "ilova ishlayaptimi" degan savolga to'liqroq javob beradi, faqat "qulamayaptimi" emas.
Savol: Bitta CI pipeline'da bir nechta flavor (dev/staging/production) uchun build qanday tashkil qilinadi — parallel yoki ketma-ket?
Javob: Odatda matrix strategy ishlatiladi — CI konfiguratsiyasida flavor
ro'yxati beriladi va har biri uchun alohida, parallel job ishga tushadi (masalan,
GitHub Actions'da strategy.matrix). Bu umumiy pipeline vaqtini qisqartiradi, chunki
uchta flavor ketma-ket emas, bir vaqtda quriladi. Testlar esa odatda flavor'dan
mustaqil bo'lgani uchun faqat bir marta, matrix'dan tashqarida ishga tushiriladi —
takrorlanishning oldini olish uchun.
Ko'p uchraydigan xato javoblar#
| Savol/vaziyat | Xato javob | To'g'ri javob |
|---|---|---|
| "Build number'ni qanday boshqarasiz?" | "Har safar qo'lda +1 qilamiz" | CI'da avtomatik generatsiya qilinadi (Git commit soni yoki CI run raqami asosida), inson xatosiga o'rin qoldirilmaydi |
| "Keystore'ni qayerda saqlaysiz?" | "Repo'da, qulay bo'lishi uchun" | Hech qachon Git'ga qo'shilmaydi — sirlar menejerida yoki CI'ning shifrlangan secrets bo'limida |
| "Xatoni qanday bilasiz?" | "Foydalanuvchilar shikoyat qilganda" | Crashlytics/Sentry orqali proaktiv, foydalanuvchi shikoyat qilmasdan oldin |
| "Yangi versiya buzuq chiqsa nima qilasiz?" | "Tezda tuzatib, yangi versiya chiqaramiz" | Staged rollout'ni to'xtatib, avtomatik eski versiyaga qaytariladi — tuzatish parallel tayyorlanadi |
Uch bosqichli CI pipeline loyihalang
O'z (yoki namunaviy) Flutter loyihangiz uchun to'liq CI konfiguratsiya faylini yozing: kod tahlili, testlar, dev va production flavor'lar uchun alohida build bosqichlari, va build muvaffaqiyatli tugagach jamoaga xabar yuborish. Nega har bir bosqichni o'sha tartibda joylashtirganingizni asoslang.
Maslahatni ko'rsatish
GitHub Actions yoki istalgan CI tizimida: analyze → test → build bosqichlarini yozing, so'ng production flavor bilan signed APK/AAB yig'ib, Slack'ga xabar yuborishni qo'shing.