# 20 pytan rekrutacyjnych z Flutter dla programistow mobilnych > Przygotowanie do rozmowy kwalifikacyjnej z Flutter: 20 najczesciej zadawanych pytan. Widgety, zarzadzanie stanem, Dart, architektura i najlepsze praktyki z przykladami kodu. - Published: 2026-01-24 - Updated: 2026-04-12 - Author: SharpSkill - Tags: flutter, dart, interview, mobile development, technical interview - Reading time: 15 min --- Rozmowy kwalifikacyjne z Flutter weryfikuja biegle opanowanie frameworka, jezyka Dart oraz wzorcow architektury mobilnej. Ponizszy przewodnik obejmuje 20 najczesciej zadawanych pytan, od zagadnien podstawowych po zaawansowane koncepcje, z wyczerpujacymi odpowiedziami i przykladami kodu. > **Wskazowka Rekrutacyjna** > > Rekruterzy cenia kandydatow, ktorzy potrafia wyjasnic "dlaczego" obok "jak". Dla kazdej koncepcji zrozumienie przypadkow uzycia i kompromisow technicznych robi roznice. ## Podstawowe Pytania o Flutter i Dart ### 1. Jaka jest roznica miedzy StatelessWidget a StatefulWidget? **StatelessWidget** reprezentuje niezmienialny widget, ktorego wyglad zalezy wylacznie od poczatkowej konfiguracji. Po zbudowaniu nigdy sie nie zmienia. **StatefulWidget** utrzymuje mutowalny stan, ktory moze ewoluowac w czasie, wyzwalajac przebudowy widgetu. ```dart // stateless_example.dart // StatelessWidget: static display, no change after construction class WelcomeMessage extends StatelessWidget { // Final parameter - never changes final String username; const WelcomeMessage({super.key, required this.username}); @override Widget build(BuildContext context) { // Build called once (unless parent rebuilds) return Text('Welcome, $username'); } } ``` ```dart // stateful_example.dart // StatefulWidget: mutable state, can rebuild itself class LikeButton extends StatefulWidget { const LikeButton({super.key}); @override State createState() => _LikeButtonState(); } class _LikeButtonState extends State { // Mutable local state int _likeCount = 0; void _incrementLike() { // setState triggers rebuild with new state setState(() { _likeCount++; }); } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: _incrementLike, child: Text('Likes: $_likeCount'), ); } } ``` Zasada jest prosta: domyslnie nalezy stosowac StatelessWidget, a StatefulWidget tylko wtedy, gdy widget musi zarzadzac wlasnym stanem lokalnym. ### 2. Jak dziala drzewo widgetow we Flutter? Flutter organizuje interfejs w trzy powiazane drzewa: **Widget Tree** (niezmienna deklaracja), **Element Tree** (cykl zycia i wiazanie) oraz **Render Tree** (uklad i renderowanie). ```dart // widget_tree_example.dart // Declarative structure - widgets describe the UI class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // Each widget creates an Element in the Element Tree return MaterialApp( home: Scaffold( // Scaffold creates multiple RenderObjects body: Center( // Center modifies its child's layout child: Column( children: [ // Each Text has its own RenderParagraph Text('First'), Text('Second'), ], ), ), ), ); } } ``` Gdy wywolywana jest metoda `setState`, Flutter porownuje stare i nowe drzewo widgetow, aby przebudowac tylko zmodyfikowane elementy. To roznicowanie opiera sie na kluczach i typach widgetow. ### 3. Czym jest konstruktor const i dlaczego warto go uzywac? **Konstruktory const** tworza widgety w czasie kompilacji, a nie w czasie wykonania. Flutter moze ponownie wykorzystywac te instancje, poprawiajac wydajnosc dzieki unikaniu zbednych przebudow. ```dart // const_example.dart class OptimizedScreen extends StatelessWidget { const OptimizedScreen({super.key}); @override Widget build(BuildContext context) { return Column( children: [ // ✅ const: same instance reused on each build const Icon(Icons.star, size: 48), const SizedBox(height: 16), const Text('Static title'), // ❌ Non-const: new instance on each build Icon(Icons.star, color: Theme.of(context).primaryColor), ], ); } } // Custom widget with const constructor class StaticCard extends StatelessWidget { final String title; // All fields must be final for const const StaticCard({super.key, required this.title}); @override Widget build(BuildContext context) { return Card(child: Text(title)); } } ``` Analizator Flutter wskazuje pominete okazje za pomoca reguly lint `prefer_const_constructors`. ### 4. Jakie sa rodzaje kluczy (Keys) we Flutter? **Klucze** zachowuja stan widgetow podczas reorganizacji. Bez kluczy Flutter opiera sie na pozycji w drzewie, co moze powodowac bledy przy zmianie kolejnosci list. ```dart // keys_example.dart class TodoList extends StatelessWidget { final List todos; const TodoList({super.key, required this.todos}); @override Widget build(BuildContext context) { return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; // ✅ ValueKey: preserves state if order changes return TodoTile( key: ValueKey(todo.id), todo: todo, ); }, ); } } // Different key types for different contexts class KeyExamples extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // ValueKey: based on a unique value Container(key: ValueKey('unique-id')), // ObjectKey: based on object identity Container(key: ObjectKey(myObject)), // UniqueKey: new key on each build Container(key: UniqueKey()), // GlobalKey: access state from outside Form(key: _formKey), ], ); } } ``` GlobalKeys pozwalaja rowniez na dostep do stanu widgetu z dowolnego miejsca, ale nalezy je stosowac oszczednie. ## Pytania o Dart ### 5. Jaka jest roznica miedzy final a const w Dart? **final** definiuje zmienna, ktora moze byc przypisana tylko raz i jest ewaluowana w czasie wykonania. **const** tworzy stala czasu kompilacji, niezmienna i ewaluowana przed uruchomieniem. ```dart // final_const_example.dart class DateExample { // final: value assigned at runtime final DateTime createdAt = DateTime.now(); // const: value known at compile time static const int maxItems = 100; static const String appName = 'MyApp'; // ❌ Error: DateTime.now() not const (runtime value) // static const DateTime timestamp = DateTime.now(); } void demonstrateDifference() { // final: each call can have different value final timestamp1 = DateTime.now(); final timestamp2 = DateTime.now(); print(timestamp1 == timestamp2); // false (different) // const: same instance reused const list1 = [1, 2, 3]; const list2 = [1, 2, 3]; print(identical(list1, list2)); // true (same instance) } ``` We Flutter nalezy preferowac const dla statycznych widgetow i final dla wartosci obliczanych dynamicznie. ### 6. Jak dzialaja Future i async/await? **Future** reprezentuje wartosc, ktora bedzie dostepna w przyszlosci. **async/await** zapewnia czytelna skladnie do obslugi operacji asynchronicznych bez zagniezdzonych callbackow. ```dart // async_example.dart class UserRepository { final ApiClient _client; UserRepository(this._client); // Future: promise of a future value Future fetchUser(String id) async { try { // await suspends execution until resolution final response = await _client.get('/users/$id'); return User.fromJson(response); } catch (e) { // Errors propagate normally with async/await throw UserNotFoundException(id); } } // Parallel execution with Future.wait Future> fetchUsers(List ids) async { // All requests start simultaneously final futures = ids.map((id) => fetchUser(id)); // Wait for all to complete return Future.wait(futures); } // Sequential processing Future processSequentially(List ids) async { for (final id in ids) { // Each request waits for the previous one await fetchUser(id); } } } ``` `Future.wait` sluzy do rownoleglego wykonywania, a petle async do przetwarzania sekwencyjnego. > **FutureBuilder vs Riverpod** > > FutureBuilder sprawdza sie w prostych przypadkach, ale Riverpod (AsyncValue) oferuje lepsze zarzadzanie pamiecia podreczna, obsluge bledow i mozliwosci odswiezania w zlozonych aplikacjach. ### 7. Czym sa Streamy i jak sie je stosuje? **Streamy** reprezentuja asynchroniczne sekwencje wartosci, idealne do obslugi zdarzen w czasie rzeczywistym: WebSockety, dane z czujnikow czy interakcje uzytkownika. ```dart // stream_example.dart class MessageService { // StreamController manages creation and broadcasting final _messageController = StreamController.broadcast(); // Expose only the Stream (not the Sink) Stream get messages => _messageController.stream; void addMessage(Message message) { _messageController.sink.add(message); } void dispose() { _messageController.close(); } } // Usage with StreamBuilder class MessageList extends StatelessWidget { final MessageService service; const MessageList({super.key, required this.service}); @override Widget build(BuildContext context) { return StreamBuilder( stream: service.messages, builder: (context, snapshot) { // Handle all possible states if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } if (!snapshot.hasData) { return const Text('No messages'); } return MessageCard(message: snapshot.data!); }, ); } } ``` Streamy `.broadcast()` pozwalaja na wielu sluchaczy, w przeciwienstwie do Streamow z pojedyncza subskrypcja. ## Pytania o Architekture i Zarzadzanie Stanem ### 8. Porownanie rozwiazan do zarzadzania stanem: Provider, Riverpod, Bloc Kazde rozwiazanie odpowiada na inne potrzeby. **Provider** oferuje prostote i natywna integracje. **Riverpod** zapewnia bezpieczenstwo typow i testowalnosc. **Bloc** wymusza scisla architekture zdarzeniowa. ```dart // riverpod_example.dart // Riverpod: declarative and type-safe approach final userProvider = FutureProvider.autoDispose((ref) async { final repository = ref.watch(userRepositoryProvider); return repository.fetchCurrentUser(); }); class UserProfile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // AsyncValue handles loading/error/data final userAsync = ref.watch(userProvider); return userAsync.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('Error: $error'), data: (user) => Text(user.displayName), ); } } ``` ```dart // bloc_example.dart // Bloc: explicit events/states separation abstract class AuthEvent {} class LoginRequested extends AuthEvent { final String email; final String password; LoginRequested(this.email, this.password); } abstract class AuthState {} class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); } class AuthFailure extends AuthState { final String error; AuthFailure(this.error); } class AuthBloc extends Bloc { AuthBloc() : super(AuthInitial()) { on(_onLoginRequested); } Future _onLoginRequested( LoginRequested event, Emitter emit, ) async { emit(AuthLoading()); try { final user = await _repository.login(event.email, event.password); emit(AuthSuccess(user)); } catch (e) { emit(AuthFailure(e.toString())); } } } ``` Riverpod jest rekomendowany dla nowych projektow dzieki nowoczesnemu API i latwosci testowania. ### 9. Czym jest Clean Architecture we Flutter? **Clean Architecture** dzieli kod na niezalezne warstwy: Domain (logika biznesowa), Data (zrodla danych) i Presentation (UI). Taki podzial ulatwia testowanie i utrzymanie. ```dart // domain/entities/user.dart // Entity: pure business object, no framework dependency class User { final String id; final String email; final String name; const User({ required this.id, required this.email, required this.name, }); } // domain/repositories/user_repository.dart // Interface: abstract contract, implementation in Data abstract class UserRepository { Future getUser(String id); Future updateUser(User user); } // domain/usecases/get_user_usecase.dart // Use case: isolated business logic class GetUserUseCase { final UserRepository _repository; GetUserUseCase(this._repository); Future call(String userId) { return _repository.getUser(userId); } } ``` ```dart // data/repositories/user_repository_impl.dart // Concrete implementation with data sources class UserRepositoryImpl implements UserRepository { final UserRemoteDataSource _remoteDataSource; final UserLocalDataSource _localDataSource; UserRepositoryImpl(this._remoteDataSource, this._localDataSource); @override Future getUser(String id) async { try { // Try local cache first final cachedUser = await _localDataSource.getUser(id); if (cachedUser != null) return cachedUser; } catch (_) {} // Fallback to API final user = await _remoteDataSource.fetchUser(id); await _localDataSource.cacheUser(user); return user; } } ``` Warstwa Domain nie zalezy od niczego, Data zalezy od Domain, a Presentation zalezy od obu. ### 10. Jak zaimplementowac wstrzykiwanie zaleznosci? **Wstrzykiwanie zaleznosci** rozdziela komponenty, dostarczajac ich zaleznosci z zewnatrz. Riverpod doskonale realizuje to za pomoca providerow. ```dart // di_example.dart // Provider definitions (dependencies) final apiClientProvider = Provider((ref) { return ApiClient(baseUrl: Environment.apiUrl); }); final userRepositoryProvider = Provider((ref) { // Automatically injects ApiClient final client = ref.watch(apiClientProvider); return UserRepositoryImpl(client); }); final getUserUseCaseProvider = Provider((ref) { final repository = ref.watch(userRepositoryProvider); return GetUserUseCase(repository); }); // Usage in a widget class UserScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final getUserUseCase = ref.watch(getUserUseCaseProvider); // Use the use case... } } // Tests: easy dependency override void main() { testWidgets('displays user', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ // Replace with mock for tests userRepositoryProvider.overrideWithValue(MockUserRepository()), ], child: const MyApp(), ), ); }); } ``` Glowna zaleta: testy moga zastapic dowolna zaleznosc mockiem. ## Pytania o Wydajnosc ### 11. Jak optymalizowac wydajnosc przebudow? Minimalizacja przebudow poprawia wydajnosc. Kluczowe techniki obejmuja: widgety const, granularne dzielenie i selektory Riverpod. ```dart // rebuild_optimization.dart // ❌ Bad: everything rebuilds on each change class BadExample extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userProvider); return Column( children: [ Text(user.name), Text(user.email), const ExpensiveWidget(), // Rebuilds unnecessarily ], ); } } // ✅ Good: selector to rebuild only if name changes class GoodExample extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // select: rebuilds only if name changes final name = ref.watch(userProvider.select((u) => u.name)); return Text(name); } } // ✅ Good: splitting into smaller widgets class OptimizedScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ const Header(), // Static, never rebuilds const UserNameWidget(), // Rebuilds if name changes const UserEmailWidget(), // Rebuilds if email changes const Footer(), // Static ], ); } } ``` DevTools Performance sluzy do identyfikowania nadmiernych przebudow. ### 12. Jak optymalizowac dlugie listy? Dlugie listy wymagaja **leniwego ladowania** za pomoca ListView.builder. Nalezy unikac ListView z bezposrednimi dziecmi dla wiecej niz 20 elementow. ```dart // list_optimization.dart class OptimizedList extends StatelessWidget { final List items; const OptimizedList({super.key, required this.items}); @override Widget build(BuildContext context) { // ✅ ListView.builder: builds on demand return ListView.builder( // Fixed height improves scrolling itemExtent: 72, itemCount: items.length, itemBuilder: (context, index) { return ItemTile( // Key to preserve state during scroll key: ValueKey(items[index].id), item: items[index], ); }, ); } } // List widget with infinite loading class InfiniteList extends ConsumerStatefulWidget { @override ConsumerState createState() => _InfiniteListState(); } class _InfiniteListState extends ConsumerState { final _scrollController = ScrollController(); @override void initState() { super.initState(); _scrollController.addListener(_onScroll); } void _onScroll() { // Load more data near the end if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { ref.read(itemsProvider.notifier).loadMore(); } } @override Widget build(BuildContext context) { final items = ref.watch(itemsProvider); return ListView.builder( controller: _scrollController, itemCount: items.length, itemBuilder: (context, index) => ItemTile(item: items[index]), ); } } ``` Dla bardzo dlugich list (ponad 1000 elementow) warto rozwazyc `ListView.separated` lub wyspecjalizowane pakiety, takie jak `scrollable_positioned_list`. > **Cache Obrazow** > > Dla list z obrazami nalezy uzywac `cached_network_image`, aby uniknac ponownego ladowania. Niebuforowane obrazy powoduja zacinanie sie przewijania. ### 13. Jak dziala silnik renderujacy Impeller? **Impeller** zastepuje Skia jako domyslny silnik renderujacy (Flutter 3.16+). Prekompiluje shadery, eliminujac "jank" pierwszego wyswietlania. ```dart // impeller_benefits.dart // Complex animations benefit from Impeller class SmoothAnimation extends StatefulWidget { @override State createState() => _SmoothAnimationState(); } class _SmoothAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, )..repeat(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { // Impeller: no runtime shader compilation return Transform.rotate( angle: _controller.value * 2 * 3.14159, child: child, ); }, child: const FlutterLogo(size: 100), ); } } ``` Impeller jest domyslnie wlaczony na iOS. Na Androidzie nalezy sprawdzic kompatybilnosc za pomoca `flutter run --enable-impeller`. ## Pytania o Nawigacje i Formularze ### 14. Jak obslugiwac nawigacje z deep linkingiem? **Deep linking** umozliwia otwieranie aplikacji na konkretnym ekranie za pomoca URL. GoRouter natywnie obsluguje te funkcjonalnosc. ```dart // deep_linking.dart final router = GoRouter( routes: [ GoRoute( path: '/products/:productId', builder: (context, state) { // Extract URL parameter final productId = state.pathParameters['productId']!; return ProductScreen(productId: productId); }, ), GoRoute( path: '/search', builder: (context, state) { // Query parameters (?query=flutter) final query = state.uri.queryParameters['query'] ?? ''; return SearchScreen(initialQuery: query); }, ), ], ); // Programmatic navigation void navigateToProduct(BuildContext context, String id) { // go: replaces navigation stack context.go('/products/$id'); // push: adds to stack (allows back) context.push('/products/$id'); // pushNamed with extra for complex data context.pushNamed( 'productDetail', pathParameters: {'productId': id}, extra: ProductData(id: id), ); } ``` Konfiguracja plikow natywnych (AndroidManifest.xml, Info.plist) jest niezbedna do wlaczenia systemowego deep linkingu. ### 15. Jak skutecznie walidowac formularze? Walidacja laczy Form, TextFormField i niestandardowe walidatory. Pakiety w stylu Zod, takie jak `formz`, dodaja strukture. ```dart // form_validation.dart class RegistrationForm extends StatefulWidget { @override State createState() => _RegistrationFormState(); } class _RegistrationFormState extends State { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmController = TextEditingController(); @override Widget build(BuildContext context) { return Form( key: _formKey, // Real-time validation autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email'), keyboardType: TextInputType.emailAddress, validator: _validateEmail, ), TextFormField( controller: _passwordController, decoration: const InputDecoration(labelText: 'Password'), obscureText: true, validator: _validatePassword, ), TextFormField( controller: _confirmController, decoration: const InputDecoration(labelText: 'Confirm'), obscureText: true, validator: _validateConfirmPassword, ), ElevatedButton( onPressed: _submit, child: const Text('Create account'), ), ], ), ); } String? _validateEmail(String? value) { if (value == null || value.isEmpty) { return 'Email required'; } final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(value)) { return 'Invalid email format'; } return null; } String? _validatePassword(String? value) { if (value == null || value.length < 8) { return 'Minimum 8 characters'; } if (!value.contains(RegExp(r'[A-Z]'))) { return 'At least one uppercase letter required'; } if (!value.contains(RegExp(r'[0-9]'))) { return 'At least one number required'; } return null; } String? _validateConfirmPassword(String? value) { if (value != _passwordController.text) { return 'Passwords do not match'; } return null; } void _submit() { if (_formKey.currentState!.validate()) { // Form is valid, proceed } } } ``` Tryb `AutovalidateMode.onUserInteraction` oferuje najlepsze UX: bledy wyswietlane sa dopiero po interakcji uzytkownika. ## Pytania o Testowanie ### 16. Jak testowac widgety Flutter? Testy widgetow weryfikuja interfejs bez koniecznosci uzycia fizycznego urzadzenia. Pakiet `flutter_test` dostarcza niezbedne narzedzia. ```dart // widget_test.dart import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; void main() { testWidgets('displays welcome message', (tester) async { // Arrange: build the widget await tester.pumpWidget( const ProviderScope( child: MaterialApp( home: WelcomeScreen(username: 'Alice'), ), ), ); // Assert: verify content expect(find.text('Welcome, Alice'), findsOneWidget); }); testWidgets('button increments counter', (tester) async { await tester.pumpWidget( const MaterialApp(home: CounterScreen()), ); // Initial state expect(find.text('0'), findsOneWidget); // Act: tap the button await tester.tap(find.byType(ElevatedButton)); await tester.pump(); // Rebuild after setState // Assert: counter incremented expect(find.text('1'), findsOneWidget); }); testWidgets('form validates email', (tester) async { await tester.pumpWidget( const MaterialApp(home: LoginForm()), ); // Enter invalid email await tester.enterText( find.byKey(const Key('email-field')), 'invalid-email', ); await tester.tap(find.byType(ElevatedButton)); await tester.pump(); // Verify error message expect(find.text('Invalid email format'), findsOneWidget); }); } ``` `pump()` przesuwa o jedna klatke, a `pumpAndSettle()` czeka na zakonczenie wszystkich animacji. ### 17. Jak testowac providery Riverpod? Riverpod ulatwia testowanie dzieki `ProviderContainer` i nadpisywaniu zaleznosci. ```dart // provider_test.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; // Repository mock class MockUserRepository extends Mock implements UserRepository {} void main() { late MockUserRepository mockRepository; late ProviderContainer container; setUp(() { mockRepository = MockUserRepository(); container = ProviderContainer( overrides: [ userRepositoryProvider.overrideWithValue(mockRepository), ], ); }); tearDown(() { container.dispose(); }); test('loads user from repository', () async { // Arrange final expectedUser = User(id: '1', name: 'Test'); when(() => mockRepository.getUser('1')) .thenAnswer((_) async => expectedUser); // Act final user = await container.read(userProvider('1').future); // Assert expect(user, expectedUser); verify(() => mockRepository.getUser('1')).called(1); }); test('handles repository error', () async { when(() => mockRepository.getUser(any())) .thenThrow(Exception('Network error')); expect( () => container.read(userProvider('1').future), throwsException, ); }); } ``` Testy providerow sa szybkie, poniewaz nie wymagaja renderowania interfejsu. ## Pytania o Wdrozenie ### 18. Jak zarzadzac roznymi srodowiskami (dev, staging, prod)? Srodowiska konfiguruje sie za pomoca plikow `.env` lub stalych czasu kompilacji z `--dart-define`. ```dart // environment.dart enum Environment { dev, staging, prod } class AppConfig { final Environment environment; final String apiUrl; final bool enableAnalytics; const AppConfig._({ required this.environment, required this.apiUrl, required this.enableAnalytics, }); // Predefined configurations static const dev = AppConfig._( environment: Environment.dev, apiUrl: 'https://api-dev.example.com', enableAnalytics: false, ); static const staging = AppConfig._( environment: Environment.staging, apiUrl: 'https://api-staging.example.com', enableAnalytics: true, ); static const prod = AppConfig._( environment: Environment.prod, apiUrl: 'https://api.example.com', enableAnalytics: true, ); // Read from --dart-define static AppConfig fromEnvironment() { const env = String.fromEnvironment('ENV', defaultValue: 'dev'); switch (env) { case 'prod': return prod; case 'staging': return staging; default: return dev; } } } ``` ```bash # terminal # Launch with specific environment flutter run --dart-define=ENV=staging # Production build flutter build apk --dart-define=ENV=prod --release ``` ### 19. Jak zaimplementowac flavoring dla wielu wariantow aplikacji? **Flavoring** tworzy wiele wariantow (klient1, klient2) z odrebnymi konfiguracjami (ikona, nazwa, API). ```dart // main_client1.dart import 'package:flutter/material.dart'; import 'config/flavor_config.dart'; import 'app.dart'; void main() { FlavorConfig( flavor: Flavor.client1, name: 'App Client 1', apiUrl: 'https://api.client1.com', primaryColor: Colors.blue, ); runApp(const App()); } // config/flavor_config.dart enum Flavor { client1, client2, internal } class FlavorConfig { final Flavor flavor; final String name; final String apiUrl; final Color primaryColor; // Singleton for global access static FlavorConfig? _instance; static FlavorConfig get instance => _instance!; FlavorConfig({ required this.flavor, required this.name, required this.apiUrl, required this.primaryColor, }) { _instance = this; } bool get isProduction => flavor != Flavor.internal; } ``` Konfiguracja plikow natywnych Android (build.gradle) i iOS (xcconfig) jest wymagana dla kazdego flavoru. ### 20. Jakie sa najlepsze praktyki bezpieczenstwa we Flutter? Bezpieczenstwo obejmuje przechowywanie danych wrazliwych, walidacje danych wejsciowych i ochrone przed inzynieria wsteczna. ```dart // security_example.dart import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SecureStorage { // Encrypted storage for sensitive data final _storage = const FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), ); Future saveToken(String token) async { await _storage.write(key: 'auth_token', value: token); } Future getToken() async { return _storage.read(key: 'auth_token'); } Future deleteToken() async { await _storage.delete(key: 'auth_token'); } } // Input validation class InputValidator { // Injection prevention static String sanitize(String input) { return input .replaceAll(RegExp(r'[<>"\']'), '') .trim(); } // Length validation static bool isValidLength(String input, int min, int max) { return input.length >= min && input.length <= max; } } // SSL pinning protection class SecureApiClient { Dio createSecureClient() { final dio = Dio(); // Add certificate pinning (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { final client = HttpClient(); client.badCertificateCallback = (cert, host, port) { // Verify certificate fingerprint return _isValidCertificate(cert); }; return client; }; return dio; } } ``` Nigdy nie nalezy przechowywac sekretow w kodzie. Tokeny powinny byc przechowywane w `flutter_secure_storage`, a flaga `--obfuscate` powinna byc uzywana w produkcji. ## Podsumowanie Te 20 pytan obejmuje kluczowe aspekty rozmow kwalifikacyjnych z Flutter: podstawy frameworka, biegle opanowanie Dart, wzorce architektoniczne i najlepsze praktyki produkcyjne. Sukces tkwi w glebkim zrozumieniu mechanizmow dzialania, a nie tylko skladni. ### Lista kontrolna przygotowania - ✅ Opanowanie roznicy miedzy StatelessWidget a StatefulWidget oraz przypadkow uzycia - ✅ Zrozumienie drzewa widgetow i optymalizacji przebudow - ✅ Cwiczenie async/await, Futures i Streams na przykladach - ✅ Implementacja projektu z Riverpod do zarzadzania stanem - ✅ Znajomosc GoRouter i deep linkingu - ✅ Pisanie testow jednostkowych i testow widgetow - ✅ Zrozumienie konfiguracji wielosrodowiskowej Regularna praktyka na wlasnych projektach pozostaje najlepszym sposobem utrwalania tej wiedzy. Kazde pytanie omowione tutaj zasluguje na glebsze zbadanie z rzeczywistym kodem, aby opanowac niuanse frameworka Flutter. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/flutter/flutter-interview-questions