Khi nào phải dùng StatefulWidget thay vì StatelessWidget?
A.Khi widget có bất kỳ tham số nào truyền từ widget cha
B.Khi widget nằm sâu hơn ba cấp trong cây widget
C.Khi widget cần đọc dữ liệu từ API qua mạng
D.Khi widget cần giữ dữ liệu thay đổi giữa các lần build
Đáp án: D
Dùng StatefulWidget khi widget cần giữ dữ liệu thay đổi theo thời gian và tự gọi rebuild, ví dụ ô nhập liệu, bộ đếm, trạng thái đang tải. Nếu mọi thứ hiển thị đều đến từ tham số truyền vào thì StatelessWidget là đủ và rẻ hơn.
B.Đối tượng cấu hình toàn ứng dụng do MaterialApp tạo ra
C.Vị trí của widget trong cây element
D.Trạng thái hiện tại của widget và các widget con
Đáp án: C
BuildContext là tay cầm trỏ tới vị trí của widget trong cây element. Từ vị trí đó Flutter tra ngược lên để tìm widget tổ tiên gần nhất, nên Theme.of(context) hay Navigator.of(context) mới hoạt động. Sai context là sai điểm bắt đầu tra cứu.
Gán giá trị mới cho biến state nhưng không gọi setState thì điều gì xảy ra?
A.Giá trị bị hoàn về mặc định ở lần build kế tiếp
B.Flutter báo lỗi ngay tại dòng gán giá trị
C.Biến đổi giá trị nhưng giao diện không vẽ lại
D.Giao diện vẫn cập nhật nhưng chậm hơn một khung hình
Đáp án: C
Giá trị đổi thật nhưng màn hình không vẽ lại. setState không gán dữ liệu, nó chỉ đánh dấu element là bẩn để Flutter xếp lịch rebuild ở khung hình sau. Đó cũng là lý do màn hình vẫn đúng khi có widget khác vô tình kích hoạt rebuild.
Sửa giá trị khởi tạo của biến trong State nhưng hot reload không thấy đổi. Vì sao?
A.Hot reload giữ nguyên state cũ, chỉ nạp lại code
B.Hot reload bỏ qua mọi thay đổi bên trong lớp State
C.Cần lưu file lần nữa để trình biên dịch nhận thay đổi
D.Biến khởi tạo phải khai báo late thì mới nạp lại được
Đáp án: A
Hot reload nạp code mới nhưng giữ nguyên state đang chạy, nên initState và giá trị khởi tạo không chạy lại. Muốn thấy thay đổi loại này thì dùng hot restart: nó dựng lại toàn bộ ứng dụng từ đầu, mất state nhưng vẫn nhanh hơn build lại.
Đặt const trước hàm dựng widget mang lại lợi ích gì?
A.Widget bị khoá không cho widget cha truyền tham số mới
B.Widget được nén lại nên giảm dung lượng gói cài đặt
C.Widget được tái dùng nên nhánh đó bỏ qua rebuild
D.Widget được vẽ trên thread riêng nên mượt hơn
Đáp án: C
const tạo một đối tượng duy nhất dùng lại nhiều lần. Khi rebuild, Flutter thấy widget mới trùng hệt widget cũ nên bỏ qua cả nhánh con đó. Đây là tối ưu rẻ nhất trong Flutter và cũng là thứ hay bị bỏ sót nhất.
Column chứa nội dung cao hơn màn hình và báo lỗi tràn. Cách xử lý đúng là gì?
A.Đặt mainAxisSize thành MainAxisSize.min
B.Bọc Column trong Expanded để nó tự co lại
C.Giảm kích thước font cho tới khi vừa màn hình
D.Bọc trong SingleChildScrollView để cuộn được
Đáp án: D
Bọc bằng SingleChildScrollView để nội dung dài trở thành vùng cuộn. Với danh sách nhiều phần tử thì dùng ListView để chỉ dựng phần đang nhìn thấy. Giảm cỡ chữ hay bỏ bớt nội dung chỉ né được trên đúng thiết bị đang thử.
Navigator.push và Navigator.pushReplacement khác nhau ở đâu?
A.pushReplacement bỏ màn hình hiện tại khỏi ngăn xếp
B.pushReplacement xoá sạch mọi màn hình trong ngăn xếp
C.push mở màn hình mới còn pushReplacement mở hộp thoại
D.pushReplacement không chạy hiệu ứng chuyển màn hình
Đáp án: A
push chồng màn hình mới lên trên, còn pushReplacementthay thế màn hình hiện tại nên nó biến mất khỏi ngăn xếp. Dùng cho màn hình chờ hoặc màn hình đăng nhập, nơi người dùng không nên quay lại được bằng nút back.
Hàm async luôn trả về Future. Dart bọc giá trị trả về vào Future kể cả khi trong thân hàm không có await. Quên await ở nơi gọi là nguyên nhân số một khiến biến in ra dòng Instance of Future.
Toán tử ! là lời khẳng định giá trị khác null. Trình biên dịch tin và cho qua, nhưng lúc chạy nếu giá trị là null thì ném lỗi ngay. Dùng nhiều ! là dấu hiệu mô hình dữ liệu chưa chặt, không phải cách xử lý null.
Khác nhau giữa dependencies và dev_dependencies trong pubspec.yaml là gì?
A.dev_dependencies chỉ chạy được trên máy ảo chứ không phải máy thật
B.dependencies bị khoá phiên bản còn dev thì không
C.dev_dependencies không đi vào gói cài đặt phát hành
D.dev_dependencies chỉ nhận gói do đội nội bộ tự viết
Đáp án: C
dev_dependencies chỉ dùng trong lúc phát triển như flutter_test, build_runner, mocktail nên không được đóng gói vào bản phát hành. dependencies là thứ ứng dụng cần lúc chạy nên ảnh hưởng trực tiếp tới dung lượng cài đặt.
Khi chỉ cần tạo khoảng trống cố định, nên dùng SizedBox hay Container?
A.Container, vì SizedBox bắt buộc phải có widget con
B.Cả hai đều sai, phải dùng Padding cho khoảng trống
C.Container, vì nó xử lý khoảng trống chính xác hơn
D.SizedBox, vì nó nhẹ hơn và dựng được với const
Đáp án: D
SizedBox cho khoảng trống thuần vì nó chỉ áp một ràng buộc kích thước và dựng được với const. Container là widget tiện dụng gộp sẵn padding, màu, viền và biến đổi, nên chỉ đáng dùng khi thật sự cần từ hai thứ đó trở lên.
Thêm ảnh vào thư mục assets nhưng ứng dụng báo không tìm thấy. Thiếu bước nào?
A.Khai báo đường dẫn trong mục assets của pubspec.yaml
B.Chuyển ảnh sang định dạng webp cho Flutter đọc được
C.Đặt lại tên tệp không dấu và không khoảng trắng
D.Chạy flutter clean rồi build lại từ đầu
Đáp án: A
Phải khai báo trong pubspec.yaml dưới mục assets. Khai báo cả thư mục bằng đường dẫn kết thúc bằng dấu gạch chéo cũng được, nhưng chỉ áp cho tệp ngay trong thư mục đó chứ không đệ quy xuống thư mục con.
Scaffold cung cấp những gì cho một màn hình Material?
A.Khung bố cục sẵn cho app bar, body, drawer, snack bar
B.Bộ định tuyến quản lý ngăn xếp màn hình của ứng dụng
C.Bộ chủ đề màu và kiểu chữ cho toàn bộ ứng dụng
D.Vùng an toàn tự động tránh tai thỏ trên mọi thiết bị
Đáp án: A
Scaffold là khung của một màn hình Material: appBar, body, drawer, bottomNavigationBar, floatingActionButton, và chỗ hiển thị snack bar. Nó lo việc xếp chồng và chừa chỗ giữa các vùng đó để không phải tự tính toạ độ.
A.Lưu giá trị ô nhập vào bộ nhớ thiết bị giữa các phiên
B.Đọc và đặt nội dung của ô nhập từ bên ngoài
C.Đóng bàn phím khi người dùng nhấn nút xác nhận
D.Kiểm tra tính hợp lệ của giá trị người dùng nhập
Đáp án: B
TextEditingController giữ nội dung và vị trí con trỏ của một TextField, cho phép đọc, đặt lại giá trị và lắng nghe thay đổi từ bên ngoài widget. Nó phải được dispose trong State, nếu không sẽ rò bộ nhớ.
Image.network tải lại ảnh mỗi lần mở màn hình. Cách xử lý phù hợp?
A.Tải ảnh bằng http rồi tự vẽ bằng Image.memory
B.Bọc widget ảnh bằng const để giữ lại đối tượng cũ
C.Tăng kích thước cache ảnh của Flutter lên tối đa
D.Dùng gói lưu ảnh xuống đĩa như cached_network_image
Đáp án: D
Cache ảnh mặc định của Flutter chỉ nằm trong RAM và biến mất khi ứng dụng đóng. Muốn giữ qua các phiên thì cần cache trên đĩa, phổ biến nhất là cached_network_image, vốn cũng có sẵn ảnh chờ và ảnh lỗi.
B.GestureDetector chỉ hoạt động trên thiết bị cảm ứng
C.InkWell không cần widget con còn GestureDetector thì cần
D.InkWell vẽ hiệu ứng gợn sóng theo chuẩn Material
Đáp án: D
InkWell bắt cử chỉ chạm kèm hiệu ứng gợn sóng Material, còn GestureDetector chỉ nhận cử chỉ mà không vẽ gì. Đổi lại, GestureDetector phủ nhiều loại cử chỉ hơn như kéo, vuốt, phóng to và chạm hai lần.
Trong Flutter, tạo khoảng cách bên ngoài viền của một widget bằng cách nào?
A.Đặt thuộc tính margin trên chính widget đó
B.Bọc widget đó trong Padding ở tầng ngoài
C.Đặt thuộc tính offset trong phần trang trí của widget
D.Dùng Transform.translate để dịch widget ra xa
Đáp án: B
Flutter không tách margin và padding như CSS. Khoảng cách bên ngoài chính là padding của widget bao ngoài, thường viết bằng Padding hoặc bằng SizedBox giữa các phần tử. Container có margin chỉ vì nó tự bọc thêm một Padding bên ngoài.
Vì sao phải gọi WidgetsFlutterBinding.ensureInitialized trước runApp trong một số trường hợp?
A.Vì runApp chỉ chạy được sau khi giao diện đã dựng xong
B.Vì Dart cần biết trước số lượng widget của ứng dụng
C.Vì cần khoá hướng màn hình trước khi vẽ khung hình đầu tiên
D.Vì plugin cần kênh nền tảng đã sẵn sàng trước khi gọi
Đáp án: D
Việc gọi bất kỳ plugin nào trước runApp cần kênh nền tảng đã sẵn sàng, mà kênh đó chỉ tồn tại sau khi binding được khởi tạo. Đây là lý do khởi tạo Firebase, đọc shared_preferences hay khoá hướng màn hình trong main đều yêu cầu dòng lệnh này.
B.const cần giá trị biết được lúc biên dịch, final thì không
C.final cấm gán lại còn const thì vẫn cho gán lại
D.final chỉ dùng được bên trong hàm, không dùng cho trường lớp
Đáp án: B
Cả hai đều cấm gán lại. Khác biệt nằm ở thời điểm: const cần giá trị xác định được lúc biên dịch nên đối tượng được chia sẻ và đóng băng sâu, còn final cho phép giá trị tính lúc chạy, ví dụ final now = DateTime.now().
Khai báo biến bằng dynamic thay vì var gây rủi ro gì?
A.Biến chiếm nhiều bộ nhớ hơn hẳn so với var
B.Biến không thể gán lại sau lần khởi tạo đầu tiên
C.Mất kiểm tra kiểu nên lỗi chỉ lộ ra lúc chạy
D.Trình biên dịch từ chối build ở chế độ phát hành
Đáp án: C
var là suy luận kiểu, biến vẫn có kiểu tĩnh cố định sau khi gán lần đầu. dynamictắt kiểm tra kiểu, mọi lời gọi phương thức được hoãn tới lúc chạy, nên gõ sai tên hàm chỉ nổ khi người dùng chạm vào đúng nhánh đó.
Flutter duy trì ba cây song song. Vai trò của cây element là gì?
A.Lưu ảnh đã vẽ để khung hình sau dùng lại
B.Chứa mô tả bất biến về giao diện mong muốn
C.Giữ trạng thái và nối widget với render object
D.Tính toán kích thước và vị trí khi bố cục
Đáp án: C
Cây element là tầng sống lâu ở giữa: widget là bản mô tả bất biến bị vứt đi liên tục, render object lo đo và vẽ, còn element giữ vị trí, giữ State và quyết định lần rebuild này nên dùng lại hay tạo mới render object.
Danh sách các StatefulWidget bị xoá một phần tử, state của phần tử khác nhảy sai chỗ. Nguyên nhân?
A.Vì ListView tái dùng widget như RecyclerView của Android
B.Vì các phần tử chưa được bọc trong RepaintBoundary
C.Vì setState được gọi trước khi danh sách kịp cập nhật
D.Thiếu key nên Flutter ghép widget theo vị trí
Đáp án: D
Thiếu key. Khi ghép widget mới với element cũ, Flutter so theo kiểu và vị trí, nên xoá phần tử giữa danh sách khiến các phần tử sau dồn lên và nhận nhầm State cũ. Thêm ValueKey theo id của dữ liệu là hết.
Quy tắc bố cục cốt lõi của Flutter được phát biểu thế nào?
A.Ràng buộc đi xuống, kích thước đi lên, cha đặt vị trí
B.Mọi widget đều nhận kích thước từ MediaQuery của màn hình
C.Con tự chọn kích thước rồi cha phải nới ra cho vừa
D.Kích thước được tính hai lượt như trong CSS flexbox
Đáp án: A
Ràng buộc đi xuống, kích thước đi lên, cha đặt vị trí. Cha đưa xuống khoảng rộng và cao cho phép, con tự chọn kích thước trong khoảng đó rồi báo ngược lên, cuối cùng cha quyết định đặt con ở đâu. Hầu hết lỗi bố cục là hiểu sai một trong ba bước này.
Danh sách 5000 dòng dựng bằng ListView với children giật rất nặng. Vì sao?
A.Mỗi dòng tạo một RepaintBoundary riêng gây tốn bộ nhớ
B.Toàn bộ 5000 widget được dựng ngay lập tức
C.ListView vẽ lại toàn bộ danh sách sau mỗi khung hình cuộn
D.Do thiếu key nên Flutter phải dựng lại cây con mỗi lần
Đáp án: B
Dạng ListView(children: [...])dựng sẵn toàn bộ phần tử trước khi hiển thị. Với 5000 dòng là 5000 widget cùng lúc. ListView.builder chỉ dựng phần đang nhìn thấy cộng một vùng đệm, nên chi phí không còn phụ thuộc độ dài danh sách.
A.Phản chiếu kiểu để tìm widget cha có kiểu tương ứng
B.InheritedWidget cho phép tra ngược lên và đăng ký phụ thuộc
C.Một EventBus phát sự kiện xuống toàn bộ widget con
D.Một singleton toàn cục do MaterialApp khởi tạo lúc chạy
Đáp án: B
Theme.of(context) là một InheritedWidget. Lời gọi vừa tìm widget tổ tiên gần nhất theo kiểu vừa đăng ký phụ thuộc, nên khi theme đổi thì đúng những widget đã đọc nó được rebuild, không phải cả cây.
Màn hình dùng AnimationController và ScrollController bị rò bộ nhớ. Thiếu bước nào?
A.Gọi setState một lần cuối trước khi rời màn hình
B.Gọi dispose cho các controller trong State
C.Bọc màn hình trong AutomaticKeepAliveClientMixin
D.Khai báo controller là late final thay vì final
Đáp án: B
Thiếu dispose(). Controller, StreamSubscription, FocusNode, TextEditingController đều giữ tham chiếu và listener; không huỷ thì chúng vẫn sống sau khi màn hình đóng, giữ luôn cả cây widget cũ trong bộ nhớ.
Hai widget cùng lắng nghe một Stream từ http và widget thứ hai báo lỗi. Nguyên nhân?
A.Vì StreamBuilder tự động huỷ đăng ký của widget trước
B.Vì mỗi widget cần một BuildContext riêng để lắng nghe
C.Vì stream đã phát hết dữ liệu trước khi widget thứ hai gắn vào
D.Stream single-subscription chỉ cho một người nghe
Đáp án: D
Stream mặc định là single-subscription, chỉ chấp nhận một người nghe trong suốt vòng đời. Nghe lần thứ hai ném lỗi ngay. Dùng asBroadcastStream(), một StreamController.broadcast, hoặc tốt hơn là để một lớp repository giữ dữ liệu và phát lại cho các widget.
FutureBuilder gọi lại API mỗi lần widget cha rebuild. Cách sửa đúng?
A.Bọc FutureBuilder trong widget const để chặn rebuild
B.Tạo Future một lần trong initState rồi truyền vào
C.Kiểm tra snapshot.connectionState rồi trả về sớm
D.Đặt initialData để FutureBuilder bỏ qua lần gọi sau
Đáp án: B
Nguyên nhân là tạo Future ngay trong build: mỗi lần rebuild sinh một Future mới nên FutureBuilder chạy lại từ đầu. Tạo một lần trong initState và lưu vào trường của State, hoặc để lớp quản lý state bên ngoài giữ kết quả.
Điểm khác biệt cốt lõi giữa Provider và Riverpod là gì?
A.Riverpod chỉ hỗ trợ state bất biến còn Provider thì không
B.Riverpod không phụ thuộc BuildContext để tra cứu
C.Riverpod thay thế hoàn toàn cơ chế InheritedWidget của Flutter
D.Provider chạy đồng bộ còn Riverpod chạy bất đồng bộ
Đáp án: B
Riverpod không tra cứu qua BuildContext. Provider tìm dữ liệu bằng cách đi ngược lên cây widget nên đặt sai chỗ sẽ ném lỗi lúc chạy, còn provider của Riverpod là biến toàn cục có kiểu rõ ràng nên sai sót lộ ra sớm hơn.
Giải mã JSON 5 MB ngay trên UI thread làm ứng dụng khựng. Cách xử lý đúng?
A.Đánh dấu hàm giải mã là async rồi await kết quả
B.Chuyển việc giải mã sang isolate bằng compute
C.Bọc phần hiển thị trong RepaintBoundary để giảm vẽ lại
D.Chia dữ liệu thành nhiều lần setState nhỏ hơn
Đáp án: B
Đưa phép tính nặng sang isolate, đơn giản nhất là compute(jsonDecode, raw). Isolate có bộ nhớ và event loop riêng nên UI thread rảnh để giữ 60 khung hình. async không giúp gì vì nó không tạo thread mới.
Khi nào nên dùng AnimationController thay cho AnimatedContainer?
A.Khi widget cần hoạt ảnh trên nhiều thuộc tính cùng lúc
B.Khi ứng dụng cần chạy mượt trên máy cấu hình thấp
C.Khi cần điều khiển tiến độ, lặp hoặc đảo chiều
D.Khi hoạt ảnh cần chạy lâu hơn 300 mili giây
Đáp án: C
Dùng AnimationController khi cần điều khiển: tạm dừng, đảo chiều, lặp, phối nhiều hoạt ảnh theo mốc thời gian, hoặc gắn tiến độ vào cử chỉ kéo. Hoạt ảnh ngầm định như AnimatedContainer chỉ hợp cho chuyển trạng thái một chiều.
B.Expanded tính theo tỉ lệ phần trăm còn Flexible theo pixel
C.Expanded buộc con lấp đầy phần dư, Flexible thì không bắt buộc
D.Expanded chỉ dùng trong Row, Flexible chỉ dùng trong Column
Đáp án: C
Expanded là Flexible với fit: FlexFit.tight nên con bắt buộc lấp đầy phần được chia. Flexible mặc định là loose, con được phép nhỏ hơn. Cả hai đều chia phần dư theo hệ số flex.
Nên dùng LayoutBuilder thay vì MediaQuery trong trường hợp nào?
A.Khi cần lấy chiều cao của thanh trạng thái và vùng an toàn
B.Khi cần biết thiết bị đang xoay ngang hay dọc
C.Khi cần đọc cỡ chữ hệ thống người dùng đang đặt
D.Khi cần kích thước của chính ô chứa widget đó
Đáp án: D
Dùng LayoutBuilder khi quyết định phụ thuộc kích thước ô chứa widget, ví dụ thẻ trong lưới hay ngăn của màn hình chia đôi. MediaQuery mô tả cả màn hình nên vẫn đúng cho vùng an toàn, hướng xoay và cỡ chữ hệ thống.
Bản phát hành Flutter biên dịch trước sang mã máy và không có phản chiếu lúc chạy, vì phản chiếu chặn việc loại bỏ mã thừa. Do đó phải viết fromJson bằng tay hoặc sinh mã bằng json_serializable và freezed qua build_runner.
Cách nào đúng để đổi endpoint API giữa môi trường dev và production?
A.Đặt cờ toàn cục rồi sửa tay trước mỗi lần phát hành
B.Đọc biến môi trường của hệ điều hành lúc ứng dụng khởi động
C.Dùng kDebugMode để chọn endpoint tương ứng
D.Truyền qua dart-define kết hợp flavor lúc build
Đáp án: D
Dùng --dart-define hoặc --dart-define-from-file để chốt cấu hình lúc build, kết hợp flavor khi cần khác nhau cả tên gói, biểu tượng và dịch vụ. Nhờ vậy có thể cài song song bản dev và bản production trên cùng một máy.
Cách đúng để một màn hình dùng bảng màu khác phần còn lại của ứng dụng là gì?
A.Đặt biến toàn cục màu rồi đọc trong build của widget
B.Truyền màu qua tham số xuống từng widget con
C.Đổi ThemeData của MaterialApp khi mở màn hình đó
D.Bọc màn hình đó bằng Theme với dữ liệu chủ đề riêng
Đáp án: D
Bọc nhánh đó bằng Theme và truyền ThemeData riêng, thường tạo từ chủ đề hiện tại bằng copyWith. Vì Theme là InheritedWidget, mọi widget bên dưới tự đọc giá trị gần nhất mà phần còn lại của ứng dụng không đổi.
Khi nào dùng mixin thay vì kế thừa lớp trừu tượng trong Dart?
A.Khi cần chia sẻ hành vi cho nhiều lớp không cùng cha
B.Khi cần lớp con bắt buộc cài đặt lại mọi phương thức
C.Khi cần khai báo trường có thể thay đổi giá trị
D.Khi cần tạo thể hiện trực tiếp mà không qua lớp con
Đáp án: A
Dùng mixin khi cần chia sẻ hành vi theo chiều ngang cho các lớp không cùng cây kế thừa. Dart chỉ cho kế thừa một lớp nhưng trộn được nhiều mixin, nên mixin hợp cho những mảnh như ghi log, xác thực dữ liệu hay quản lý một tài nguyên chung.
Lợi ích chính của sealed class trong Dart 3 khi mô hình hoá trạng thái là gì?
A.Cho phép so sánh bằng giá trị mà không cần viết thêm
B.Các lớp con tự động trở thành bất biến
C.Trình biên dịch bắt lỗi khi switch bỏ sót một nhánh
D.Giảm dung lượng gói cài đặt nhờ loại bỏ mã thừa
Đáp án: C
sealed khoá tập hợp lớp con trong cùng tệp, nhờ đó switch trở thành kiểm tra đủ nhánh lúc biên dịch. Thêm một trạng thái mới mà quên xử lý ở đâu đó thì build hỏng ngay, thay vì lộ ra khi người dùng gặp màn hình trắng.
MethodChannel truyền dữ liệu giữa Dart và native theo cách nào?
A.Mã hoá thông điệp bất đồng bộ qua một tên kênh chung
B.Sinh sẵn mã cầu nối lúc biên dịch từ khai báo giao diện
C.Gọi trực tiếp hàm native trong cùng tiến trình một cách đồng bộ
D.Chạy mã native trong một isolate riêng của Dart
Đáp án: A
MethodChannel là thông điệp bất đồng bộ theo tên kênh. Dart gửi tên phương thức kèm tham số, dữ liệu được mã hoá nhị phân, phía native xử lý rồi trả kết quả về. Vì khớp theo chuỗi nên gõ sai tên chỉ lộ ra lúc chạy.
Lưu token đăng nhập bằng shared_preferences có vấn đề gì?
A.Dữ liệu bị xoá mỗi khi ứng dụng được cập nhật
B.Dữ liệu không được mã hoá, máy đã root đọc được
C.Việc đọc ghi chạy đồng bộ nên làm treo giao diện
D.Dung lượng lưu tối đa quá nhỏ cho một token
Đáp án: B
shared_preferences ghi ra tệp cấu hình thường của nền tảng và không mã hoá gì cả. Trên máy đã root hoặc jailbreak, nội dung đọc được dễ dàng. Token, khoá và thông tin cá nhân nên nằm trong flutter_secure_storage để dùng Keystore và Keychain.
Ứng dụng cần đọc dữ liệu ngoại tuyến với bộ lọc và sắp xếp phức tạp. Chọn giải pháp nào?
A.Giữ trong cache HTTP rồi đọc lại khi mất mạng
B.Ghi toàn bộ dữ liệu ra tệp JSON rồi lọc trong bộ nhớ
C.Cơ sở dữ liệu SQLite qua sqflite hoặc drift
D.Lưu vào shared_preferences dưới dạng chuỗi mã hoá JSON
Đáp án: C
Dữ liệu có quan hệ, cần lọc và sắp xếp thì dùng SQLite qua sqflite, hoặc drift khi muốn truy vấn có kiểu và migration rõ ràng. Kho khoá giá trị chỉ hợp khi luôn đọc theo khoá và lượng dữ liệu nhỏ.
Cách gọn nhất để tự động làm mới access token khi API trả 401 là gì?
A.Đặt hẹn giờ làm mới token định kỳ theo thời hạn
B.Bắt ngoại lệ ở tầng giao diện rồi yêu cầu đăng nhập lại
C.Đặt interceptor ở tầng client HTTP để thử lại một lần
D.Kiểm tra mã trạng thái ở mỗi hàm gọi API rồi tự xử lý
Đáp án: C
Dùng interceptor ở tầng client, ví dụ Dio: bắt phản hồi 401, gọi làm mới token, rồi phát lại đúng yêu cầu đó một lần. Phải khoá để nhiều yêu cầu song song chỉ kích hoạt một lượt làm mới, nếu không sẽ có chuỗi làm mới chồng chéo.
A.Widget test chạy không cần thiết bị nên nhanh hơn nhiều
B.Widget test chỉ kiểm tra được widget không có state
C.Widget test bắt buộc phải có ảnh chuẩn để so sánh
D.Integration test không gọi được API thật
Đáp án: A
Widget test dựng cây widget trong môi trường kiểm thử trong bộ nhớ, không cần thiết bị, chạy trong vài mili giây. Integration test chạy cả ứng dụng trên thiết bị hoặc máy ảo, sát thực tế hơn nhưng chậm và dễ chập chờn hơn nhiều.
Đo hiệu năng ứng dụng Flutter nên chạy ở chế độ nào?
A.Profile, vì nó biên dịch như release nhưng còn công cụ đo
B.Release, vì đó chính là thứ người dùng chạy
C.Chế độ nào cũng được vì kết quả tương đương nhau
D.Debug, vì có đầy đủ thông tin gỡ lỗi nhất
Đáp án: A
Dùng profile. Chế độ debug chạy máy ảo Dart với biên dịch tức thời và nhiều kiểm tra bổ sung nên chậm hơn nhiều lần thực tế. Profile biên dịch trước như release nhưng vẫn giữ đủ công cụ để xem biểu đồ khung hình trong DevTools.
Cách hiệu quả nhất để giảm dung lượng bản Android phát hành là gì?
A.Phát hành app bundle để cửa hàng cấp bản theo thiết bị
B.Gỡ bỏ toàn bộ phông chữ tuỳ chỉnh khỏi ứng dụng
C.Bật nén tối đa cho toàn bộ tài nguyên trong dự án
D.Chuyển sang chế độ build release thay vì profile
Đáp án: A
Phát hành app bundle để Google Play sinh bản cài riêng theo kiến trúc và mật độ màn hình của từng máy. Ngoài ra Flutter tự cắt biểu tượng không dùng, còn phần ảnh và phông trong assets thì phải tự rà vì chúng luôn được đóng gói đủ.
Điều kiện để một liên kết web mở thẳng vào ứng dụng thay vì trình duyệt là gì?
A.Khai báo lược đồ riêng cho ứng dụng trong tệp cấu hình
B.Người dùng phải chọn ứng dụng làm trình xử lý mặc định
C.Có tệp xác minh quyền sở hữu tên miền trên server
D.Đăng ký tên miền đó với cửa hàng ứng dụng tương ứng
Đáp án: C
Cần xác minh quyền sở hữu tên miền: assetlinks.json cho App Links của Android và apple-app-site-association cho Universal Links của iOS, đặt đúng đường dẫn well-known trên server https. Thiếu bước này thì liên kết chỉ mở trình duyệt.
Cách chuẩn để hỗ trợ nhiều ngôn ngữ trong Flutter là gì?
A.Dùng tệp ARB sinh lớp chuỗi qua flutter_localizations
B.Đặt map chuỗi toàn cục rồi tra theo mã ngôn ngữ
C.Tải chuỗi từ server mỗi lần mở ứng dụng
D.Viết điều kiện chọn chuỗi ngay tại chỗ hiển thị
Đáp án: A
Dùng flutter_localizations với các tệp ARB cho từng ngôn ngữ, sinh ra lớp truy cập chuỗi có kiểu. Ngoài việc tra chuỗi, cách này xử lý luôn số nhiều, giống đực giống cái, và định dạng ngày giờ tiền tệ theo locale.
Cần tạm dừng phát video khi người dùng chuyển ứng dụng khác. Nên dùng gì?
A.Kiểm tra trong dispose của widget đang phát video
B.Đặt hẹn giờ kiểm tra định kỳ xem app còn hiển thị không
C.Lắng nghe sự kiện mất tiêu điểm của widget video
D.Theo dõi trạng thái vòng đời qua WidgetsBindingObserver
Đáp án: D
Trộn WidgetsBindingObserver vào State và xử lý didChangeAppLifecycleState. Các trạng thái gồm resumed, inactive, paused, detached và hidden; tạm dừng ở paused hoặc inactive tuỳ mức độ nhạy cảm của nội dung.
Cách đúng để mọi lỗi chưa bắt trong ứng dụng đều được gửi về dịch vụ giám sát là gì?
A.Bọc runApp trong khối try catch ở hàm main
B.Gắn cả FlutterError.onError và onError của PlatformDispatcher
C.Bắt ngoại lệ trong từng lời gọi API bằng try catch
D.Đặt ErrorWidget.builder để hiển thị màn hình lỗi thân thiện
Đáp án: B
Cần hai hook: FlutterError.onError cho lỗi phát sinh trong khung Flutter như lỗi lúc dựng widget, và PlatformDispatcher.instance.onError cho lỗi bất đồng bộ ngoài khung. Chỉ gắn một cái thì luôn có một nửa số lỗi biến mất.
Vì sao ứng dụng có deep link phức tạp thường chuyển sang go_router?
A.Vì đường dẫn được khai báo nên khôi phục được ngăn xếp
B.Vì nó xử lý hoạt ảnh chuyển màn hình mượt hơn Navigator
C.Vì nó thay thế hoàn toàn Navigator trong Flutter
D.Vì nó không cần BuildContext để điều hướng
Đáp án: A
Điều hướng khai báo gắn trạng thái ứng dụng với một đường dẫn, nên mở thẳng một màn hình sâu từ deep link hay từ URL web vẫn dựng lại được ngăn xếp cha. Với Navigator thuần, việc này phải tự viết và rất dễ sai.
B.Cho phép giao diện đọc state mà không cần context
C.Giúp state được lưu tự động xuống bộ nhớ thiết bị
D.Mọi thay đổi đi qua một đường, dễ lần vết
Đáp án: D
Tách event khỏi state tạo ra một luồng thay đổi duy nhất: giao diện gửi event, bloc xử lý và phát ra state mới. Nhờ đó ghi log được chuỗi event dẫn tới lỗi, kiểm thử chỉ cần bơm event và so state, và không có chỗ nào sửa state lén.
ValueListenableBuilder có lợi thế gì so với đặt setState ở gốc màn hình?
A.Cho phép cập nhật giao diện từ isolate khác
B.Giá trị được đồng bộ tự động với các màn hình khác
C.Không cần huỷ đăng ký khi widget bị gỡ khỏi cây
D.Chỉ phần trong builder được dựng lại khi đổi
Đáp án: D
ValueListenableBuilder thu hẹp phạm vi rebuild về đúng phần hiển thị giá trị đó. setState ở gốc màn hình dựng lại cả cây con, kể cả những phần không liên quan, và trên màn hình phức tạp thì đó là nguyên nhân rớt khung hình.
Khi nào cần CustomScrollView với sliver thay vì ListView thường?
A.Khi các vùng cuộn phải chung một trục
B.Khi cần kéo xuống để làm mới dữ liệu
C.Khi danh sách có hơn một nghìn phần tử
D.Khi các phần tử có chiều cao khác nhau
Đáp án: A
Dùng sliver khi nhiều vùng nội dung khác kiểu phải cuộn như một khối: app bar co giãn theo cuộn, sau đó là lưới, rồi tới danh sách. Ghép các widget cuộn lồng nhau sẽ tạo hai trục cuộn tranh nhau, còn sliver thì chỉ có một.
A.Cả hai màn hình phải dùng cùng một AnimationController
B.Ảnh ở hai màn hình phải có cùng tỉ lệ khung hình
C.Hai widget Hero ở hai route dùng chung một tag
D.Widget nguồn phải được bọc trong RepaintBoundary
Đáp án: C
Hai widget Hero ở hai route phải có cùng tag. Trong lúc chuyển route, Flutter nhấc widget lên một lớp phủ và nội suy vị trí, kích thước từ chỗ cũ tới chỗ mới. Trùng tag trong cùng một màn hình sẽ ném lỗi.
Cách phát hiện người dùng cuộn gần cuối danh sách để tải thêm là gì?
A.Gọi tải thêm trong itemBuilder của phần tử cuối cùng
B.Nghe ScrollController và so với maxScrollExtent
C.Đặt hẹn giờ kiểm tra vị trí cuộn định kỳ
D.Đếm số lần người dùng vuốt trên màn hình
Đáp án: B
Gắn ScrollController và so position.pixels với position.maxScrollExtent trừ đi một ngưỡng, ví dụ 200 pixel. Phải có cờ chặn để không gọi trùng khi sự kiện cuộn bắn liên tục, và phải xử lý trạng thái hết dữ liệu.
Màn hình danh sách nên xử lý những trạng thái nào để không có màn hình trắng?
A.Tải thất bại kèm nút thử lại
B.Đang tải lần đầu
C.Tải xong nhưng không có dữ liệu
D.Người dùng chưa cấp quyền thông báo
Đáp án: A, B, C
Ba trạng thái tối thiểu: đang tải, rỗng và lỗi có nút thử lại, bên cạnh trạng thái có dữ liệu. Bỏ trạng thái rỗng là nguyên nhân phổ biến nhất của màn hình trắng không giải thích được, và bỏ nhánh lỗi khiến người dùng không có lối thoát.
Vì sao không nên đặt autovalidateMode là always cho biểu mẫu đăng ký?
A.Chế độ này làm chậm việc gõ phím trên máy yếu
B.Người dùng thấy lỗi đỏ khi chưa nhập gì
C.Chế độ này bỏ qua validator viết tay
D.Chế độ này chỉ chạy được với TextFormField
Đáp án: B
always kiểm tra ngay từ khi biểu mẫu hiển thị, nên mọi ô trống đều đỏ trước khi người dùng gõ chữ nào. Lựa chọn hợp lý là onUserInteraction, hoặc mặc định là kiểm tra khi gửi rồi mới bật kiểm tra liên tục.
Cách đóng bàn phím khi người dùng chạm ra ngoài ô nhập là gì?
A.Gọi phương thức ẩn bàn phím của kênh nền tảng
B.Gọi setState để dựng lại màn hình không có ô nhập
C.Bỏ tiêu điểm hiện tại qua FocusScope của context
D.Đặt readOnly cho ô nhập khi chạm ra ngoài
Đáp án: C
Gọi FocusScope.of(context).unfocus(), thường đặt trong GestureDetector bọc cả màn hình. Bàn phím gắn với trường đang giữ tiêu điểm nên mất tiêu điểm là bàn phím đóng theo, và trạng thái ứng dụng vẫn nhất quán.
Cách nhận kết quả từ một hộp thoại xác nhận là gì?
A.Truyền callback vào hộp thoại rồi gọi khi bấm nút
B.Đặt biến toàn cục rồi đọc sau khi hộp thoại đóng
C.await showDialog rồi đọc giá trị đã pop
D.Lắng nghe sự kiện đóng route qua RouteObserver
Đáp án: C
showDialog trả về một Future hoàn tất khi hộp thoại đóng, giá trị chính là thứ truyền cho Navigator.pop. Nhớ rằng đóng bằng cách chạm ra ngoài trả về null, nên luôn xử lý nhánh đó thay vì giả định có kết quả.
Vì sao nên gọi snack bar qua ScaffoldMessenger thay vì Scaffold.of?
A.Cách này không cần BuildContext để gọi
B.Snack bar tồn tại được khi màn hình bị thay thế
C.Cách này cho phép hiển thị nhiều snack bar cùng lúc
D.Cách này tự động dịch nội dung theo ngôn ngữ hệ thống
Đáp án: B
ScaffoldMessenger nằm phía trên các Scaffold nên snack bar sống sót qua việc chuyển màn hình. Với Scaffold.of, thông báo gắn vào Scaffold hiện tại và biến mất ngay khi màn hình đó bị thay thế, đúng lúc người dùng cần đọc nó nhất.
Chuyển tab rồi quay lại làm mất vị trí cuộn và dữ liệu đã tải. Cách xử lý?
A.Đặt key cố định cho mỗi trang trong TabBarView
B.Trộn AutomaticKeepAliveClientMixin cho trang tab
C.Tăng số lượng trang được dựng trước trong TabBarView
D.Lưu vị trí cuộn vào shared_preferences mỗi lần cuộn
Đáp án: B
Trộn AutomaticKeepAliveClientMixin vào State của trang, đặt wantKeepAlive là true và gọi super.build(context). Khi đó TabBarView giữ trang trong bộ nhớ nên vị trí cuộn và dữ liệu đã tải còn nguyên.
Trên máy tính bảng, thay thanh điều hướng dưới bằng thành phần nào là chuẩn Material?
A.Giữ nguyên thanh dưới nhưng tăng chiều cao
B.TabBar đặt ngay dưới thanh tiêu đề
C.NavigationRail đặt dọc bên cạnh nội dung
D.Drawer luôn mở cố định ở bên trái màn hình
Đáp án: C
NavigationRail là thành phần dành cho khổ trung bình. Hướng dẫn Material 3 chia ba mức: dưới 600 dùng thanh dưới, từ 600 tới 840 dùng thanh dọc, từ 840 trở lên dùng ngăn điều hướng luôn mở cạnh nội dung.
Đặt cỡ chữ cố định bằng pixel trong toàn ứng dụng gây vấn đề gì?
A.Ứng dụng không chạy được trên máy tính bảng
B.Bỏ qua cỡ chữ hệ thống mà người dùng đã đặt
C.Cửa hàng ứng dụng từ chối phê duyệt bản phát hành
D.Chữ hiển thị mờ trên màn hình có mật độ cao
Đáp án: B
Người dùng chỉnh cỡ chữ hệ thống vì lý do thị lực, và ứng dụng cố định cỡ chữ sẽ phớt lờ thiết lập đó. Flutter áp textScaler cho Text, nên vấn đề thật nằm ở bố cục cố định chiều cao, khiến chữ lớn bị cắt hoặc tràn.
Thông báo đẩy hiển thị khi ứng dụng chạy nền nhưng không hiện khi đang mở. Vì sao?
A.Cần quyền riêng cho thông báo khi ứng dụng đang hiển thị
B.Token thiết bị chỉ hợp lệ khi ứng dụng chạy nền
C.Ứng dụng đang mở thì mã ứng dụng nhận và tự xử lý
D.Thông báo bị hệ điều hành chặn để không làm phiền
Đáp án: C
Khi ứng dụng đang hiển thị, hệ điều hành giao dữ liệu cho ứng dụng thay vì tự vẽ thông báo. Muốn thấy gì đó thì tự hiển thị bằng thông báo cục bộ hoặc một banner trong ứng dụng, dựa trên dữ liệu nhận được ở handler tương ứng.
Người dùng từ chối vĩnh viễn quyền máy ảnh. Ứng dụng nên làm gì?
A.Chuyển sang dùng thư viện máy ảnh không cần quyền
B.Gọi lại yêu cầu quyền cho tới khi người dùng đồng ý
C.Giải thích lý do và mở thẳng phần cài đặt ứng dụng
D.Chặn ứng dụng cho tới khi quyền được cấp
Đáp án: C
Khi bị từ chối vĩnh viễn, yêu cầu quyền trả về từ chối ngay mà không hiện hộp thoại. Cách đúng là giải thích ngắn gọn vì sao cần quyền rồi cung cấp nút mở cài đặt ứng dụng, đồng thời để phần còn lại của ứng dụng vẫn dùng được.
Nhúng trang thanh toán bằng WebView cần chú ý điều gì nhất về bảo mật?
A.Giới hạn tên miền được phép điều hướng
B.Chỉ mở WebView khi thiết bị đang dùng mạng di động
C.Xoá cache sau mỗi lần mở trang
D.Tắt JavaScript để tránh mã độc chạy trong trang
Đáp án: A
Phải kiểm soát điều hướng: chỉ cho phép các tên miền trong danh sách tin cậy, chặn phần còn lại hoặc mở ra trình duyệt hệ thống. Người dùng không thấy thanh địa chỉ trong WebView nên không tự phát hiện được khi trang đã chuyển sang nơi khác.
Công cụ nào cho biết widget nào đang dựng lại quá nhiều lần?
A.Báo cáo kích thước gói khi build release
B.Bộ đếm số lần dựng lại trong DevTools
C.Nhật ký console khi bật chế độ chi tiết
D.Biểu đồ bộ nhớ trong DevTools
Đáp án: B
DevTools có bộ đếm lần dựng lại, đánh dấu ngay trên cây widget những chỗ dựng nhiều lần trong một khoảng thời gian. Đây là cách nhanh nhất để tìm chỗ đặt setState quá cao hoặc chỗ thiếu const.
Cờ obfuscate khi build release có tác dụng gì và đánh đổi gì?
A.Loại bỏ mọi chuỗi ký tự khỏi tệp nhị phân
B.Đổi tên ký hiệu nên vết ngăn xếp khó đọc
C.Mã hoá toàn bộ mã Dart nên không thể dịch ngược
D.Giảm đáng kể thời gian khởi động ứng dụng
Đáp án: B
--obfuscate đổi tên lớp và hàm thành ký hiệu ngắn, làm việc dịch ngược khó hơn. Đổi lại, vết ngăn xếp không còn đọc được nếu thiếu tệp ánh xạ sinh ra bởi --split-debug-info, nên phải lưu tệp đó theo từng bản phát hành.
Trường version trong pubspec.yaml ánh xạ sang đâu khi build?
A.Trước dấu cộng là tên phiên bản, sau là số build
B.Toàn bộ chuỗi trở thành tên phiên bản hiển thị
C.Số build được sinh tự động theo thời điểm build
D.Chuỗi này chỉ dùng cho pub.dev, không ảnh hưởng bản build
Đáp án: A
version: 1.4.2+37 tách làm hai: 1.4.2 là tên phiên bản hiển thị, còn 37 là số build. Cửa hàng từ chối bản tải lên có số build trùng hoặc nhỏ hơn, nên đây là con số phải tăng ở mỗi lần phát hành.
Tệp analysis_options.yaml dùng để làm gì trong dự án Flutter?
A.Bật tắt các tính năng thử nghiệm của trình biên dịch
B.Cấu hình luật phân tích tĩnh và mức nghiêm ngặt
C.Cấu hình định dạng mã như độ dài dòng và dấu cách
D.Khai báo các gói phụ thuộc dùng cho phân tích mã
Đáp án: B
Đây là nơi cấu hình phân tích tĩnh: bật bộ luật như flutter_lints, nâng cảnh báo thành lỗi, và bật các chế độ kiểu nghiêm ngặt. Nó biến nhiều lỗi lúc chạy thành lỗi hiện ngay trong trình soạn thảo.
Khác biệt giữa package và plugin trong hệ sinh thái Flutter là gì?
A.Package không thể phụ thuộc vào package khác
B.Plugin phải được nhóm Flutter phê duyệt trước khi phát hành
C.Plugin có phần mã native cho từng nền tảng
D.Package chỉ chứa widget còn plugin chứa logic
Đáp án: C
Package là mã Dart thuần, chạy ở mọi nền tảng. Plugin kèm mã native cho Android, iOS, web hoặc máy tính, nên nó chỉ hoạt động trên những nền tảng đã được cài đặt và cần build lại khi thêm vào.
Ô tìm kiếm gọi API theo từng ký tự và kết quả cũ đè lên kết quả mới. Cách xử lý?
A.Chỉ gọi API khi người dùng bấm nút tìm kiếm
B.Tăng thời gian chờ của yêu cầu mạng lên cao hơn
C.Sắp xếp kết quả theo thời gian trước khi hiển thị
D.Chống dội đầu vào và huỷ yêu cầu cũ khi gõ tiếp
Đáp án: D
Cần hai việc: chống dội khoảng 300 mili giây để không gọi mỗi ký tự, và huỷ hoặc bỏ qua phản hồi cũ khi truy vấn mới bắt đầu. Không có phần thứ hai thì phản hồi chậm của truy vấn cũ vẫn về sau và đè lên kết quả đúng.
Vì sao lớp state cần cài đặt == và hashCode khi dùng với các thư viện quản lý state?
A.Vì Dart yêu cầu mọi lớp phải cài đặt hai phương thức này
B.Vì trình biên dịch cần chúng để tối ưu bộ nhớ
C.Vì thư viện so sánh state cũ và mới trước khi vẽ
D.Vì cần thiết cho việc chuyển đổi sang JSON
Đáp án: C
Mặc định Dart so sánh theo danh tính đối tượng, nên hai state có nội dung giống hệt vẫn khác nhau. Thư viện quản lý state dựa vào phép so sánh này để bỏ qua rebuild thừa, nên thiếu == và hashCode là rebuild liên tục.
Vì sao không nên tạo thẳng đối tượng ApiClient bên trong widget?
A.Flutter cấm tạo đối tượng trong hàm build
B.Không thay được bằng bản giả lập khi kiểm thử
C.Đối tượng đó không thể dùng trong hàm bất đồng bộ
D.Widget sẽ không rebuild khi dữ liệu thay đổi
Đáp án: B
Phụ thuộc bị chôn trong widget thì không thay thế được lúc kiểm thử, nên widget test buộc phải gọi mạng thật. Ngoài ra tạo trong build còn sinh đối tượng mới mỗi lần rebuild, kéo theo mất cache và mở kết nối thừa.
Lý do chính để tách tầng repository giữa giao diện và nguồn dữ liệu là gì?
A.Cho phép giao diện đọc dữ liệu một cách đồng bộ
B.Bắt buộc trong kiến trúc sạch nên phải có
C.Giảm số lượng lời gọi mạng mà không cần cache
D.Giao diện không phụ thuộc vào nguồn dữ liệu nào
Đáp án: D
Repository tách cái gì cần lấy khỏi lấy từ đâu. Giao diện chỉ gọi một phương thức và không biết dữ liệu đến từ mạng, cache hay cơ sở dữ liệu cục bộ, nên đổi nguồn hoặc thêm chế độ ngoại tuyến không lan ra tầng widget.