Top 20 Flutter Sollicitatievragen voor Mobiele Ontwikkelaars
Bereid je voor op Flutter-sollicitatiegesprekken met de 20 meest gestelde vragen. Widgets, state management, Dart, architectuur en best practices uitgelegd met codevoorbeelden.

Flutter-sollicitatiegesprekken toetsen de beheersing van het framework, de programmeertaal Dart en architectuurpatronen voor mobiele apps. Deze gids behandelt de 20 meest gestelde vragen, van basisbegrippen tot geavanceerde concepten, met uitgebreide antwoorden en codevoorbeelden.
Interviewers waarderen kandidaten die naast het "hoe" ook het "waarom" uitleggen. Bij elk concept maakt het verschil wanneer iemand de use cases en technische afwegingen begrijpt.
Fundamentele Flutter- en Dart-vragen
1. Wat is het verschil tussen StatelessWidget en StatefulWidget?
StatelessWidget vertegenwoordigt een onveranderlijk widget waarvan het uiterlijk uitsluitend afhangt van de initiële configuratie. Na het bouwen verandert het nooit meer. StatefulWidget behoudt veranderlijke state die in de loop van de tijd kan evolueren en zo widget-rebuilds triggert.
// 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'),
);
}
}De vuistregel: gebruik standaard StatelessWidget en reserveer StatefulWidget uitsluitend voor widget-lokale state.
2. Hoe werkt de widgetboom van Flutter?
Flutter organiseert de interface in drie onderling verbonden bomen: de Widget Tree (onveranderlijke declaratie), de Element Tree (levenscyclus en binding) en de Render Tree (layout en painting).
// 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'),
],
),
),
),
);
}
}Bij het aanroepen van setState vergelijkt Flutter de oude en nieuwe widgetboom om alleen de gewijzigde elementen opnieuw te bouwen. Deze differentiatie berust op keys en widgettypes.
3. Wat is een const constructor en waarom wordt deze gebruikt?
Const constructors creëren widgets tijdens compilatie in plaats van tijdens runtime. Flutter kan deze instanties hergebruiken, wat de prestaties verbetert door onnodige rebuilds te vermijden.
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));
}
}De Flutter-analyzer signaleert gemiste mogelijkheden via de prefer_const_constructors-lintregel.
4. Beschrijf de verschillende typen Keys in Flutter
Keys behouden de widgetstate bij herordening. Zonder keys baseert Flutter zich op de positie in de boom, wat tot bugs kan leiden bij het herschikken van lijsten.
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 bieden ook de mogelijkheid om de state van een widget van buitenaf te benaderen, maar het gebruik ervan dient beperkt te blijven.
Dart-vragen
5. Wat is het verschil tussen final en const in Dart?
final definieert een variabele die slechts één keer toegewezen kan worden, geëvalueerd tijdens runtime. const creëert een compilatietijdconstante, onveranderlijk en geëvalueerd vóór uitvoering.
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)
}In Flutter verdient const de voorkeur voor statische widgets en final voor berekende waarden.
6. Hoe werken Futures en async/await?
Future vertegenwoordigt een waarde die later beschikbaar zal zijn. async/await biedt een leesbare syntax voor het afhandelen van deze asynchrone operaties zonder geneste callbacks.
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 voor parallellisme, async-loops voor sequentiële verwerking.
FutureBuilder werkt voor eenvoudige gevallen, maar Riverpod (AsyncValue) biedt beter cachebeheer, foutafhandeling en verversingsmogelijkheden voor complexe applicaties.
7. Leg Streams en hun gebruik uit
Streams vertegenwoordigen asynchrone reeksen van waarden, ideaal voor realtime-events: WebSockets, sensorgegevens of gebruikersinteracties.
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!);
},
);
}
}.broadcast() Streams staan meerdere luisteraars toe, in tegenstelling tot single-subscription Streams.
Klaar om je Flutter gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Architectuur- en State Management-vragen
8. Vergelijk state management-oplossingen: Provider, Riverpod, Bloc
Elke oplossing beantwoordt aan verschillende behoeften. Provider biedt eenvoud en native integratie. Riverpod brengt type-safety en testbaarheid. Bloc dwingt een strikte event-driven architectuur af.
// 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 wordt aanbevolen voor nieuwe projecten dankzij de moderne API en het gemak bij het testen.
9. Wat is Clean Architecture in Flutter?
Clean Architecture scheidt code in onafhankelijke lagen: Domain (bedrijfslogica), Data (databronnen) en Presentation (UI). Deze scheiding vergemakkelijkt het testen en onderhoud.
// 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;
}
}De Domain-laag hangt van niets af, Data hangt af van Domain, Presentation hangt af van beide.
10. Hoe wordt dependency injection geïmplementeerd?
Dependency injection ontkoppelt componenten door hun afhankelijkheden van buitenaf aan te leveren. Riverpod blinkt hierin uit met zijn providers.
// 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(),
),
);
});
}Het grootste voordeel: tests kunnen elke afhankelijkheid vervangen door een mock.
Prestatievragen
11. Hoe worden rebuild-prestaties geoptimaliseerd?
Het minimaliseren van rebuilds verbetert de prestaties. Belangrijke technieken: const-widgets, granulaire opsplitsing en Riverpod-selectors.
// ❌ 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
],
);
}
}Gebruik DevTools Performance om overmatige rebuilds te identificeren.
12. Hoe worden lange lijsten geoptimaliseerd?
Lange lijsten vereisen lazy loading met ListView.builder. Vermijd ListView met directe children bij meer dan 20 items.
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]),
);
}
}Bij zeer lange lijsten (1000+ items) kan ListView.separated of een gespecialiseerd package zoals scrollable_positioned_list overwogen worden.
Gebruik bij lijsten met afbeeldingen cached_network_image om herladen te voorkomen. Niet-gecachede afbeeldingen veroorzaken scroll-haperingen.
13. Hoe werkt de Impeller-renderingengine?
Impeller vervangt Skia als standaard renderingengine (Flutter 3.16+). Het compileert shaders vooraf om "jank" bij de eerste weergave te elimineren.
// 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 is standaard ingeschakeld op iOS. Op Android kan de compatibiliteit gecontroleerd worden met flutter run --enable-impeller.
Navigatie- en Formuliervragen
14. Hoe wordt navigatie met deep linking afgehandeld?
Deep linking maakt het mogelijk de app op een specifiek scherm te openen via een URL. GoRouter handelt deze functionaliteit native af.
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),
);
}Natieve bestanden (AndroidManifest.xml, Info.plist) moeten geconfigureerd worden om systeemniveau deep linking in te schakelen.
15. Hoe worden formulieren effectief gevalideerd?
Validatie combineert Form, TextFormField en aangepaste validators. Zod-achtige packages zoals formz voegen structuur toe.
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
}
}
}De modus AutovalidateMode.onUserInteraction biedt de beste UX: fouten worden pas na interactie getoond.
Testvragen
16. Hoe worden Flutter-widgets getest?
Widgettests verifiëren de UI zonder afhankelijkheid van een apparaat. Het flutter_test-package biedt de benodigde hulpmiddelen.
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() gaat één frame vooruit, pumpAndSettle() wacht tot alle animaties voltooid zijn.
17. Hoe worden Riverpod-providers getest?
Riverpod vergemakkelijkt het testen via ProviderContainer en overrides.
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,
);
});
}Providertests zijn snel omdat ze geen UI-rendering vereisen.
Deploymentvragen
18. Hoe worden verschillende omgevingen (dev, staging, prod) beheerd?
Omgevingen worden geconfigureerd via .env-bestanden of compilatietijdconstanten met --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. Hoe wordt flavoring voor meerdere appvarianten geïmplementeerd?
Flavoring creëert meerdere varianten (client1, client2) met afzonderlijke configuraties (pictogram, naam, 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;
}Natieve bestanden Android (build.gradle) en iOS (xcconfig) moeten voor elke flavor geconfigureerd worden.
20. Wat zijn de best practices voor Flutter-beveiliging?
Beveiliging omvat de opslag van gevoelige gegevens, inputvalidatie en bescherming tegen reverse engineering.
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;
}
}Sla geheimen nooit op in de code. Gebruik flutter_secure_storage voor tokens en --obfuscate voor productiebuilds.
Conclusie
Deze 20 vragen behandelen de essentiële aspecten van Flutter-sollicitatiegesprekken: basisprincipes van het framework, beheersing van Dart, architectuurpatronen en best practices voor productie. Succes schuilt in een diepgaand begrip van de onderliggende mechanismen, niet alleen de syntax.
Voorbereidingschecklist
- ✅ Beheers het verschil tussen StatelessWidget en StatefulWidget en de bijbehorende use cases
- ✅ Begrijp de widgetboom en rebuild-optimalisatie
- ✅ Oefen async/await, Futures en Streams met praktische oefeningen
- ✅ Implementeer een project met Riverpod voor state management
- ✅ Ken GoRouter en deep linking
- ✅ Schrijf unittests en widgettests
- ✅ Begrijp multi-omgevingsconfiguratie
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Regelmatige oefening met persoonlijke projecten blijft de beste manier om deze kennis te consolideren. Elke hier behandelde vraag verdient verdere verdieping met echte code om de subtiliteiten van het Flutter-framework te beheersen.
Tags
Delen
Gerelateerde artikelen

Flutter: Een eerste cross-platform applicatie bouwen
Volledige gids voor het bouwen van een mobiele cross-platform applicatie met Flutter en Dart. Widgets, statusbeheer, navigatie en best practices voor beginners.

Flutter State Management in 2026: Riverpod vs Bloc vs GetX Vergeleken
Een praktische vergelijking van Flutter state management oplossingen in 2026. Riverpod 3.0, Bloc 9.0 en GetX geevalueerd met echte codevoorbeelden, prestatiebenchmarks en migratiestrategieen.

Laravel en PHP sollicitatievragen: de Top 25 in 2026
De 25 meest gestelde Laravel- en PHP-sollicitatievragen. Eloquent ORM, middleware, Artisan, queues, tests en architectuur met uitgebreide antwoorden en codevoorbeelden.