Tổng hợp 81 câu hỏi phỏng vấn Flutter kèm đáp án chi tiết, song ngữ Việt–Anh — sắp xếp từ cơ bản đến nâng cao. Ôn luyện miễn phí, tự động lưu tiến độ.
var cho phép Dart tự suy luận kiểu dữ liệu một lần lúc khai báo và không thể thay đổi kiểu sau đó. dynamic cho phép kiểu thay đổi trong runtime, bỏ qua kiểm tra kiểu tĩnh. final tạo biến không thể gán lại sau…
Null safety nghĩa là các biến không thể chứa null trừ khi được đánh dấu nullable bằng ?. Điều này giúp phòng ngừa lỗi NullPointerException ở runtime bằng cách phát hiện lỗi ngay lúc compile. Với null safety, lập trình viên phải khai báo rõ…
Toán tử ! bảo Dart coi biến nullable là non-nullable mà không kiểm tra. Ví dụ: String name = nullableName! — điều này khẳng định nullableName không phải null. Chỉ dùng khi bạn chắc chắn 100% biến không null; nếu sai sẽ ném lỗi runtime.…
Toán tử null-aware giúp xử lý giá trị nullable an toàn. ?. truy cập thuộc tính chỉ khi đối tượng khác null: person?.name. ?? cung cấp giá trị mặc định nếu bên trái là null: name ?? "Unknown". ??= chỉ gán nếu biến đang là…
List là collection có thứ tự và cho phép trùng lặp: [1, 2, 2, 3]. Set là collection không có thứ tự với các phần tử duy nhất: {1, 2, 3}. Map lưu trữ cặp key-value: {"name": "John", "age": 30}. Chọn List khi thứ tự…
Future là một giá trị sẽ có một lần trong tương lai (giống Promise của JS); Stream là chuỗi nhiều giá trị đến dần theo thời gian, như một băng chuyền. Trong Flutter, FutureBuilder dựng UI từ một Future, StreamBuilder dựng UI từ một Stream.…
async đánh dấu hàm là bất đồng bộ; await tạm dừng thực thi cho đến khi Future hoàn thành. Điều này giúp code bất đồng bộ đọc dễ như code đồng bộ. await chỉ hoạt động trong hàm async.
Type promotion là khi Dart tự động thu hẹp kiểu của biến dựa trên luồng điều khiển. Nếu bạn có String? và kiểm tra if (name != null), trong khối đó Dart coi name là String non-nullable mà không cần !. Điều này xảy ra…
Positional parameters được truyền theo thứ tự: void greet(String name, int age) gọi là greet("John", 30). Named parameters dùng dấu ngoặc nhọn: void greet({String? name, int? age}) gọi là greet(name: "John", age: 30). Named parameters có thể tuỳ chọn; kết hợp từ khoá required để…
Closure là một hàm capture (giữ lại) các biến từ phạm vi bao quanh và có thể truy cập chúng ngay cả sau khi hàm ngoài đã kết thúc. Ví dụ: int makeAdder(int x) { return (int y) = x + y; } — hàm…
extends kế thừa từ lớp cha, tái sử dụng code: class Dog extends Animal. implements coi lớp như một interface và buộc override tất cả method (không tái sử dụng code): class Dog implements Animal. with thêm hành vi mixin mà không cần kế thừa:…
StatelessWidget chỉ phụ thuộc vào tham số truyền vào (và các InheritedWidget nó đọc); StatefulWidget có thêm một object State sống lâu, giữ dữ liệu thay đổi được trong lúc widget còn trên cây. - StatelessWidget: build() chạy lại khi widget cha rebuild với tham…
BuildContext là "địa chỉ" của widget trong cây — thực chất nó chính là Element của widget đó. Mọi thao tác "tìm ngược lên cây" đều đi qua context: Theme.of(context), MediaQuery.of(context), Navigator.of(context), ScaffoldMessenger.of(context). Vì of(context) tìm từ vị trí của context đi lên, dùng context…
Flutter tách UI thành ba cây với vai trò khác nhau: widget là bản mô tả, element là "phiên bản đang sống" giữ vị trí và state, render object lo layout và vẽ. - Widget tree: object bất biến, rẻ, được tạo lại mỗi lần…
Key giúp Flutter nhận ra "đây vẫn là widget cũ" khi vị trí của nó trong danh sách con thay đổi. Element so widget mới với cũ bằng Widget.canUpdate: cùng runtimeType và cùng key. Không có key, Flutter chỉ so theo kiểu + vị trí.…
ValueKey xác định widget bằng một giá trị cụ thể; hai widget cùng giá trị được coi là giống nhau. ObjectKey dùng identity (tham chiếu) của đối tượng; mỗi đối tượng duy nhất có key riêng. UniqueKey luôn tạo identity duy nhất, hữu ích khi…
const constructor tạo object ngay lúc compile, và hai biểu thức const giống hệt nhau trả về cùng một instance (canonicalization). Lợi ích trong Flutter đến từ chính điều đó: khi widget cha rebuild, element so widget mới với widget cũ. Nếu widget con là…
Row xếp widget theo chiều ngang; Column xếp theo chiều dọc. Mặc định chúng chiếm không gian tối thiểu. Expanded buộc con lấp đầy không gian còn lại đều nhau. Flexible cho phép con chiếm thêm không gian nhưng có thể nhỏ hơn nếu cần.…
setState() báo cho framework biết state trong State vừa đổi: nó chạy callback đồng bộ, rồi đánh dấu element là dirty (markNeedsBuild). Flutter gọi lại build() ở frame kế tiếp chứ không rebuild ngay, và nhiều lần setState trong cùng frame chỉ gây một lần…
initState() — gọi một lần khi widget được thêm vào (khởi tạo controller, listener). didChangeDependencies() — gọi sau initState và khi dependency thay đổi. build() — gọi thường xuyên, trả về widget tree. didUpdateWidget() — gọi khi parent rebuild với thuộc tính khác. dispose() —…
dispose() được gọi khi widget bị xoá vĩnh viễn khỏi cây. Cần dọn dẹp tài nguyên để tránh memory leak: đóng stream, dispose animation controller, huỷ đăng ký change notifier, huỷ timer. Không dispose đúng cách khiến app giữ tham chiếu đến object đã chết,…
RepaintBoundary cô lập một subtree để nó vẽ lại độc lập mà không ảnh hưởng toàn bộ cây. Dùng nó xung quanh widget vẽ lại thường xuyên (animation, progress indicator). Khi con của RepaintBoundary thay đổi, chỉ subtree đó mới vẽ lại. Lạm dụng tạo…
State management là cách xử lý dữ liệu thay đổi trong app (input người dùng, kết quả API). Quản lý state kém gây ra bug, memory leak và code khó bảo trì. Quản lý state tốt tách UI khỏi logic, giúp test dễ dàng và…
setState() rebuild toàn bộ cây con của widget đó, gây vấn đề hiệu năng với cây widget lớn. Logic nghiệp vụ trộn lẫn với UI làm code khó test. Không scale được — nhiều cập nhật state làm code khó theo dõi và khó debug.…
Provider là thư viện state management dùng ChangeNotifier để thông báo listener khi state thay đổi. Widget lắng nghe qua Consumer hoặc context.watch<T() và chỉ rebuild khi dữ liệu chúng phụ thuộc thay đổi. Provider nhẹ và phù hợp cho app nhỏ đến vừa, nhưng…
Riverpod (kế thừa tinh thần của Provider, cùng tác giả) hoàn toàn type-safe, không phụ thuộc BuildContext, và dùng functional provider. Riverpod sinh code lúc compile-time giúp phòng ngừa nhiều bug. Riverpod hỗ trợ parameterized provider, test tốt hơn và auto-dispose. Dùng Riverpod cho project…
BLoC (Business Logic Component) tách logic khỏi UI theo một chiều: UI gửi event vào, BLoC xử lý rồi phát ra state mới, UI chỉ vẽ theo state. Bộ package thường dùng là bloc + flutterbloc. Phía UI: BlocProvider cung cấp bloc xuống cây, BlocBuilder…
Ba lựa chọn phổ biến nhất, khác nhau chủ yếu ở mức ràng buộc kiến trúc: Provider Riverpod BLoC / Cubit ------------ Bản chất Wrapper quanh InheritedWidget + ChangeNotifier Viết lại từ Provider, không phụ thuộc BuildContext Luồng event → state trên Stream Lỗi thiếu…
ChangeNotifier là class đơn giản thông báo listener khi state thay đổi thông qua notifyListeners(). extend ChangeNotifier để tạo đối tượng observable. Provider theo dõi ChangeNotifier và rebuild khi notifyListeners() được gọi. Nó nhẹ nhưng cần quản lý thông báo thủ công. Cho state đơn…
Consumer theo dõi toàn bộ provider và rebuild bất cứ khi nào nó thay đổi. Selector cho phép chỉ theo dõi một phần cụ thể của state: Selector<UserProvider, String(selector: (, user) = user.name, ...) chỉ rebuild khi name thay đổi, không phải khi age thay…
GetIt là service locator cho dependency injection. Bạn đăng ký singleton hoặc factory: getIt.registerSingleton<Repository(Repository()), rồi truy cập ở bất kỳ đâu: getIt<Repository(). Bản thân nó không phải state management mà giúp clean architecture bằng cách quản lý dependency. Thường dùng kết hợp BLoC hoặc Riverpod…
Provider: Mock ChangeNotifier, dùng ProviderContainer để test. Riverpod: Dùng ProviderContainer, override provider bằng mock. BLoC: Test trực tiếp với bloctest, xác minh event → state transition. Không bao giờ phụ thuộc BuildContext trong code cần test; tách logic nghiệp vụ vào service class. State management…
Navigator 1.0 là API imperative: bạn tự push/pop từng màn lên stack. Navigator 2.0 (Router API) là declarative: stack màn hình được suy ra từ state của app, thường là URL. gorouter là package của Flutter team bọc Router API cho dễ dùng, và là…
GoRouter là routing package được Flutter chính thức khuyến nghị. Cung cấp URL-based declarative routing với path/query parameter. Tự động xử lý deep linking, hỗ trợ nested navigation với ShellRoute, thêm redirect logic cho auth guard, và cho phép type-safe route với code generation. Thay…
Cách 1 (Navigator cũ): Truyền qua constructor: Navigator.push(context, MaterialPageRoute(builder: () = DetailPage(item: item))). Cách 2 (GoRouter): Dùng path parameter: route: "/detail/:id" và truy cập qua GoRouterState. Cách 3 (State Management): Lưu data trong provider/BLoC, truy cập từ bất kỳ màn hình nào. GoRouter với path…
Deep linking là mở app tại màn hình cụ thể thông qua URL (như myapp://detail/123). Deep link đến từ push notification, web link hoặc system intent. GoRouter hỗ trợ deep linking tự động — định nghĩa route như path: GoRoute(path: '/detail/:id', ...) và GoRouter khớp…
GoRouter cung cấp GoRouterState với location, parameter, extra data: String id = state.pathParameters['id']!. Với nested navigation (bottom tab có stack độc lập), dùng ShellRoute. Với navigation dựa trên auth, dùng redirect() để kiểm tra state auth trước khi build page: if (!isLoggedIn) return '/login'. Nhờ…
ShellRoute bọc nhiều route trong một khung cha dùng chung (như bottom navigation bar). Route bên trong ShellRoute build trong context của parent đó, duy trì navigation stack độc lập cho mỗi tab. Đây là cách đúng để triển khai bottom tab navigation: mỗi tab…
Dùng callback redirect để kiểm tra state auth trước khi build route. Ví dụ: nếu user chưa đăng nhập và cố truy cập route được bảo vệ, redirect về /login. Kết hợp với Riverpod để redirect tự động phản ứng khi auth state thay đổi.…
Navigator 1.0 yêu cầu quản lý navigation state và back stack thủ công. Named route hoạt động nhưng xử lý deep linking kém — phải thêm nhiều boilerplate. Navigation phức tạp (nested stack, conditional routing) trở nên rối. Toàn bộ navigation tree là global, khó…
Clean architecture tách code thành các layer: Presentation (UI, widget), Domain (logic nghiệp vụ, entity, use case), Data (repository, data source). Mỗi layer độc lập và có thể test. Sự tách biệt này cho phép thay đổi UI framework, thay data source, và test logic…
Repository là lớp abstraction giữa domain logic và data source. Thay vì UI gọi trực tiếp API, bạn gọi Repository.getUser(id) — method này ẩn đi việc data đến từ network, cache hay database. Interface repository được định nghĩa trong domain layer; implementation trong data layer.…
Dependency injection là truyền dependency (service, repository) vào class thay vì tạo chúng bên trong. Thay vì class UserBloc { final repo = UserRepository(); }, hãy inject: class UserBloc { UserBloc(this.repo); final UserRepository repo; }. Nhờ vậy dễ test (mock repository) và dễ thay (đổi…
Singleton tạo một instance cho toàn bộ app: getIt.registerSingleton<Repository(Repository()). Dùng cho tài nguyên chia sẻ (database, API client, repository). Factory tạo instance mới mỗi lần: getIt.registerFactory<UserBloc(() = UserBloc(repo)). Dùng cho BLoC (mỗi màn hình cần state độc lập). Dùng sai gây state leak (chia sẻ…
MVC (Model-View-Controller): Controller xử lý input và cập nhật Model, View hiển thị Model. MVVM (Model-View-ViewModel): ViewModel expose data và logic UI cần. UI bind vào ViewModel. ViewModel không biết về UI. MVVM testable hơn MVC vì ViewModel không có dependency vào UI framework. Flutter…
Use case đóng gói logic nghiệp vụ cho một tính năng: GetUserUseCase, LoginUseCase. Mỗi use case là một class với method call() nhận tham số và trả về kết quả. Use case nằm ở domain layer, không phụ thuộc framework, và rất dễ test. Use…
Cấu trúc phổ biến: lib/presentation/ (UI, widget, BLoC/ViewModel), lib/domain/ (entity, repository interface, use case), lib/data/ (API client, local DB, repository implementation), lib/config/ (cấu hình app, constant). Mỗi feature có thể có domain/data/presentation riêng. Điều này giúp code ngăn nắp, dễ tìm và dễ test.
Entity đại diện cho khái niệm domain (đối tượng nghiệp vụ cốt lõi): User(id, name, email). Entity là class Dart thuần, không phụ thuộc framework và dễ test, nằm ở domain layer. Model là biểu diễn API/database với serialization: UserModel.toJson(). Model nằm ở data layer.…
Records là kiểu dữ liệu nhẹ, bất biến, cho phép nhóm nhiều giá trị lại mà không cần tạo class. Bạn có thể dùng dạng positional (theo vị trí) (String, int) hoặc tên trường ({String name, int age}). Để trả về từ hàm: (String, int)…
Pattern matching cho phép khớp giá trị và destructure cùng lúc. So với chuỗi if/else thì cách viết này declarative hơn hẳn.
Switch expression trả về giá trị trực tiếp và bắt buộc phải exhaustive (phủ hết mọi trường hợp). Cú pháp dùng =: String grade = score switch { 90 = 'A', 80 = 'B', = 'F' };. Switch statement thì không trả về giá trị,…
Impeller là rendering engine mới của Flutter, thay thế Skia. Mặc định trên iOS từ Flutter 3.10, và trên Android (API 29+) từ Flutter 3.27. Điểm khác biệt chính: Impeller pre-compile toàn bộ shader lúc build, trong khi Skia compile shader lần đầu tiên khi…
Mặc định Flutter Web compile Dart sang JavaScript, phải qua parser và interpreter của trình duyệt. Với WASM, Dart compile ra native bytecode chạy trực tiếp trên VM của browser, bỏ qua parsing overhead. Build bằng flutter build web --wasm. Kết quả: tác vụ CPU-intensive…
Tree-shaking là quá trình tự động loại bỏ code, class, method không được dùng trong quá trình build. Flutter chỉ giữ lại code reachable từ main(). Ví dụ: nếu bạn import một package lớn nhưng chỉ dùng một hàm, chỉ hàm đó được đưa vào…
Signals là hệ thống reactive nguyên bản lấy cảm hứng từ SolidJS, cho phép cập nhật UI theo kiểu fine-grained—chỉ widget nào phụ thuộc vào signal đó mới rebuild, không phải cả cây widget. Khai báo: final count = signal(0);, dùng trong widget: Watch((context) =…
Flutter Web compile Dart ra JS (hoặc WASM) và render vào Canvas/HTML, không phải DOM thực. Lợi thế: tái sử dụng code với mobile, không cần học JS ecosystem. Nhược điểm: bundle size ~3MB+ (nặng hơn SPA thông thường), SEO khó vì nội dung trong…
Desktop yêu cầu xử lý keyboard, mouse, window management—không có trên mobile. Responsive layout quan trọng hơn vì màn hình lớn và DPI khác nhau. Truy cập native API phải qua platform channel (file system, menu bar, notifications). Packaging khác nhau theo platform: MSIX (Windows),…
Package http là HTTP client đơn giản, đủ dùng cho request cơ bản. Dio là HTTP client mạnh hơn với: interceptor (middleware để log, thêm token tự động, retry), request/response transformer, quản lý timeout chi tiết, upload file multipart, download file với progress tracking, và…
Dùng package firebaseauth: await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password). Lắng nghe trạng thái auth bằng authStateChanges() stream—đây là cách đúng để biết user đã đăng nhập chưa. Lưu ý phổ biến: (1) không lắng nghe authStateChanges() mà kiểm tra currentUser ngay lập tức—có thể null khi…
InheritedWidget là widget đặc biệt cho phép các widget con đọc dữ liệu từ tổ tiên mà không phải truyền tay qua từng cấp constructor. Provider, Theme, MediaQuery đều được dựng trên cơ chế này. Hình dung nó như một "trạm phát" đặt cao trên…
Cả hai là widget "tự rebuild theo dữ liệu bất đồng bộ", khác nhau ở nguồn dữ liệu phát ra bao nhiêu giá trị: - FutureBuilder lắng nghe một Future — nguồn phát đúng một kết quả rồi kết thúc. Hợp với thao tác một-lần:…
Hero tạo hiệu ứng một phần tử chung "bay" liền mạch giữa hai màn hình khi điều hướng — ví dụ ảnh thumbnail nhỏ ở danh sách phóng to thành ảnh lớn ở trang chi tiết. Người dùng cảm nhận đó là cùng một đối…
MediaQuery cung cấp thông tin về môi trường hiển thị hiện tại: kích thước màn hình (size), hướng xoay (orientation), tỉ lệ pixel (devicePixelRatio), padding an toàn (notch, thanh trạng thái qua padding/viewPadding), độ phóng chữ (textScaler). Đây là nguồn dữ liệu để layout tự…