Decyzja startowa: czy Flutter to właściwa droga do pierwszej aplikacji mobilnej
Kryteria wyboru na zimno
Przed instalacją czegokolwiek odpowiedz na kilka konkretnych pytań. To zawęzi zakres i pozwoli oszczędzić godziny:
Czy chcesz jednocześnie celować w Android i iOS bez duplikowania pracy? Jeśli tak, Flutter zwykle daje najszybszy efekt „jednego kodu na dwa systemy”.
Czy interfejs ma być spójny i bardzo płynny nawet na słabszych urządzeniach? Silnik renderujący Fluttera działa niezależnie od natywnych widoków, co co do zasady przekłada się na przewidywalność UI.
Czy dopuszczasz naukę nowego języka (Dart) w zamian za prosty model UI oparty o widgety? Jeśli tak, wejście jest relatywnie łagodne.
Czy planujesz używać natywnych API (np. Apple HealthKit) już w pierwszym wydaniu? Da się, ale wymaga mostków (platform channels) lub gotowych pluginów.
Jeśli na co najmniej dwa pierwsze pytania odpowiedź brzmi „tak”, Flutter jest rozsądnym wyborem do pierwszej aplikacji mobilnej krok po kroku. Gdy kluczowa jest tylko jedna platforma, a zespół zna ją świetnie, rozważ też natywny start; dla jednej platformy czasami będzie to krótsza ścieżka.
Zakres pierwszej aplikacji, który realnie dowieziesz
Na początek zdefiniuj najprostszy produkt, który pokaże pełny „przekrój” prac:
1–2 ekrany,
lista zadań lub notatek (CRUD),
trwałość danych lokalnie (np. shared_preferences),
lekka nawigacja (push/pop),
tematyka Material 3,
ikona aplikacji i poprawna nazwa pakietu.
Taki cel wymusza dotknięcie najważniejszych obszarów: UI, stan, dane, konfiguracja, uruchomienie na urządzeniu, a nawet przygotowanie do wydania.
Mini-brief pytań, na które szukasz odpowiedzi
Jak zainstalować Fluttera i przejść przez flutter doctor bez ostrzeżeń?
Jak utworzyć projekt, zrozumieć strukturę folderów i uruchomić hot reload?
Jak napisać prosty ekran listy z formularzem dodawania i trwałością danych?
Jak dobrać i dodać zależności w pubspec.yaml bez konfliktów?
Jak przetestować podstawową logikę i UI?
Jak przygotować release na Android i iOS (podpisywanie, identyfikatory, wersjonowanie)?
Checklista przygotowań: środowisko i narzędzia
Wymagania wstępne i decyzje narzędziowe
Na starcie przyjmij prosty układ narzędziowy:
System: Windows 10/11, macOS (dla iOS wymagany macOS z Xcode), Linux (Ubuntu/Fedora itp.).
IDE: Android Studio lub Visual Studio Code (z wtyczkami Dart i Flutter).
Android SDK i emulator Pixel (AVD). Dla iOS – Xcode, iOS Simulator, CocoaPods.
Git dla kontroli wersji.
Java (JDK 17) dla współczesnych wersji Gradle/AGP.
Jeśli masz tylko Windows lub Linux, skup się na Androidzie. Na iOS zbudujesz dopiero na macOS.
Instalacja Flutter SDK krok po kroku
Pobierz archiwum Flutter SDK z oficjalnej strony (kanał stable).
Rozpakuj do stałej lokalizacji (np. C:/src/flutter lub ~/development/flutter).
Dodaj do PATH folder flutter/bin. Zmianę zastosuj w terminalu/PowerShellu (sprawdź poleceniem flutter –version).
Uruchom flutter doctor i zanotuj braki.
Jeżeli korzystasz z VS Code, doinstaluj rozszerzenia „Flutter” i „Dart”. W Android Studio uruchom kreator SDK Manager i pobierz przynajmniej najnowszą stabilną platformę Android, narzędzia build-tools oraz emulator.
Źródło: Pexels | Autor: Shoper .pl
Android Studio, SDK i emulator – konfiguracja bez niespodzianek
Zainstaluj Android Studio i podczas pierwszego uruchomienia wybierz SDK Location bez spacji w ścieżce, jeśli to możliwe.
AVD Manager: utwórz wirtualne urządzenie, np. Pixel 5, system image x86_64 (lub ARM na Apple Silicon), API 33–35.
W Windows upewnij się, że masz włączoną wirtualizację (BIOS/UEFI) i Hyper-V/WSL nie koliduje z HAXM/WHX.
Xcode, CocoaPods i iOS Simulator (macOS)
App Store: zainstaluj Xcode. Otwórz raz i zaakceptuj licencję, doinstaluj komponenty.
Zainstaluj CocoaPods: gem install cocoapods lub przez Homebrew: brew install cocoapods.
Uruchom iOS Simulator z Xcode (Devices and Simulators) i pobierz aktualne obrazy systemów.
flutter doctor – interpretacja i typowe naprawy
Polecenie podstawowe:
flutter doctor -v
Android toolchain: jeśli JDK jest niekompatybilne, zainstaluj JDK 17 i ustaw JAVA_HOME.
Android licenses: zaakceptuj licencje: flutter doctor –android-licenses.
Xcode: brak CocoaPods? Zainstaluj i podaj PATH (sprawdź pod: which pod).
Connected device: uruchom emulator (Android) lub iOS Simulator albo podłącz telefon z włączonym debugowaniem.
Pierwsze uruchomienie projektu i anatomia katalogów
Tworzenie projektu poleceniami CLI
flutter create pierwsza_aplikacja
cd pierwsza_aplikacja
flutter run
Jeżeli masz uruchomiony emulator/urządzenie, pojawi się przykładowa aplikacja „Counter”. To szybki test hot reloadingu i pipeline’u build.
Struktura plików, które zobaczysz
lib/: kod aplikacji w Dart (start: main.dart).
test/: testy jednostkowe i widgetowe.
android/ i ios/: projekty natywne (Gradle, Xcode), konfiguracje wydaniowe.
pubspec.yaml: deklaracja zależności, assetów, nazw i wersji.
W codziennej pracy edytujesz głównie lib/ i pubspec.yaml; katalogi platformowe dotykasz przy wydaniu lub dodawaniu natywnych konfiguracji (uprawnienia, ikony, signing).
Hot reload kontra hot restart
Hot reload (r): wstrzykuje zmiany w kodzie i odświeża UI bez utraty stanu (gdy to możliwe). Idealny do zmian layoutu i logiki budowania widgetów.
Hot restart (R): restartuje aplikację i inicjuje ją od zera. Potrzebny po zmianach globalnych lub inicjalizacji.
Minimalna modyfikacja dla polskiego kontekstu
Ustaw język polski i aktywuj Material 3:
Aktywuj Material 3 i polską lokalizację. Najprościej dodać lokalizacje jako zależność SDK i ustawić motyw oraz język w MaterialApp.
Uwaga: jeśli korzystasz z niestandardowych fontów lub ikon, dodaj je do pubspec.yaml i wykonaj flutter pub get przed uruchomieniem.
Budowa rdzenia: lista zadań (CRUD) + trwałość danych
Źródło: Pexels | Autor: AS Photography
Dodanie zależności i wybór prostego magazynu
Na start wystarczy pamięć lokalna. Plugin shared_preferences przechowa listę w formacie JSON. Dodaj zależność poleceniem:
flutter pub add shared_preferences
Po instalacji wykonaj krótką próbę kompilacji, aby złapać ewentualne konflikty wersji:
flutter pub get
flutter analyze
Ekran główny: lista, dodawanie, usuwanie
Utwórz prosty ekran z polem tekstowym, przyciskiem dodawania i listą, która wspiera usuwanie gestem. Dane trzymaj w pamięci i zapisuj po każdej zmianie do shared_preferences.
Dodanie zadania działa enterem i przyciskiem „Dodaj”.
Po restarcie aplikacji lista nie jest pusta (dane zapisują się lokalnie).
Przesunięcie elementu w lewo usuwa go bez błędów.
Ostrzeżenie: shared_preferences nie jest bazą danych. Gdy lista zacznie rosnąć, rozważ drift/sqflite lub isar. Na start wystarczy proste klucze–wartości.
Podstawowa nawigacja i drugi ekran
Dodanie ekranu „Szczegóły” z minimalnym edytorem
Drugi ekran pozwala dotknąć nawigacji i prostych formularzy. Przekaż wybrany tekst, umożliw edycję i zwróć wynik.
Gest przesunięcia usuwa element. Dodaj krótkie okno cofnięcia. W praktyce daje to margines błędu – użytkownik może wyjść z kłopotów jednym tapnięciem.
// lib/home_page.dart (fragment - w Dismissible)
onDismissed: (_) async {
final removed = item; // zapamiętaj
final removedIndex = index;
await _removeTask(index); // usuń i zapisz
if (!mounted) return;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Usunięto: $removed'),
duration: const Duration(seconds: 3),
action: SnackBarAction(
label: 'Cofnij',
onPressed: () async {
// wstaw z powrotem pod dawny indeks lub na koniec
final insertAt = removedIndex <= _tasks.length ? removedIndex : _tasks.length;
setState(() => _tasks.insert(insertAt, removed));
await _saveTasks();
},
),
),
);
},
Kryteria sprawdzenia po podpięciu edycji
Tap na elemencie otwiera ekran „Szczegóły”, powrót bez zapisu nie zmienia listy.
Zapis zmienionego tekstu nadpisuje istniejące zadanie i pozostaje po restarcie.
Przesunięcie w lewo usuwa i pokazuje SnackBar; „Cofnij” przywraca wpis bez błędów.
Ostrzeżenia i typowe niuanse
Po await Navigator.push(...) użyj if (!mounted) return;, aby uniknąć setState na odmontowanym widżecie.
Nie zapisuj pustych wyników edycji – odrzuć null i białe znaki (trim().isEmpty).
Gdy elementy są identyczne, użyj w Dismissible klucza łączącego treść i indeks (co już masz), aby uniknąć konfliktów.
Uruchomienie na urządzeniu i szybkie buildy
Procedura: emulator lub fizyczny telefon
Włącz emulator (Android Studio/AVD) lub podłącz urządzenie przez USB i włącz debugowanie (Android) / zaufaj komputerowi (iOS).
Sprawdź listę urządzeń: flutter devices. Jeśli pusto – doinstaluj SDK/emulator i zaakceptuj licencje: flutter doctor --android-licenses.
Uruchom aplikację: flutter run -d <ID_urządzenia>. Podczas działania:
r – Hot Reload (zmiany w kodzie UI bez restartu stanu),
R – Hot Restart (szybki restart, czyści stan),
q – wyjście.
Pakiet instalacyjny do szybkiej weryfikacji
Android (debug APK): flutter build apk --debug i zainstaluj na urządzeniu.
Android (release AAB/APK): flutter build appbundle lub flutter build apk --release – do publikacji wymagane jest podpisanie.
iOS: flutter build ios (wymaga Xcode; instalacja na urządzeniu zwykle przez Xcode z właściwym profilem).
Lista kontrolna przed buildem
Tryb produkcyjny wyłączony banner debug: masz debugShowCheckedModeBanner: false.
Ikona i nazwę aplikacji ustawisz później; na start nie blokuje to builda.
Jeśli Gradle pobiera się długo przy pierwszym uruchomieniu – to normalne; kolejny build będzie krótszy.
Najrozsądniej wdrażać małe kroki: jedna zmiana w UI, szybki Hot Reload, potem zapis stanu i krótki test cofnięcia. Dzięki temu błąd widać od razu, a poprawka zajmuje minuty, nie godzinę.
Uporządkowanie stanu: lekki store z ChangeNotifier
Cel i decyzja: kiedy lokalny setState, a kiedy wspólny stan
Jeżeli stan dotyczy jednego ekranu – setState zwykle wystarczy. Gdy zaczynasz współdzielić dane (lista zadań + ustawienia + drugi ekran), przejście do prostego store’u upraszcza zależności i testy.
Źródło: Pexels | Autor: Nikolai Kolosov
Procedura refaktoryzacji do Provider + ChangeNotifier
Dodaj zależność: provider (pubspec.yaml – sekcja dependencies) i zaktualizuj pakiety.
Utwórz TaskStore odpowiedzialny za listę i zapis do shared_preferences.
Uruchamiaj aplikację dopiero po zainicjalizowaniu store’u (FutureBuilder w bootstrapie).
W HomePage używaj context.watch()/read() zamiast prywatnych pól i _saveTasks().