# 20 Pertanyaan Wawancara Flutter Teratas untuk Developer Mobile > Persiapkan wawancara Flutter dengan 20 pertanyaan yang paling sering ditanyakan. Widget, state management, Dart, arsitektur, dan praktik terbaik dijelaskan secara detail dengan contoh kode. - Published: 2026-01-24 - Updated: 2026-04-12 - Author: SharpSkill - Tags: flutter, dart, interview, mobile development, technical interview - Reading time: 15 min --- Wawancara Flutter menguji penguasaan framework, bahasa Dart, dan pola arsitektur mobile. Panduan ini mencakup 20 pertanyaan yang paling sering diajukan, dari dasar-dasar hingga konsep lanjutan, dengan jawaban terperinci dan contoh kode. > **Tips Wawancara** > > Pewawancara menghargai kandidat yang menjelaskan "mengapa" selain "bagaimana". Untuk setiap konsep, memahami kasus penggunaan dan trade-off teknis menjadi pembeda utama. ## Pertanyaan Dasar Flutter dan Dart ### 1. Apa perbedaan antara StatelessWidget dan StatefulWidget? **StatelessWidget** merepresentasikan widget yang bersifat immutable, tampilannya bergantung sepenuhnya pada konfigurasi awal. Setelah dibangun, widget ini tidak pernah berubah. **StatefulWidget** memelihara state yang bersifat mutable dan dapat berubah seiring waktu, sehingga memicu rebuild widget. ```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'), ); } } ``` Aturannya: gunakan StatelessWidget secara default, dan StatefulWidget hanya untuk state yang bersifat lokal pada widget. ### 2. Bagaimana cara kerja widget tree di Flutter? Flutter mengorganisasi antarmuka ke dalam tiga tree yang saling terhubung: **Widget Tree** (deklarasi immutable), **Element Tree** (lifecycle dan binding), serta **Render Tree** (layout dan painting). ```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'), ], ), ), ), ); } } ``` Ketika `setState` dipanggil, Flutter membandingkan widget tree lama dan baru untuk membangun ulang hanya elemen yang berubah. Proses diferensiasi ini bergantung pada key dan tipe widget. ### 3. Apa itu const constructor dan mengapa menggunakannya? **Const constructor** membuat widget pada saat kompilasi, bukan saat runtime. Flutter dapat menggunakan kembali instance yang sama, sehingga meningkatkan performa dengan menghindari rebuild yang tidak diperlukan. ```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)); } } ``` Flutter analyzer menandai peluang yang terlewat melalui lint `prefer_const_constructors`. ### 4. Jelaskan berbagai jenis Key di Flutter **Key** menjaga state widget selama proses reorganisasi. Tanpa key, Flutter mengandalkan posisi dalam tree, yang dapat menyebabkan bug saat mengurutkan ulang daftar. ```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), ], ); } } ``` GlobalKey juga memungkinkan akses ke state widget dari mana saja, tetapi sebaiknya digunakan secara terbatas. ## Pertanyaan Dart ### 5. Apa perbedaan antara final dan const di Dart? **final** mendefinisikan variabel yang hanya dapat diassign satu kali dan dievaluasi pada saat runtime. **const** membuat konstanta pada saat kompilasi, bersifat immutable dan dievaluasi sebelum eksekusi. ```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) } ``` Di Flutter, gunakan const untuk widget statis dan final untuk nilai yang dihitung. ### 6. Bagaimana cara kerja Future dan async/await? **Future** merepresentasikan nilai yang akan tersedia di kemudian waktu. **async/await** menyediakan sintaks yang mudah dibaca untuk menangani operasi asynchronous tanpa callback yang bersarang. ```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); } } } ``` Gunakan `Future.wait` untuk paralelisme dan async loop untuk pemrosesan sekuensial. > **FutureBuilder vs Riverpod** > > FutureBuilder cocok untuk kasus sederhana, tetapi Riverpod (AsyncValue) menawarkan manajemen cache, penanganan error, dan kemampuan refresh yang lebih baik untuk aplikasi yang kompleks. ### 7. Jelaskan Stream dan penggunaannya **Stream** merepresentasikan urutan nilai asynchronous, ideal untuk event real-time: WebSocket, data sensor, atau interaksi pengguna. ```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!); }, ); } } ``` Stream `.broadcast()` memungkinkan banyak listener, berbeda dengan Stream single-subscription. ## Pertanyaan Arsitektur dan State Management ### 8. Bandingkan solusi state management: Provider, Riverpod, Bloc Setiap solusi menangani kebutuhan yang berbeda. **Provider** menawarkan kesederhanaan dan integrasi native. **Riverpod** membawa type-safety dan testability. **Bloc** menerapkan arsitektur event-driven yang ketat. ```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 direkomendasikan untuk proyek baru berkat API modern dan kemudahan pengujiannya. ### 9. Apa itu Clean Architecture di Flutter? **Clean Architecture** memisahkan kode ke dalam lapisan independen: Domain (logika bisnis), Data (sumber data), dan Presentation (UI). Pemisahan ini memudahkan pengujian dan pemeliharaan. ```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; } } ``` Lapisan Domain tidak bergantung pada apa pun, Data bergantung pada Domain, dan Presentation bergantung pada keduanya. ### 10. Bagaimana cara mengimplementasikan dependency injection? **Dependency injection** memisahkan komponen dengan menyediakan dependensinya dari luar. Riverpod unggul dalam hal ini melalui sistem provider-nya. ```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(), ), ); }); } ``` Keunggulan utamanya: pengujian dapat mengganti dependensi apa pun dengan mock. ## Pertanyaan Performa ### 11. Bagaimana cara mengoptimalkan performa rebuild? Meminimalkan rebuild meningkatkan performa. Teknik utama meliputi: widget const, pemecahan granular, dan selector 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 ], ); } } ``` Gunakan DevTools Performance untuk mengidentifikasi rebuild yang berlebihan. ### 12. Bagaimana cara mengoptimalkan daftar yang panjang? Daftar panjang memerlukan **lazy loading** dengan ListView.builder. Hindari penggunaan ListView dengan children langsung untuk lebih dari 20 item. ```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]), ); } } ``` Untuk daftar yang sangat panjang (1000+ item), pertimbangkan `ListView.separated` atau paket khusus seperti `scrollable_positioned_list`. > **Cache Gambar** > > Untuk daftar dengan gambar, gunakan `cached_network_image` untuk menghindari pemuatan ulang. Gambar yang tidak di-cache menyebabkan scroll tersendat. ### 13. Jelaskan cara kerja mesin rendering Impeller **Impeller** menggantikan Skia sebagai mesin rendering default (Flutter 3.16+). Impeller melakukan prakompilasi shader untuk menghilangkan "jank" pada tampilan pertama. ```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 diaktifkan secara default di iOS. Di Android, periksa kompatibilitasnya dengan `flutter run --enable-impeller`. ## Pertanyaan Navigasi dan Form ### 14. Bagaimana cara menangani navigasi dengan deep linking? **Deep linking** memungkinkan pembukaan aplikasi pada layar tertentu melalui URL. GoRouter menangani fungsionalitas ini secara native. ```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), ); } ``` Konfigurasi file native (AndroidManifest.xml, Info.plist) diperlukan untuk mengaktifkan deep linking pada tingkat sistem. ### 15. Bagaimana cara memvalidasi form secara efektif? Validasi menggabungkan Form, TextFormField, dan custom validator. Paket bertipe Zod seperti `formz` menambahkan struktur. ```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 } } } ``` Mode `AutovalidateMode.onUserInteraction` menawarkan UX terbaik: error ditampilkan setelah interaksi pengguna. ## Pertanyaan Testing ### 16. Bagaimana cara menguji widget Flutter? Widget test memverifikasi UI tanpa bergantung pada perangkat. Paket `flutter_test` menyediakan utilitas yang diperlukan. ```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()` memajukan satu frame, sedangkan `pumpAndSettle()` menunggu semua animasi selesai. ### 17. Bagaimana cara menguji provider Riverpod? Riverpod memudahkan pengujian melalui `ProviderContainer` dan override. ```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, ); }); } ``` Pengujian provider berjalan cepat karena tidak memerlukan rendering UI. ## Pertanyaan Deployment ### 18. Bagaimana cara mengelola environment yang berbeda (dev, staging, prod)? Environment dikonfigurasi melalui file `.env` atau konstanta compile-time menggunakan `--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. Bagaimana cara mengimplementasikan flavoring untuk beberapa varian aplikasi? **Flavoring** membuat beberapa varian (client1, client2) dengan konfigurasi berbeda (ikon, nama, 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; } ``` Konfigurasi file native Android (build.gradle) dan iOS (xcconfig) diperlukan untuk setiap flavor. ### 20. Apa saja praktik terbaik keamanan Flutter? Keamanan mencakup penyimpanan data sensitif, validasi input, dan perlindungan terhadap reverse engineering. ```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; } } ``` Jangan pernah menyimpan secret di dalam kode. Gunakan `flutter_secure_storage` untuk token dan `--obfuscate` untuk build produksi. ## Kesimpulan Ke-20 pertanyaan ini mencakup aspek-aspek esensial wawancara Flutter: dasar-dasar framework, penguasaan Dart, pola arsitektur, dan praktik terbaik produksi. Kunci keberhasilan terletak pada pemahaman mendalam terhadap mekanisme yang mendasarinya, bukan sekadar sintaks. ### Checklist Persiapan - Kuasai perbedaan StatelessWidget/StatefulWidget dan kasus penggunaannya - Pahami widget tree dan optimasi rebuild - Latih async/await, Future, dan Stream dengan latihan praktis - Implementasikan proyek dengan Riverpod untuk state management - Ketahui GoRouter dan deep linking - Tulis unit test dan widget test - Pahami konfigurasi multi-environment Latihan rutin pada proyek pribadi tetap menjadi cara terbaik untuk mengkonsolidasikan pengetahuan ini. Setiap pertanyaan yang dibahas di sini layak dieksplorasi lebih dalam dengan kode nyata untuk menguasai seluk-beluk framework Flutter. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/flutter/flutter-interview-questions