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.

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.
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.
// 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'),
);
}
}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).
// 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'),
],
),
),
),
);
}
}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.
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));
}
}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.
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),
],
);
}
}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.
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.
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);
}
}
}Gunakan Future.wait untuk paralelisme dan async loop untuk pemrosesan sekuensial.
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.
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!);
},
);
}
}Stream .broadcast() memungkinkan banyak listener, berbeda dengan Stream single-subscription.
Siap menguasai wawancara Flutter Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
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.
// 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 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.
// 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;
}
}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.
// 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(),
),
);
});
}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.
// ❌ 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
],
);
}
}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.
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]),
);
}
}Untuk daftar yang sangat panjang (1000+ item), pertimbangkan ListView.separated atau paket khusus seperti scrollable_positioned_list.
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.
// 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 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.
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.
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
}
}
}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.
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.
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.
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. Bagaimana cara mengimplementasikan flavoring untuk beberapa varian aplikasi?
Flavoring membuat beberapa varian (client1, client2) dengan konfigurasi berbeda (ikon, nama, 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;
}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.
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;
}
}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
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
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.
Tag
Bagikan
Artikel terkait

Flutter: Membangun Aplikasi Lintas Platform Pertama
Panduan lengkap untuk membangun aplikasi mobile lintas platform dengan Flutter dan Dart. Widget, manajemen state, navigasi, dan praktik terbaik untuk pemula.

State Management Flutter di 2026: Riverpod vs Bloc vs GetX — Panduan Lengkap
Perbandingan praktis solusi state management Flutter di tahun 2026. Riverpod 3.0, Bloc 9.0, dan GetX dievaluasi dengan contoh kode nyata, analisis performa, dan strategi migrasi.

Pertanyaan Wawancara Laravel dan PHP: 25 Teratas di 2026
25 pertanyaan wawancara Laravel dan PHP yang paling sering ditanyakan. Service Container, Eloquent ORM, middleware, queues, dan deployment produksi dengan jawaban lengkap beserta contoh kode.