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.

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.
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.
// 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});
Widget build(BuildContext context) {
// Build called once (unless parent rebuilds)
return Text('Welcome, $username');
}
}// StatefulWidget: mutable state, can rebuild itself
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
// Mutable local state
int _likeCount = 0;
void _incrementLike() {
// setState triggers rebuild with new state
setState(() {
_likeCount++;
});
}
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).
// Declarative structure - widgets describe the UI
class MyApp extends StatelessWidget {
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.
class OptimizedScreen extends StatelessWidget {
const OptimizedScreen({super.key});
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});
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.
class TodoList extends StatelessWidget {
final List<Todo> todos;
const TodoList({super.key, required this.todos});
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 {
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.
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.
class UserRepository {
final ApiClient _client;
UserRepository(this._client);
// Future: promise of a future value
Future<User> 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<List<User>> fetchUsers(List<String> 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<void> processSequentially(List<String> 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 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.
class MessageService {
// StreamController manages creation and broadcasting
final _messageController = StreamController<Message>.broadcast();
// Expose only the Stream (not the Sink)
Stream<Message> 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});
Widget build(BuildContext context) {
return StreamBuilder<Message>(
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.
Gotowy na rozmowy o Flutter?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
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.
// Riverpod: declarative and type-safe approach
final userProvider = FutureProvider.autoDispose<User>((ref) async {
final repository = ref.watch(userRepositoryProvider);
return repository.fetchCurrentUser();
});
class UserProfile extends ConsumerWidget {
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),
);
}
}// 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<AuthEvent, AuthState> {
AuthBloc() : super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> 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.
// 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<User> getUser(String id);
Future<void> updateUser(User user);
}
// domain/usecases/get_user_usecase.dart
// Use case: isolated business logic
class GetUserUseCase {
final UserRepository _repository;
GetUserUseCase(this._repository);
Future<User> call(String userId) {
return _repository.getUser(userId);
}
}// Concrete implementation with data sources
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource _remoteDataSource;
final UserLocalDataSource _localDataSource;
UserRepositoryImpl(this._remoteDataSource, this._localDataSource);
Future<User> 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.
// Provider definitions (dependencies)
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient(baseUrl: Environment.apiUrl);
});
final userRepositoryProvider = Provider<UserRepository>((ref) {
// Automatically injects ApiClient
final client = ref.watch(apiClientProvider);
return UserRepositoryImpl(client);
});
final getUserUseCaseProvider = Provider<GetUserUseCase>((ref) {
final repository = ref.watch(userRepositoryProvider);
return GetUserUseCase(repository);
});
// Usage in a widget
class UserScreen extends ConsumerWidget {
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.
// ❌ Bad: everything rebuilds on each change
class BadExample extends ConsumerWidget {
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 {
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 {
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.
class OptimizedList extends StatelessWidget {
final List<Item> items;
const OptimizedList({super.key, required this.items});
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 {
ConsumerState<InfiniteList> createState() => _InfiniteListState();
}
class _InfiniteListState extends ConsumerState<InfiniteList> {
final _scrollController = ScrollController();
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();
}
}
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.
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.
// Complex animations benefit from Impeller
class SmoothAnimation extends StatefulWidget {
State<SmoothAnimation> createState() => _SmoothAnimationState();
}
class _SmoothAnimationState extends State<SmoothAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat();
}
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.
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.
class RegistrationForm extends StatefulWidget {
State<RegistrationForm> createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmController = TextEditingController();
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.
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.
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.
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;
}
}
}# terminal
# Launch with specific environment
flutter run --dart-define=ENV=staging
# Production build
flutter build apk --dart-define=ENV=prod --release19. Jak zaimplementowac flavoring dla wielu wariantow aplikacji?
Flavoring tworzy wiele wariantow (klient1, klient2) z odrebnymi konfiguracjami (ikona, nazwa, API).
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.
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<void> saveToken(String token) async {
await _storage.write(key: 'auth_token', value: token);
}
Future<String?> getToken() async {
return _storage.read(key: 'auth_token');
}
Future<void> 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
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
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.
Tagi
Udostępnij
Powiązane artykuły

Flutter: Budowanie pierwszej aplikacji wieloplatformowej
Kompletny przewodnik po tworzeniu wieloplatformowej aplikacji mobilnej z Flutter i Dart. Widgety, zarzadzanie stanem, nawigacja i dobre praktyki dla poczatkujacych.

Zarządzanie stanem we Flutterze w 2026: Riverpod vs Bloc vs GetX
Praktyczne porównanie rozwiązań do zarządzania stanem we Flutterze w 2026 roku. Riverpod 3.0, Bloc 9.0 i GetX ocenione na podstawie przykładów kodu, wydajności i strategii migracji.

25 pytań rekrutacyjnych z Laravel i PHP w 2026 roku
25 najczęściej zadawanych pytań rekrutacyjnych z Laravel: Service Container, Eloquent ORM, middleware, kolejki, bezpieczeństwo, testowanie i wzorce architektoniczne z przykładami kodu.