Warianty kompilacji i konfiguracja środowiska
Definiuj warianty dev, staging i prod wraz z zasobami dla poszczególnych środowisk oraz konfiguracjami Dart-define
Warianty kompilacji i konfiguracja środowiska to bezpłatna lekcja Flutter Mobile Development na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Flutter Mobile Development, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.
Dlaczego warianty kompilacji mają znaczenie
Produkcyjna aplikacja Fluttera rzadko komunikuje się tylko z jednym backendem. Potrzebny jest wariant dev wskazujący lokalne lub testowe API, wariant staging dla zespołu QA oraz zabezpieczony wariant prod przeznaczony do sklepu.
- Flavors to nazwane warianty kompilacji, które mogą różnić się identyfikatorem aplikacji, ikoną, nazwą i podpisem.
- Konfiguracja środowiska to dane wstrzykiwane przez każdy wariant: bazowe adresy URL, flagi funkcji i klucze API.
Celem jest instalowanie wariantów dev, staging i prod obok siebie na jednym urządzeniu, z pełną izolacją i bez ryzyka pomyłki.
Dwie warstwy: natywne flavors i Dart-define
Przejrzysta konfiguracja rozdziela dwie kwestie:
- Natywne flavors (Android
productFlavors, iOSschemes/xcconfig) sterują tożsamością aplikacji: identyfikatorem pakietu, nazwą wyświetlaną i ikoną. - Wartości Dart-define sterują konfiguracją w czasie działania, odczytywaną przez kod Dart: adresem URL API, nazwą środowiska i poziomem logowania.
Natywne flavors pozwalają współistnieć wariantom dev, staging i prod na urządzeniu, z odrębnymi identyfikatorami, takimi jak com.acme.app.dev. Dart-define utrzymuje sekrety i adresy URL poza repozytorium kodu źródłowego oraz przypina je w czasie kompilacji.
Definiowanie bezpiecznego typowo enuma środowiska
Zacznij w Darcie od jednego źródła prawdy określającego dostępne środowiska. Enum zapobiega literówkom i umożliwia wyczerpującą obsługę za pomocą switch.
Ten fragment to czysty Dart, bez zależności od Fluttera, więc działa wszędzie.
enum Environment { dev, staging, prod }
String labelFor(Environment env) {
switch (env) {
case Environment.dev:
return 'Development';
case Environment.staging:
return 'Staging';
case Environment.prod:
return 'Production';
}
}
void main() {
for (final env in Environment.values) {
print('${env.name} -> ${labelFor(env)}');
}
}Odczytywanie Dart-define w czasie kompilacji
Flutter udostępnia stałe czasu kompilacji za pomocą String.fromEnvironment, bool.fromEnvironment i int.fromEnvironment. Przekazuje się je podczas kompilacji za pomocą --dart-define.
- Wartości są zapisywane w pliku binarnym; NIE są odczytywane w czasie działania z urządzenia.
- Zawsze podawaj
defaultValue, aby brak definicji powodował przewidywalne zachowanie.
Ponieważ są to wartości const, można je obliczać nawet w kontekstach const.
const String apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:8080',
);
const String envName = String.fromEnvironment(
'ENV',
defaultValue: 'dev',
);
const bool analyticsEnabled = bool.fromEnvironment(
'ANALYTICS',
defaultValue: false,
);
void main() {
print('env=$envName url=$apiUrl analytics=$analyticsEnabled');
}Budowanie obiektu AppConfig
Rozproszenie wywołań String.fromEnvironment po całej bazie kodu oznacza utratę kontroli. Zcentralizuj je w jednym niezmiennym obiekcie AppConfig, który rozwiążesz raz i przekażesz dalej.
Dzięki temu wszystkie różnice między wariantami znajdują się w jednym miejscu, które można poddać audytowi, a testowanie staje się proste: wystarczy utworzyć AppConfig z wybranymi wartościami.
class AppConfig {
final String envName;
final String apiUrl;
final bool analyticsEnabled;
const AppConfig({
required this.envName,
required this.apiUrl,
required this.analyticsEnabled,
});
factory AppConfig.fromEnvironment() {
return const AppConfig(
envName: String.fromEnvironment('ENV', defaultValue: 'dev'),
apiUrl: String.fromEnvironment('API_URL',
defaultValue: 'http://localhost:8080'),
analyticsEnabled:
bool.fromEnvironment('ANALYTICS', defaultValue: false),
);
}
bool get isProd => envName == 'prod';
}
void main() {
final config = AppConfig.fromEnvironment();
print('Running in ${config.envName} -> ${config.apiUrl}');
}Przekazywanie Dart-define z wiersza poleceń
Konfigurację wstrzykuje się podczas uruchamiania lub kompilowania. Każdy parametr --dart-define ustawia jeden klucz. Połącz go z natywnym flavor za pomocą --flavor.
--flavor stagingwybiera natywny wariant (identyfikator, ikonę i nazwę).--dart-definezasila obiektAppConfig.
Wpisywanie tych parametrów za każdym razem jest podatne na błędy, dlatego zespoły zapisują je w skryptach lub plikach (w następnej scenie).
flutter run \
--flavor staging \
--target lib/main.dart \
--dart-define=ENV=staging \
--dart-define=API_URL=https://staging.api.acme.com \
--dart-define=ANALYTICS=true
flutter build apk \
--release \
--flavor prod \
--dart-define=ENV=prod \
--dart-define=API_URL=https://api.acme.com \
--dart-define=ANALYTICS=trueDart-define-from-file dla czystszego CI
Długie ciągi parametrów --dart-define są kruche. Flutter obsługuje --dart-define-from-file, który odczytuje plik JSON (lub plik w stylu .env) zawierający pary klucz/wartość.
- Utrzymuj jeden plik dla każdego środowiska:
config/dev.json,config/staging.json,config/prod.json. - Pliki niezawierające sekretów umieszczaj w repozytorium, a pliki z sekretami wstrzykuj w CI z bezpiecznego magazynu.
Pokazano przykładowy plik config/prod.json oraz jego użycie. Klucze odpowiadają bezpośrednio odwołaniom fromEnvironment.
// config/prod.json
{
"ENV": "prod",
"API_URL": "https://api.acme.com",
"ANALYTICS": true
}
// Invocation:
// flutter build appbundle --release \
// --flavor prod \
// --dart-define-from-file=config/prod.jsonAndroid: productFlavors
Na Androidzie flavors deklaruje się w pliku android/app/build.gradle. Każdy flavor zmienia przyrostek identyfikatora aplikacji i nazwę, dzięki czemu kompilacje można instalować obok siebie.
applicationIdSuffixdopisuje wartość do bazowego identyfikatora (np.com.acme.app.dev).resValuezmienia etykietę programu uruchamiającego dla danego flavor.
Przed zdefiniowaniem flavors wymagany jest wpis flavorDimensions.
android {
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev"
resValue "string", "app_name", "Acme Dev"
}
staging {
dimension "env"
applicationIdSuffix ".staging"
resValue "string", "app_name", "Acme Staging"
}
prod {
dimension "env"
resValue "string", "app_name", "Acme"
}
}
}iOS: Schemes i xcconfig
Na iOS flavors odpowiadają schemes Xcode, opartym na konfiguracjach kompilacji i plikach .xcconfig. Każdy scheme ustawia odrębny PRODUCT_BUNDLE_IDENTIFIER i nazwę wyświetlaną.
- Utwórz konfiguracje takie jak
Debug-dev,Release-proditd. - Plik
.xcconfigdla każdego środowiska nadpisuje identyfikator pakietu iDISPLAY_NAME, odczytywane wInfo.plistza pomocą$(DISPLAY_NAME).
Flutter dopasowuje --flavor prod do scheme Xcode o nazwie prod.
// ios/Flutter/staging.xcconfig
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = com.acme.app.staging
DISPLAY_NAME = Acme Staging
// In Info.plist:
// <key>CFBundleDisplayName</key>
// <string>$(DISPLAY_NAME)</string>Zasoby dla poszczególnych środowisk
Flavors często wymagają różnych zasobów: kolorowego banera DEV, ikony aplikacji staging czy innej konfiguracji Firebase.
- Organizuj zasoby w folderach takich jak
assets/dev/iassets/prod/, a ścieżkę wybieraj w czasie działania na podstawieAppConfig. - W przypadku natywnych ikon Android automatycznie rozwiązuje
src/dev/res, a iOS używa katalogów zasobów dla poszczególnych konfiguracji. - Umieszczaj pliki
google-services.jsonspecyficzne dla flavor w kataloguandroid/app/src/<flavor>/.
Część Dart po prostu wyprowadza ścieżkę zasobu z aktywnego środowiska.
class AssetPaths {
final String envName;
const AssetPaths(this.envName);
String get logo => 'assets/$envName/logo.png';
String get configBanner =>
envName == 'prod' ? '' : 'assets/$envName/banner.png';
}
void main() {
for (final env in ['dev', 'staging', 'prod']) {
final paths = AssetPaths(env);
print('$env logo: ${paths.logo}');
}
}Podłączanie AppConfig do main()
Rozwiąż konfigurację raz podczas uruchamiania i udostępnij ją drzewu widgetów (za pomocą InheritedWidget, providera lub service locatora). Unikaj odczytywania fromEnvironment głęboko w widgetach.
- Zbuduj konfigurację przed wywołaniem
runApp. - W przypadku kompilacji innych niż prod wyświetlaj widoczny baner środowiska, aby uniknąć pomyłek zespołu QA.
Poniższy fragment to kod frameworka Flutter, więc nie można uruchomić go samodzielnie, ale pokazuje kanoniczny punkt wejścia.
import 'package:flutter/material.dart';
void main() {
final config = AppConfig.fromEnvironment();
runApp(MyApp(config: config));
}
class MyApp extends StatelessWidget {
final AppConfig config;
const MyApp({super.key, required this.config});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Acme (${config.envName})',
debugShowCheckedModeBanner: !config.isProd,
home: const Scaffold(body: Center(child: Text('Home'))),
);
}
}Szybkie sprawdzenie: gdzie znajduje się konfiguracja
Skonfigurowali Państwo warianty dev, staging i prod. Sprawdźmy, czy rozumieją Państwo sposób działania wartości Dart-define.
Podsumowanie
Zbudowali Państwo kompletną strategię konfiguracji flavorów i środowisk:
- Dwie warstwy: natywne flavors (Android
productFlavors, iOS schemes/xcconfig) określają tożsamość aplikacji, a dart-define ustawia konfigurację w czasie działania. - Bezpieczeństwo typów: jeden enum
Environmenti niezmiennyAppConfig, rozwiązywany raz za pomocąAppConfig.fromEnvironment(). - Wstrzykiwanie: przekaż
--flavorrazem z--dart-definealbo uporządkuj konfigurację za pomocą--dart-define-from-file=config/<env>.json. - Zasoby: foldery dla poszczególnych środowisk i natywne nadpisania zasobów dla ikon, banerów i konfiguracji Firebase.
- Najważniejszy wniosek: dart-define działa w czasie kompilacji i jest zapisywany w pliku binarnym, więc każda zmiana konfiguracji wymaga ponownej kompilacji.
Efekt: warianty dev, staging i prod można instalować obok siebie, z pełną izolacją i bez ryzyka pomyłki.
Ucz się Dart dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 22
- Lekcje
- 88
Często zadawane pytania
Czy lekcja „Warianty kompilacji i konfiguracja środowiska” jest bezpłatna?
Tak — pełny tekst „Warianty kompilacji i konfiguracja środowiska” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Flutter Mobile Development, przejdź na CoddyKit PRO. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.
Co nauczysz się w „Warianty kompilacji i konfiguracja środowiska”?
Definiuj warianty dev, staging i prod wraz z zasobami dla poszczególnych środowisk oraz konfiguracjami Dart-define Ćwiczysz Flutter Mobile Development z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Flutter Mobile Development?
Nie wymagamy żadnego doświadczenia. Flutter Mobile Development w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Warianty kompilacji i konfiguracja środowiska”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Flutter Mobile Development?
Tak. Każda lekcja Flutter Mobile Development zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Warianty kompilacji i konfiguracja środowiska
- Automatyczne potoki z Fastlane i GitHub Actions
- Raportowanie awarii i stack trace'y z symbolami
- Zdalna konfiguracja, flagi funkcji i etapowe wdrażanie