# Top 20 Câu Hỏi Phỏng Vấn Flutter Dành Cho Lập Trình Viên Mobile > Chuẩn bị cho buổi phỏng vấn Flutter với 20 câu hỏi thường gặp nhất. Widget, quản lý state, Dart, kiến trúc và các best practice được giải thích chi tiết kèm code mẫu. - Published: 2026-01-24 - Updated: 2026-04-12 - Author: SharpSkill - Tags: flutter, dart, interview, mobile development, technical interview - Reading time: 15 min --- Buổi phỏng vấn Flutter đánh giá khả năng thành thạo framework, ngôn ngữ Dart và các mô hình kiến trúc mobile. Bài viết này tổng hợp 20 câu hỏi được hỏi thường xuyên nhất, từ kiến thức nền tảng đến các khái niệm nâng cao, kèm theo câu trả lời chi tiết và ví dụ code minh họa. > **Mẹo Phỏng Vấn** > > Nhà tuyển dụng đánh giá cao ứng viên giải thích được "tại sao" bên cạnh "như thế nào". Với mỗi khái niệm, việc hiểu rõ các trường hợp sử dụng và sự đánh đổi về mặt kỹ thuật sẽ tạo nên sự khác biệt. ## Câu Hỏi Cơ Bản về Flutter và Dart ### 1. Sự khác biệt giữa StatelessWidget và StatefulWidget là gì? **StatelessWidget** đại diện cho một widget bất biến, giao diện của nó chỉ phụ thuộc vào cấu hình ban đầu. Sau khi được xây dựng, nó không bao giờ thay đổi. **StatefulWidget** duy trì state có thể biến đổi theo thời gian, kích hoạt việc 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'), ); } } ``` Quy tắc chung: sử dụng StatelessWidget theo mặc định, chỉ dùng StatefulWidget khi cần quản lý state cục bộ trong widget. ### 2. Widget tree trong Flutter hoạt động như thế nào? Flutter tổ chức giao diện thành ba cây liên kết với nhau: **Widget Tree** (khai báo bất biến), **Element Tree** (vòng đời và liên kết), và **Render Tree** (bố cục và vẽ giao diện). ```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'), ], ), ), ), ); } } ``` Khi `setState` được gọi, Flutter so sánh cây widget cũ và mới để chỉ rebuild những phần tử đã thay đổi. Quá trình phân biệt này dựa trên key và kiểu widget. ### 3. Const constructor là gì và tại sao nên sử dụng? **Const constructor** tạo widget tại thời điểm biên dịch thay vì thời điểm chạy. Flutter có thể tái sử dụng các instance này, cải thiện hiệu suất bằng cách tránh các lần rebuild không cần thiết. ```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 sẽ cảnh báo các cơ hội bị bỏ lỡ thông qua lint rule `prefer_const_constructors`. ### 4. Giải thích các loại Key khác nhau trong Flutter **Key** giữ nguyên state của widget trong quá trình sắp xếp lại. Khi không có key, Flutter dựa vào vị trí trong cây, điều này có thể gây ra lỗi khi thay đổi thứ tự danh sách. ```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 cũng cho phép truy cập state của widget từ bất kỳ đâu, nhưng cần được sử dụng một cách tiết kiệm. ## Câu Hỏi về Dart ### 5. Sự khác biệt giữa final và const trong Dart là gì? **final** khai báo một biến chỉ được gán giá trị một lần, được đánh giá tại thời điểm chạy. **const** tạo một hằng số tại thời điểm biên dịch, bất biến và được đánh giá trước khi thực thi. ```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) } ``` Trong Flutter, ưu tiên sử dụng const cho các widget tĩnh và final cho các giá trị được tính toán. ### 6. Future và async/await hoạt động như thế nào? **Future** đại diện cho một giá trị sẽ có sẵn trong tương lai. **async/await** cung cấp cú pháp dễ đọc để xử lý các thao tác bất đồng bộ mà không cần callback lồng nhau. ```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); } } } ``` Sử dụng `Future.wait` cho xử lý song song, vòng lặp async cho xử lý tuần tự. > **FutureBuilder vs Riverpod** > > FutureBuilder phù hợp cho các trường hợp đơn giản, nhưng Riverpod (AsyncValue) mang lại khả năng quản lý cache, xử lý lỗi và refresh tốt hơn cho các ứng dụng phức tạp. ### 7. Giải thích Stream và cách sử dụng **Stream** đại diện cho chuỗi giá trị bất đồng bộ, lý tưởng cho các sự kiện thời gian thực: WebSocket, dữ liệu cảm biến, hoặc tương tác người dùng. ```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()` cho phép nhiều listener lắng nghe cùng lúc, khác với Stream đăng ký đơn (single-subscription). ## Câu Hỏi về Kiến Trúc và Quản Lý State ### 8. So sánh các giải pháp quản lý state: Provider, Riverpod, Bloc Mỗi giải pháp đáp ứng nhu cầu khác nhau. **Provider** mang lại sự đơn giản và tích hợp tự nhiên. **Riverpod** cung cấp type-safety và khả năng test tốt. **Bloc** áp dụng kiến trúc event-driven nghiêm ngặt. ```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 được khuyến nghị cho các dự án mới nhờ API hiện đại và khả năng viết test dễ dàng. ### 9. Clean Architecture trong Flutter là gì? **Clean Architecture** phân tách code thành các tầng độc lập: Domain (logic nghiệp vụ), Data (nguồn dữ liệu), và Presentation (giao diện). Sự phân tách này giúp việc testing và bảo trì trở nên dễ dàng hơn. ```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; } } ``` Tầng Domain không phụ thuộc vào bất kỳ thành phần nào khác, Data phụ thuộc vào Domain, Presentation phụ thuộc vào cả hai. ### 10. Làm thế nào để triển khai dependency injection? **Dependency injection** tách rời các thành phần bằng cách cung cấp các phụ thuộc từ bên ngoài. Riverpod xử lý rất tốt điều này thông qua hệ thống provider. ```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(), ), ); }); } ``` Lợi thế lớn nhất: các bài test có thể thay thế bất kỳ dependency nào bằng mock. ## Câu Hỏi về Hiệu Suất ### 11. Làm thế nào để tối ưu hiệu suất rebuild? Giảm thiểu số lần rebuild giúp cải thiện hiệu suất đáng kể. Các kỹ thuật chính bao gồm: widget const, chia nhỏ widget, và Riverpod selector. ```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 ], ); } } ``` Sử dụng DevTools Performance để xác định các lần rebuild quá mức. ### 12. Làm thế nào để tối ưu danh sách dài? Danh sách dài yêu cầu **lazy loading** với ListView.builder. Cần tránh sử dụng ListView với children trực tiếp khi có hơn 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]), ); } } ``` Với danh sách rất dài (trên 1000 item), cân nhắc sử dụng `ListView.separated` hoặc các package chuyên dụng như `scrollable_positioned_list`. > **Cache Hình Ảnh** > > Với danh sách có chứa hình ảnh, hãy sử dụng `cached_network_image` để tránh tải lại. Hình ảnh không được cache sẽ gây giật lag khi cuộn. ### 13. Giải thích cách hoạt động của rendering engine Impeller **Impeller** thay thế Skia làm rendering engine mặc định (Flutter 3.16+). Nó biên dịch trước shader để loại bỏ hiện tượng "jank" khi hiển thị lần đầu. ```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 được bật mặc định trên iOS. Trên Android, có thể kiểm tra tương thích bằng lệnh `flutter run --enable-impeller`. ## Câu Hỏi về Điều Hướng và Form ### 14. Làm thế nào để xử lý điều hướng với deep linking? **Deep linking** cho phép mở ứng dụng tại một màn hình cụ thể thông qua URL. GoRouter xử lý tính năng này một cách tự nhiên và hiệu quả. ```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), ); } ``` Cần cấu hình các file native (AndroidManifest.xml, Info.plist) để kích hoạt deep linking ở cấp hệ thống. ### 15. Làm thế nào để validate form hiệu quả? Việc validate kết hợp Form, TextFormField và các validator tùy chỉnh. Các package kiểu Zod như `formz` giúp thêm cấu trúc cho quá trình này. ```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 } } } ``` Chế độ `AutovalidateMode.onUserInteraction` mang lại trải nghiệm người dùng tốt nhất: lỗi chỉ hiển thị sau khi người dùng tương tác với trường nhập liệu. ## Câu Hỏi về Testing ### 16. Làm thế nào để test widget trong Flutter? Widget test xác minh giao diện mà không cần thiết bị thực. Package `flutter_test` cung cấp các tiện ích cần thiết cho việc này. ```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()` tiến thêm một frame, `pumpAndSettle()` chờ đến khi tất cả animation hoàn thành. ### 17. Làm thế nào để test Riverpod provider? Riverpod hỗ trợ testing thuận tiện thông qua `ProviderContainer` và cơ chế 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, ); }); } ``` Provider test chạy rất nhanh vì không cần render giao diện. ## Câu Hỏi về Triển Khai ### 18. Làm thế nào để quản lý các môi trường khác nhau (dev, staging, prod)? Các môi trường được cấu hình thông qua file `.env` hoặc hằng số compile-time với `--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. Làm thế nào để triển khai flavoring cho nhiều biến thể ứng dụng? **Flavoring** tạo nhiều biến thể (client1, client2) với cấu hình riêng biệt (icon, tên, API) từ cùng một codebase. ```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; } ``` Cần cấu hình các file native Android (build.gradle) và iOS (xcconfig) cho từng flavor. ### 20. Các best practice về bảo mật trong Flutter là gì? Bảo mật bao gồm lưu trữ dữ liệu nhạy cảm, validate input, và bảo vệ chống 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; } } ``` Không bao giờ lưu secret trực tiếp trong code. Sử dụng `flutter_secure_storage` cho token và flag `--obfuscate` cho bản production. ## Kết Luận 20 câu hỏi này bao quát các khía cạnh thiết yếu của buổi phỏng vấn Flutter: nền tảng framework, thành thạo Dart, mô hình kiến trúc, và các best practice trong production. Thành công nằm ở việc hiểu sâu các cơ chế bên dưới, không chỉ là cú pháp. ### Danh Sách Kiểm Tra Trước Phỏng Vấn - Nắm vững sự khác biệt StatelessWidget/StatefulWidget và các trường hợp sử dụng - Hiểu rõ widget tree và tối ưu hóa rebuild - Luyện tập async/await, Future và Stream với các bài tập thực hành - Triển khai một dự án với Riverpod để quản lý state - Nắm rõ GoRouter và deep linking - Viết unit test và widget test - Hiểu cấu hình đa môi trường Việc luyện tập thường xuyên trên các dự án cá nhân vẫn là cách tốt nhất để củng cố kiến thức này. Mỗi câu hỏi được đề cập ở đây đều xứng đáng được khám phá sâu hơn với code thực tế để nắm bắt các tinh tế của Flutter framework. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/flutter/flutter-interview-questions