Việc làm

07/10 · Hà Nội · Tại văn phòng

Mobile Developer (Flutter, ReactJS, Angular, C#, Java)

BSC – Chứng khoán BIDV · Mobile Application Developer

Xem mô tả & ứng tuyển trên ITviecMô tả công việc, lương và ứng tuyển nằm ở ITviec.

10 câu hỏi có thể gặp khi phỏng vấn

Chọn theo kỹ năng và cấp của tin (Mobile (Android, iOS, Flutter), Java, JavaScript & TypeScript). Gợi ý trả lời do Drama Công Ty biên soạn để bạn ôn nhanh, không phải đề của công ty.

  1. 01Chọn native (Kotlin/Swift) hay cross-platform (Flutter, React Native) cho một dự án mới? Bạn cân nhắc gì?Mobile (Android, iOS, Flutter) · Senior

    Gợi ý trả lời

    Không có đáp án chung; quyết định dựa trên yêu cầu sản phẩm, đội ngũ và chi phí dài hạn.

    • Cross-platform (Flutter, React Native, hoặc Kotlin Multiplatform để chia sẻ logic): một codebase cho hai nền tảng, ra mắt nhanh, hợp với app nghiệp vụ, thương mại điện tử, nội bộ.
    • Native: cần tích hợp sâu với hệ điều hành (widget, camera hoặc Bluetooth phức tạp, xử lý nền), hiệu năng đồ hoạ cao, hoặc muốn dùng tính năng mới của OS ngay khi ra mắt.

    Các yếu tố cần cân nhắc:

    • Kỹ năng hiện có của team và khả năng tuyển người.
    • Mức độ tuỳ biến UI theo từng nền tảng.
    • Chi phí viết plugin native khi thư viện không có sẵn.
    • Kích thước app và thời gian khởi động.

    Người phỏng vấn muốn nghe: bạn đưa ra lập luận gắn với bối cảnh cụ thể và nhắc tới phương án lai, vd. thêm màn hình Flutter vào app native.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  2. 02Trình bày vòng đời (lifecycle) của Activity trên Android hoặc của app/view trên iOS.Mobile (Android, iOS, Flutter) · Junior

    Gợi ý trả lời

    Vòng đời cho biết khi nào hệ điều hành tạo, hiển thị, tạm dừng và huỷ màn hình, để bạn khởi tạo và giải phóng tài nguyên đúng lúc.

    • Android Activity: onCreate → onStart → onResume (đang tương tác) → onPause → onStop → onDestroy. Xoay màn hình mặc định sẽ huỷ và tạo lại Activity.
    • iOS UIKit: viewDidLoad (một lần) → viewWillAppear → viewDidAppear → viewWillDisappear → viewDidDisappear. Trạng thái app: active, inactive, background.
    • SwiftUI / Compose: nghĩ theo state và các hook như .task, onAppear, LaunchedEffect, DisposableEffect.

    Hay sai: đăng ký listener hoặc sensor trong onResume mà quên huỷ ở onPause, gây rò rỉ và hao pin. Trên Android nên thu thập Flow bằng repeatOnLifecycle.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  3. 03Làm sao giữ dữ liệu khi xoay màn hình hoặc khi app bị hệ điều hành kill ở background?Mobile (Android, iOS, Flutter) · Junior

    Gợi ý trả lời

    Có hai tình huống khác nhau: thay đổi cấu hình (xoay màn hình, đổi ngôn ngữ) và process death (hệ điều hành kill app để lấy RAM).

    Android:

    • ViewModel sống qua thay đổi cấu hình nhưng không sống qua process death.
    • SavedStateHandle (trong ViewModel) hoặc rememberSaveable (Compose) lưu trạng thái nhỏ như id, nội dung form, vị trí cuộn.
    • Dữ liệu lớn hoặc quan trọng lưu bền vững bằng Room hoặc DataStore.

    iOS: SwiftUI có @SceneStorage cho trạng thái UI nhỏ; UIKit có state restoration; dữ liệu thật lưu bằng SwiftData, Core Data hoặc file.

    Người phỏng vấn muốn nghe: bạn biết cách kiểm thử process death, vd. bật "Don't keep activities" hoặc dùng adb shell am kill khi app ở background.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  4. 04Phân biệt == và equals(). Vì sao override equals() thì phải override cả hashCode()?Java · Junior

    Gợi ý trả lời

    == so sánh tham chiếu (hai biến có trỏ cùng một object không), còn equals() so sánh nội dung theo logic bạn định nghĩa.

    • Hợp đồng: hai object equals nhau thì bắt buộc có cùng hashCode.
    • HashMap/HashSet dùng hashCode để chọn bucket trước, rồi mới dùng equals để so trong bucket. Nếu chỉ override equals, hai object "bằng nhau" có thể rơi vào hai bucket khác nhau, nên set.contains(x) trả về false dù đã add.
    • Với Java 16+, record tự sinh equals/hashCode/toString theo các field.

    Hay sai: so sánh String hoặc Integer bằng ==. Nó có thể chạy đúng nhờ string pool hoặc cache Integer từ -128 đến 127, rồi sai với giá trị khác.

    Ôn thêm câu hỏi Java →
  5. 05Vì sao String là immutable? Khi nào dùng StringBuilder thay cho String?Java · Junior

    Gợi ý trả lời

    String bất biến: mọi thao tác "sửa" đều tạo object mới. Nhờ vậy nó an toàn khi chia sẻ giữa nhiều thread, dùng làm key cho HashMap được (hash không đổi) và JVM có thể dùng chung qua string pool.

    • Nối chuỗi trong vòng lặp bằng + tạo nhiều object trung gian, nên dùng StringBuilder:
    var sb = new StringBuilder();
    for (String s : items) sb.append(s).append(',');
    String result = sb.toString();
    
    • StringBuffer giống StringBuilder nhưng các method có synchronized, chậm hơn và hiếm khi cần.
    • Nối vài chuỗi trên một dòng thì cứ dùng +, compiler đã tối ưu sẵn.

    Người phỏng vấn muốn nghe: bạn hiểu lý do thiết kế (an toàn, hash, pool), không chỉ thuộc định nghĩa.

    Ôn thêm câu hỏi Java →
  6. 06== và === khác nhau thế nào? Những giá trị nào là falsy?JavaScript & TypeScript · Junior

    Gợi ý trả lời

    === so sánh cả kiểu lẫn giá trị. == ép kiểu trước khi so, nên cho ra kết quả khó đoán: "0" == 0 là true, [] == false cũng true.

    • Các giá trị falsy: false, 0, -0, 0n, "", null, undefined, NaN. Mọi thứ khác là truthy, kể cả [], {} và "0".
    • NaN === NaN là false; dùng Number.isNaN(x).
    • Ngoại lệ hay được chấp nhận: x == null để bắt cả null lẫn undefined.

    Hay sai: viết const page = input || 1. Khi người dùng nhập 0, giá trị bị thay bằng 1. Dùng nullish coalescing input ?? 1 để chỉ thay khi là null hoặc undefined.

    Ôn thêm câu hỏi JavaScript & TypeScript →
  7. 07Trong Flutter, StatelessWidget và StatefulWidget khác nhau thế nào? setState hoạt động ra sao?Mobile (Android, iOS, Flutter) · Junior

    Gợi ý trả lời

    StatelessWidget chỉ phụ thuộc tham số đầu vào; StatefulWidget có một object State riêng, tồn tại qua các lần build, để giữ dữ liệu thay đổi theo thời gian.

    • setState đánh dấu widget "dirty"; ở frame kế tiếp Flutter gọi lại build cho widget đó và cây con.
    • Widget là cấu hình nhẹ, tạo lại liên tục; Element và RenderObject mới là phần được tái sử dụng.
    • Dọn dẹp controller, stream subscription trong dispose.
    void _increment() => setState(() => _count++);
    

    Hay sai: gọi setState sau khi widget đã bị huỷ (sau một await); hãy kiểm tra mounted. Đặt setState ở widget quá cao làm build lại cả màn hình, nên tách nhỏ widget và dùng const constructor.

    Khi state dùng chung nhiều màn hình, chuyển sang Provider, Riverpod hoặc Bloc.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  8. 08Vì sao không được làm việc nặng trên main thread (UI thread)? Bạn xử lý thế nào?Mobile (Android, iOS, Flutter) · Junior

    Gợi ý trả lời

    Main thread vẽ UI và nhận thao tác người dùng. Mỗi frame chỉ có khoảng 16 ms (60 Hz) hoặc ít hơn với màn 120 Hz, nên việc nặng sẽ gây giật, lag; Android chặn quá lâu sẽ báo ANR.

    Cách xử lý theo nền tảng:

    • Android: coroutines với Dispatchers.IO cho I/O, Dispatchers.Default cho tính toán, sau đó cập nhật state trên Main.
    • iOS: async/await với Swift concurrency; code UI đánh dấu @MainActor.
    • Flutter: Dart chạy một isolate; I/O bất đồng bộ không chặn, nhưng tính toán nặng (parse JSON lớn, xử lý ảnh) nên đẩy sang isolate khác bằng Isolate.run hoặc compute.

    Hay sai: nghĩ async là đủ. Hàm async trong Dart vẫn chạy trên cùng isolate, nên vòng lặp tính toán nặng vẫn làm đơ UI.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  9. 09Kotlin coroutines: suspend function, scope, dispatcher và structured concurrency là gì?Mobile (Android, iOS, Flutter)

    Gợi ý trả lời

    Coroutine là tác vụ nhẹ có thể tạm dừng (suspend) mà không chặn thread. suspend fun chỉ gọi được từ coroutine hoặc từ suspend function khác.

    • Scope quyết định vòng đời: viewModelScope, lifecycleScope tự huỷ coroutine khi ViewModel hoặc màn hình bị huỷ.
    • Dispatcher quyết định chạy ở đâu: Main, IO, Default.
    • Structured concurrency: coroutine con gắn với cha; cha huỷ thì con huỷ, con lỗi thì lan lên cha (trừ khi dùng SupervisorJob).
    viewModelScope.launch {
        val user = withContext(Dispatchers.IO) { repo.fetchUser() }
        _state.value = UiState.Success(user)
    }
    

    Hay sai: dùng GlobalScope gây rò rỉ; bắt CancellationException trong catch (e: Exception) mà không ném lại, làm hỏng cơ chế huỷ.

    Ôn thêm câu hỏi Mobile (Android, iOS, Flutter) →
  10. 10Vì sao bạn muốn rời công ty hiện tại?Câu hỏi hành vi & văn hoá

    Gợi ý trả lời

    Trả lời thật nhưng hướng về phía trước: nói điều bạn đang tìm kiếm, không kể xấu nơi cũ.

    Lý do chấp nhận được: muốn thử thách mới, muốn làm sản phẩm, dự án kết thúc, công ty tái cấu trúc, thu nhập chưa tương xứng (nói khéo).

    Câu mẫu: "Em đã làm 2 năm ở dự án outsourcing, học được nhiều về quy trình và làm việc với khách nước ngoài. Giờ em muốn gắn bó lâu dài với một sản phẩm để thấy được tác động của những gì mình làm, và vị trí bên mình đúng hướng đó."

    Hay sai: chê sếp, chê đồng nghiệp, kể chuyện drama. Người phỏng vấn sẽ nghĩ bạn cũng sẽ nói về công ty họ như vậy. Nếu nhảy việc nhiều, chuẩn bị lời giải thích cho từng lần.

    Ôn thêm câu hỏi Câu hỏi hành vi & văn hoá →

Bạn đã phỏng vấn ở BSC – Chứng khoán BIDV?

Kể lại các vòng và câu hỏi đã gặp (ẩn danh) để người sau ôn đúng trọng tâm.

Chia sẻ kinh nghiệm phỏng vấn