Đi tới nội dung

Mobile Engineer

Xây app iOS và Android mượt, chạy được khi mất mạng và phát hành an toàn

Lộ trình này đi từ việc chọn hướng (React Native + Expo, Flutter hay native) qua nền tảng hệ điều hành, UI, dữ liệu offline, native module, hiệu năng, test, phát hành và bảo mật, kết thúc ở AI chạy trên thiết bị. Dành cho developer web, đặc biệt là người viết React, muốn chuyển sang mobile một cách nghiêm túc chứ không chỉ dừng ở mức chạy được demo.

Bắt đầu từ chặng 01
chặng
10chặng
chủ đề
50chủ đề
tuần, học part-time
~34tuần, học part-time
  • Nền tảng
  • Cốt lõi
  • Nâng cao
  • ReactReact Native
  • ExpoExpo
  • TypeScriptTypeScript
  • SwiftSwift / SwiftUI
  • KotlinKotlin / Jetpack Compose
  • FlutterFlutter
  • SQLiteSQLite
  • ExpoEAS

Góc nhìn của mình

Mình là frontend developer, làm React và TypeScript hằng ngày, không phải mobile engineer chuyên nghiệp. Mình đã tự tìm hiểu React Native và Expo qua side project, và làm việc cùng các bạn mobile trong team khi tích hợp API, WebSocket và luồng đăng nhập dùng chung giữa web và app. Từ những lần đó mình thấy mobile khác web ở những chỗ rất cụ thể: vòng đời app, quyền truy cập, chữ ký build, review của store và việc không thể hotfix tùy ý. Lộ trình này là con đường mình sẽ đi nếu bắt đầu vai trò Mobile Engineer hôm nay, được kiểm tra lại với tài liệu chính thức tính đến cuối 2026.

Điều mình ước biết sớm hơn

  • Điều mình ước biết sớm hơn là React Native không phải là web chạy trên điện thoại: không có DOM, không có CSS cascade, và mỗi lần chạm vào native là một thế giới khác.
  • Lúc mới học mình hay test trên simulator đời mới rồi yên tâm, trong khi người dùng thật đang cầm máy Android tầm trung với mạng chập chờn; hãy có một máy thật cấu hình thấp ngay từ đầu.
  • Phát hành app là một kỹ năng riêng: code signing, review của store, OTA update và staged rollout đáng được học bài bản như viết component.
  • Biết đọc một chút Swift và Kotlin giúp mình đỡ sợ native module và đọc được stack trace crash, kể cả khi phần lớn code vẫn viết bằng TypeScript.
Đã qua 0/10 chặng

Nền tảng01–03

  1. 01Chặng 01, 2 tuầnChọn hướng đi: cross-platform hay nativeChọn được stack phù hợp với đội và sản phẩm, và giải thích được trade-off của lựa chọn đó.
    Mình chọn React Native + Expo vì tận dụng được React và TypeScript, nhưng đó là lựa chọn theo bối cảnh chứ không phải câu trả lời đúng cho mọi người. Dành thời gian hiểu các hướng khác giúp mình nói chuyện với đồng nghiệp native dễ hơn nhiều.

    Kiến thức cần nắm

    React Native + Expo
    Viết UI bằng React và TypeScript, render ra view native thật, chia sẻ phần lớn code giữa iOS, Android và có thể cả web. Expo là framework được React Native khuyến nghị để bắt đầu dự án mới, lo phần build, routing, cập nhật OTA và thư viện native. Điểm yếu là bạn phụ thuộc vào hệ sinh thái thư viện và đôi khi vẫn phải xuống Swift hoặc Kotlin.
    Flutter
    Dùng Dart và tự vẽ toàn bộ UI bằng engine Impeller, nên giao diện nhất quán giữa các nền tảng và hiệu năng animation tốt. Đổi lại, UI không phải component native của hệ điều hành, nên muốn đúng cảm giác iOS hay Android phải tự chăm chút, và đội phải học thêm một ngôn ngữ ít dùng ngoài Flutter.
    Native: SwiftUI và Jetpack Compose
    SwiftUI và Jetpack Compose đều là UI khai báo (declarative), tư duy khá gần React: state đi xuống, sự kiện đi lên, UI là hàm của state. Native cho trải nghiệm và quyền truy cập API mới nhất ngay ngày đầu, phù hợp app cần đồ họa nặng, tích hợp sâu hệ điều hành hoặc đội đủ người cho hai nền tảng.
    Kotlin Multiplatform
    KMP cho phép chia sẻ logic (networking, dữ liệu, nghiệp vụ) bằng Kotlin giữa Android và iOS trong khi UI vẫn có thể là native. Compose Multiplatform đã ổn định trên iOS, nên giờ có thể chia sẻ cả UI nếu muốn. Đây là lựa chọn tự nhiên cho đội đã mạnh Android.
    Tiêu chí ra quyết định
    Hãy cân nhắc kỹ năng sẵn có của đội, mức độ cần API native, yêu cầu về UI theo chuẩn từng nền tảng, việc có cần web cùng codebase không, và chi phí bảo trì dài hạn. Một app nội bộ dạng form và danh sách khác hẳn một app camera hay bản đồ thời gian thực.

    Nên làm

    • Viết một trang quyết định ngắn (ADR) ghi lại vì sao chọn stack, kèm các điều kiện khiến bạn đổi ý.
    • Làm thử cùng một màn hình nhỏ bằng hai stack trước khi cam kết nếu đội còn phân vân.
    • Kiểm tra các thư viện bắt buộc (thanh toán, bản đồ, push) có hỗ trợ tốt stack đã chọn không trước khi bắt đầu.

    Dễ sai

    • Chọn stack chỉ vì quen tay mà bỏ qua yêu cầu thật của sản phẩm như xử lý nền hay Bluetooth.
    • Tin rằng cross-platform nghĩa là không bao giờ phải đụng tới Xcode hay Android Studio.

    Project chốt chặng: So sánh stack bằng một màn hình

    Dựng cùng một màn hình danh sách có pull-to-refresh và màn chi tiết bằng Expo và bằng một stack khác (Flutter, SwiftUI hoặc Compose). Đạt yêu cầu khi chạy được trên máy thật của cả hai nền tảng và có một bản ADR một trang so sánh DX, kích thước app và thời gian khởi động.

    • ReactReact Native
    • FlutterFlutter
    • SwiftSwiftUI
    • KMP

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Cơ bảnVì sao một app Flutter không tự động có cảm giác native trên iOS và Android?
    2. Câu 2/4Cơ bảnTeam chọn React Native với Expo. Kỳ vọng nào là thực tế?
    3. Câu 3/4Cơ bảnApp cần đồng bộ Bluetooth Low Energy chạy nền với thiết bị y tế. Team chưa thống nhất stack. Bước tiếp theo hợp lý nhất?
    4. Câu 4/4Cơ bảnTeam rất mạnh về Android và Kotlin, muốn chia sẻ networking và business logic với iOS nhưng giữ UI native trên mỗi nền tảng. Lựa chọn nào phù hợp nhất?
  2. 02Chặng 02, 3 tuầnNền tảng hệ điều hành mobileHiểu vòng đời app, quyền, luồng xử lý và bộ nhớ trên iOS và Android để không bị bất ngờ khi app chạy thật.
    Từ web sang, mình quen với việc tab trình duyệt sống mãi. Trên mobile, hệ điều hành có thể tạm dừng hoặc giết app bất cứ lúc nào, và hiểu điều đó thay đổi cách mình thiết kế state.

    Kiến thức cần nắm

    Vòng đời app và màn hình
    Android có vòng đời Activity (onCreate, onStart, onResume, onPause, onStop) và process có thể bị kill khi thiếu bộ nhớ, sau đó khôi phục lại. iOS có trạng thái active, inactive, background, suspended, quản lý qua scene. Trong React Native, AppState cho biết app đang foreground hay background để bạn tạm dừng polling, lưu nháp hoặc làm mới dữ liệu khi quay lại.
    Quyền truy cập (permissions)
    Quyền camera, vị trí, thông báo, ảnh đều được xin lúc runtime và người dùng có thể từ chối vĩnh viễn hoặc chỉ cấp một phần, ví dụ vị trí gần đúng hay chọn một vài ảnh. iOS bắt buộc chuỗi giải thích mục đích trong Info.plist; Android cần khai báo trong manifest và xử lý trường hợp phải mở Settings. Xin quyền đúng lúc người dùng cần tính năng, không xin hết khi mở app.
    Luồng xử lý (threading)
    Cả hai nền tảng đều có main thread (UI thread) và chặn nó quá lâu sẽ gây giật hoặc ANR trên Android. Swift dùng async/await và actor, Kotlin dùng coroutine với Dispatchers. Trong React Native, JS chạy trên luồng riêng, còn các thư viện như Reanimated chạy animation trên UI thread qua worklet.
    Bộ nhớ và tài nguyên
    Thiết bị tầm trung có ít RAM, ảnh lớn và danh sách dài dễ làm app bị hệ điều hành kill. iOS dùng ARC nên cần chú ý retain cycle; Android có garbage collector nhưng vẫn rò rỉ được qua context bị giữ lâu. Hãy resize ảnh, dọn listener khi unmount và theo dõi bộ nhớ bằng Xcode Instruments hoặc Android Studio Profiler.
    Xử lý nền và thông báo
    Hệ điều hành giới hạn rất chặt việc chạy nền để tiết kiệm pin: iOS dùng BGTaskScheduler, Android dùng WorkManager. Push notification đi qua APNs và FCM, cần token, xử lý khi app đang mở, chạy nền hoặc đã tắt, và deep link từ thông báo vào đúng màn hình.

    Nên làm

    • Lưu trạng thái quan trọng như bản nháp form vào bộ nhớ bền vững thay vì tin rằng app sẽ không bị kill.
    • Xin quyền theo ngữ cảnh, kèm màn giải thích trước khi hiện hộp thoại của hệ điều hành.
    • Bật chế độ Don't keep activities trong Developer options của Android để thử khôi phục state.
    • Đẩy việc nặng như parse JSON lớn hoặc xử lý ảnh ra khỏi luồng UI.

    Dễ sai

    • Xin tất cả quyền ngay màn hình đầu tiên khiến người dùng từ chối và khó xin lại.
    • Giả định app luôn ở foreground nên timer, WebSocket và polling chạy mãi khi app ở nền.

    Project chốt chặng: App ghi chú bền bỉ

    Làm app ghi chú có chụp ảnh đính kèm và nhắc nhở bằng local notification. Đạt yêu cầu khi app khôi phục được bản nháp sau khi bị hệ điều hành kill, xử lý đủ các trường hợp từ chối quyền và WebSocket hoặc polling dừng khi app vào nền.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Cơ bảnTrên Android, người dùng đang điền form dài, chuyển sang app camera rồi quay lại thì form trống trơn. Nguyên nhân có khả năng nhất?
    2. Câu 2/4Cơ bảnVì sao không nên xin mọi permission ngay ở màn hình đầu tiên?
    3. Câu 3/4Cơ bảnApp poll API mỗi 5 giây và người dùng phàn nàn hao pin. Cách sửa đúng?
    4. Câu 4/4Cơ bảnParse một file JSON 30MB khi mở màn hình làm UI đứng vài giây trong app React Native. Cách sửa nào đúng?
  3. 03Chặng 03, 3 tuầnReact Native New Architecture và ExpoDựng và vận hành một dự án Expo hiện đại trên New Architecture, hiểu điều gì xảy ra bên dưới.
    Lúc đầu mình chỉ chạy Expo Go và nghĩ thế là đủ. Khi cần một thư viện native, mình mới hiểu vì sao development build và config plugin là cách làm chuẩn.

    Kiến thức cần nắm

    New Architecture: JSI, Fabric, TurboModules
    Từ React Native 0.82, New Architecture là kiến trúc duy nhất: bridge bất đồng bộ cũ được thay bằng JSI, cho JS gọi trực tiếp C++ đồng bộ. Fabric là renderer mới hỗ trợ tính năng concurrent của React, TurboModules là native module được tải lười và có type nhờ Codegen. Các thư viện chưa hỗ trợ New Architecture giờ là rủi ro thật, cần kiểm tra trước khi thêm.
    Hermes
    Hermes là JS engine mặc định, biên dịch JS sang bytecode lúc build nên khởi động nhanh và tốn ít bộ nhớ hơn. Từ React Native 0.84, Hermes V1 là mặc định. Debug bằng React Native DevTools, giao diện dựa trên Chrome DevTools, thay cho các công cụ debug cũ.
    Expo Router
    Routing dựa trên file trong thư mục app, giống tư duy Next.js: layout lồng nhau, stack, tab, modal, deep link và typed routes có sẵn. Các bản gần đây còn có native tabs dùng tab bar của hệ điều hành, data loader và API route. Mỗi màn hình tự có URL nên deep link và universal link gần như miễn phí.
    Development build, CNG và config plugin
    Expo Go chỉ chạy được thư viện có sẵn trong nó; app thật nên dùng development build để thêm bất kỳ native code nào. Continuous Native Generation (prebuild) sinh thư mục ios và android từ app.config, còn config plugin là cách sửa Info.plist, AndroidManifest hay Gradle bằng code thay vì sửa tay. Nhờ vậy nâng cấp SDK nhẹ nhàng hơn nhiều.
    EAS: Build, Submit, Update
    EAS Build build app trên cloud và quản lý chứng chỉ ký, EAS Submit đẩy lên App Store Connect và Google Play, EAS Update phát hành bản cập nhật JS qua OTA. Expo ra khoảng ba bản SDK mỗi năm, mỗi bản gắn với một phiên bản React Native, nên hãy có thói quen nâng cấp đều đặn.

    Nên làm

    • Dùng development build ngay khi dự án vượt khỏi demo thay vì cố giữ trong Expo Go.
    • Không commit thư mục ios và android sinh ra từ prebuild; mọi thay đổi native đi qua config plugin.
    • Chạy npx expo-doctor và npx expo install --check sau mỗi lần thêm thư viện hoặc nâng SDK.
    • Theo dõi changelog của Expo và React Native, nâng cấp từng SDK một thay vì nhảy nhiều bản.

    Dễ sai

    • Sửa tay file native trong thư mục đã prebuild rồi mất hết thay đổi ở lần prebuild sau.
    • Thêm thư viện chưa hỗ trợ New Architecture mà không kiểm tra trên React Native Directory.

    Project chốt chặng: Khung app Expo chuẩn

    Tạo app Expo với Expo Router (tab, stack, modal), TypeScript strict, development build và một config plugin tự viết thêm quyền camera. Đạt yêu cầu khi build được qua EAS cho cả hai nền tảng, deep link mở đúng màn chi tiết và prebuild --clean không làm mất cấu hình nào.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Cơ bảnJSI trong New Architecture thay đổi điều gì so với bridge cũ?
    2. Câu 2/4Cơ bảnVì sao project thật nên chuyển từ Expo Go sang development build?
    3. Câu 3/4Cơ bảnMột dev sửa tay ios/<App>/Info.plist trong project dùng Continuous Native Generation, và thay đổi biến mất sau lần prebuild tiếp theo. Cách đúng là gì?
    4. Câu 4/4Cơ bảnTrên React Native từ 0.82, trước khi thêm một thư viện native mới, bạn nên kiểm tra gì?

Cốt lõi04–07

  1. 04Chặng 04, 4 tuầnUI, điều hướng và accessibilityXây giao diện cảm giác đúng chất từng nền tảng, điều hướng tự nhiên và dùng được với trình đọc màn hình.
    Lúc mới học, mình hay dựng màn hình mobile theo thói quen web và nó trông như một website thu nhỏ. Học HIG và Material 3 giúp mình hiểu vì sao back gesture, tab bar và cỡ chữ hệ thống quan trọng đến vậy.

    Kiến thức cần nắm

    Layout và styling trong React Native
    Layout dùng Flexbox qua Yoga với mặc định flexDirection là column, không có CSS cascade nên style được truyền tường minh qua component. Phải xử lý safe area, bàn phím che input và edge-to-edge, vốn đã bắt buộc trên Android mới. Có thể dùng StyleSheet, Unistyles hoặc NativeWind tùy đội, miễn là có design token thống nhất.
    Quy ước nền tảng: HIG và Material 3
    Apple Human Interface Guidelines và Material 3 mô tả điều hướng, cỡ chữ, khoảng cách và vùng chạm tối thiểu (khoảng 44pt trên iOS, 48dp trên Android). iOS 26 mang ngôn ngữ thiết kế Liquid Glass, Android có Material 3 Expressive. Không cần hai giao diện khác nhau, nhưng các chi tiết như nút back, hộp thoại và picker nên theo đúng nền tảng.
    Điều hướng và deep link
    Stack, tab và modal phải dùng native navigator để có gesture vuốt back và predictive back trên Android. Thiết kế URL cho mọi màn hình quan trọng, cấu hình universal link (iOS) và App Links (Android) để link từ web và thông báo mở thẳng vào app. Nhớ xử lý trường hợp deep link tới màn cần đăng nhập.
    Animation và gesture
    Reanimated chạy animation trên UI thread nên vẫn mượt khi JS bận, Gesture Handler xử lý cử chỉ ở tầng native. Layout animation và shared element transition làm app có cảm giác native hơn. Tôn trọng tùy chọn Reduce Motion của người dùng.
    Accessibility
    Dùng accessibilityLabel, accessibilityRole, accessibilityState và accessibilityHint để VoiceOver và TalkBack đọc đúng. Hỗ trợ Dynamic Type và font scale mà không vỡ layout, đủ tương phản màu và thứ tự focus hợp lý. Test thật bằng cách bật trình đọc màn hình và đi hết một luồng chính.

    Nên làm

    • Test mỗi màn hình với cỡ chữ hệ thống lớn nhất và với VoiceOver hoặc TalkBack.
    • Dùng component điều hướng native thay vì tự dựng stack bằng JS.
    • Định nghĩa design token (màu, khoảng cách, typography) hỗ trợ dark mode từ đầu.
    • Đảm bảo mọi vùng chạm đạt kích thước tối thiểu theo HIG và Material.

    Dễ sai

    • Copy nguyên UI web sang mobile với hover, menu nhỏ và nút bấm quá sát nhau.
    • Khóa cứng chiều cao text khiến layout vỡ khi người dùng tăng cỡ chữ.

    Project chốt chặng: App đọc tin tiếp cận được

    Xây app đọc tin có tab, danh sách, màn chi tiết, chia sẻ và deep link từ web. Đạt yêu cầu khi đi hết luồng chính chỉ bằng VoiceOver và TalkBack, không vỡ layout ở cỡ chữ lớn nhất, hỗ trợ dark mode và gesture back hoạt động đúng trên cả hai nền tảng.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Trung cấpTrong React Native, <View><Text>A</Text><Text>B</Text></View> không có style gì sẽ xếp A và B thế nào?
    2. Câu 2/4Trung cấpVì sao animation viết bằng Reanimated vẫn mượt khi JS thread đang bận?
    3. Câu 3/4Trung cấpNgười dùng tăng cỡ chữ hệ thống và text trong các nút bị cắt vì nút có height: 40. Cách sửa đúng?
    4. Câu 4/4Trung cấpNút chỉ có icon thùng rác, và TalkBack chỉ đọc là "button". Cách sửa đúng?
  2. 05Chặng 05, 4 tuầnDữ liệu offline-first và networkingThiết kế lớp dữ liệu chạy được khi mất mạng, đồng bộ lại khi có mạng và xử lý xung đột hợp lý.
    Trên web mình quen coi server là nguồn sự thật duy nhất. Trên mobile, mạng chập chờn là bình thường, nên mình phải học cách coi database local là nguồn dữ liệu chính cho UI.

    Kiến thức cần nắm

    Lưu trữ local
    expo-sqlite (hoặc op-sqlite) cho dữ liệu có cấu trúc, thường đi kèm Drizzle ORM để có schema và migration có type. MMKV cho key-value nhỏ và nhanh như cài đặt, thay cho AsyncStorage chậm. Dữ liệu nhạy cảm như token không để ở đây mà đưa vào Keychain hoặc Keystore.
    Kiến trúc offline-first
    UI đọc từ database local, việc ghi được lưu vào local trước rồi xếp vào hàng đợi (outbox) để gửi lên server khi có mạng. Mỗi thao tác cần id do client tạo và idempotency key để retry không tạo bản ghi trùng. Theo dõi trạng thái mạng bằng NetInfo nhưng đừng tin nó tuyệt đối, cứ xử lý lỗi request như bình thường.
    Đồng bộ và xử lý xung đột
    Đồng bộ delta theo mốc thời gian hoặc version thay vì tải lại toàn bộ. Chọn chiến lược xung đột rõ ràng: last-write-wins, merge theo trường, hoặc CRDT cho dữ liệu cộng tác. Các engine như PowerSync, WatermelonDB hay giải pháp dựa trên CRDT giúp tránh tự viết lại phần khó nhất.
    Networking và cache
    TanStack Query trên React Native cần nối focusManager với AppState và onlineManager với NetInfo để refetch đúng lúc. Dùng timeout, retry có backoff, phân trang bằng cursor và nén payload. Persist query cache để mở app là thấy dữ liệu cũ ngay trong lúc tải mới.
    Realtime trên mobile
    WebSocket hay SSE bị ngắt khi app vào nền, nên cần reconnect có backoff và đồng bộ lại phần đã lỡ khi quay về foreground. Với cập nhật quan trọng khi app đã đóng, dùng push notification (có thể là silent push) để đánh thức việc đồng bộ thay vì giữ kết nối.

    Nên làm

    • Thiết kế schema local có migration được version hóa ngay từ bản đầu tiên.
    • Cho mọi thao tác ghi một id phía client và idempotency key.
    • Hiển thị rõ trạng thái đồng bộ (đang chờ, lỗi, đã lưu) để người dùng tin dữ liệu.
    • Test bằng chế độ máy bay, mạng chậm giả lập và chuyển mạng giữa chừng.

    Dễ sai

    • Lưu toàn bộ state app thành một chuỗi JSON lớn trong AsyncStorage rồi đọc ghi mỗi lần thay đổi.
    • Quên migration khiến người dùng bản cũ bị crash khi cập nhật app.

    Project chốt chặng: App quản lý việc offline-first

    Làm app todo có dự án, nhãn và đồng bộ với một API NestJS hoặc backend có sẵn. Đạt yêu cầu khi thêm, sửa, xóa được lúc offline, đồng bộ đúng khi có mạng, không tạo bản ghi trùng khi retry, và có ít nhất một migration schema chạy được trên dữ liệu cũ.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Trung cấpTrong kiến trúc offline-first, vì sao mỗi thao tác ghi trong outbox cần id do client sinh ra và một idempotency key?
    2. Câu 2/4Trung cấpVì sao không nên hoàn toàn tin vào trạng thái kết nối của NetInfo?
    3. Câu 3/4Trung cấpApp lưu 5.000 công việc thành một chuỗi JSON lớn trong AsyncStorage và ghi lại toàn bộ mỗi khi sửa một công việc. App ngày càng chậm. Cách sửa đúng?
    4. Câu 4/4Trung cấpTanStack Query trên React Native không refetch khi app quay lại foreground. Cách sửa đúng?
  3. 06Chặng 06, 4 tuầnNative module và API thiết bịDùng được camera, vị trí, sinh trắc học và tự viết native module nhỏ bằng Swift và Kotlin khi thư viện không đáp ứng.
    Phần mình ngại nhất lúc đầu là mở Xcode. Viết thử một Expo Module nhỏ bằng Swift và Kotlin là lúc mình thấy native không đáng sợ như tưởng tượng.

    Kiến thức cần nắm

    Thư viện thiết bị có sẵn
    Expo SDK có expo-camera, expo-location, expo-local-authentication, expo-notifications, expo-image, expo-file-system và nhiều module khác đã hỗ trợ New Architecture. Trước khi viết native, hãy kiểm tra React Native Directory và Expo SDK. Đọc kỹ phần cấu hình quyền và giới hạn chạy nền của từng module.
    Expo Modules API
    Viết native module bằng Swift và Kotlin với DSL ngắn gọn: khai báo function, async function, event và cả native view. Module tự có config plugin, autolinking và chạy tốt trên New Architecture. Đây là cách dễ nhất cho dev đến từ React để chạm vào native.
    Turbo Native Modules và Codegen
    Cách chuẩn của React Native: viết spec TypeScript, Codegen sinh interface C++ và native, rồi hiện thực bằng Objective-C++/Swift và Kotlin. Hiểu cơ chế này giúp đọc được thư viện bên thứ ba. Nitro Modules là lựa chọn cộng đồng tối ưu hiệu năng dựa trên JSI nếu cần gọi native rất nhiều lần.
    Native view và tích hợp UI
    Khi cần component hệ điều hành như map, video player hay picker, bạn bọc native view và truyền props, events qua cầu nối. Expo UI cho phép dùng trực tiếp component SwiftUI và Jetpack Compose trong cây React. Cẩn thận với kích thước, layout và vòng đời của view native.
    Đọc Swift và Kotlin đủ dùng
    Không cần thành chuyên gia, nhưng cần đọc được optional, closure và async/await trong Swift, null safety, coroutine và extension trong Kotlin. Biết dùng Xcode và Android Studio để đặt breakpoint native, xem Logcat và đọc crash log là kỹ năng tiết kiệm rất nhiều thời gian.

    Nên làm

    • Ưu tiên thư viện được duy trì tốt và hỗ trợ New Architecture trước khi tự viết native.
    • Giữ API của native module nhỏ, bất đồng bộ và có type đầy đủ.
    • Viết config plugin cho mọi thay đổi native mà module cần.
    • Test native module trên máy thật của cả hai nền tảng, kể cả bản hệ điều hành cũ nhất bạn hỗ trợ.

    Dễ sai

    • Gọi native đồng bộ cho việc nặng làm chặn luồng JS hoặc UI.
    • Quên dọn listener và subscription phía native, gây rò rỉ bộ nhớ.

    Project chốt chặng: Module sinh trắc học và pin

    Viết một Expo Module bằng Swift và Kotlin trả về mức pin, trạng thái sạc và phát event khi thay đổi, kèm màn hình khóa bằng Face ID hoặc vân tay. Đạt yêu cầu khi module có type TypeScript, event được hủy đúng khi unmount và chạy được trên máy thật cả hai nền tảng.

    • Native Modules
    • SwiftSwift
    • KotlinKotlin

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Trung cấpNative module cho phép gọi đồng bộ qua JSI. Vì sao vẫn không nên gọi đồng bộ cho việc nặng như nén video?
    2. Câu 2/4Trung cấpComponent đăng ký const sub = Module.addListener('progress', onProgress) trong useEffect nhưng không return cleanup. Hậu quả là gì?
    3. Câu 3/4Trung cấpTrong Kotlin, val len = user.nickname?.length ?: 0 trả về gì khi nickname là null?
    4. Câu 4/4Trung cấpApp Expo cần xác thực bằng sinh trắc học (Face ID, vân tay). Bước đầu tiên hợp lý?
  4. 07Chặng 07, 3 tuầnHiệu năng: khởi động, danh sách và jankĐo và cải thiện thời gian khởi động, độ mượt khi cuộn và animation bằng công cụ profiling thật.
    Lúc mới học mình hay tối ưu theo cảm giác trên simulator, và kết quả gần như vô nghĩa. Bài học là luôn đo trên build release và máy thật cấu hình thấp.

    Kiến thức cần nắm

    Thời gian khởi động
    Phân biệt cold, warm và hot start; Android Vitals theo dõi thời gian tới khung hình đầu tiên. Giảm công việc lúc khởi động: tải lười màn hình, trì hoãn SDK analytics, tránh đọc storage đồng bộ lớn. Hermes với bytecode đã giúp nhiều, phần còn lại là kỷ luật về những gì chạy trước màn hình đầu tiên.
    Danh sách dài
    FlatList đủ cho danh sách vừa phải, còn FlashList hoặc Legend List tái sử dụng view nên mượt hơn với danh sách lớn. Memo hóa item, giữ keyExtractor ổn định, tránh hàm và object tạo mới trong renderItem. Dùng expo-image với kích thước phù hợp và cache thay cho ảnh full size.
    Jank và render thừa
    Mục tiêu là giữ khung hình ổn định ở 60 hoặc 120 fps, nên mọi việc trên luồng JS lúc cuộn hay animate đều đáng nghi. React Compiler giúp giảm render thừa, nhưng vẫn cần chia nhỏ state, tránh context quá rộng. Animation đặt trên UI thread bằng Reanimated thay vì setState liên tục.
    Công cụ profiling
    React Native DevTools có Profiler và Performance panel để xem render và thời gian JS. Bên native dùng Xcode Instruments (Time Profiler, Allocations, Hangs) và Android Studio Profiler hoặc Perfetto. Bật Performance Monitor để xem FPS của luồng JS và UI trong lúc thao tác.
    Kích thước app và tài nguyên
    Kích thước app ảnh hưởng tỉ lệ cài đặt, nhất là ở thị trường mạng chậm. Theo dõi bundle JS bằng công cụ phân tích của Expo Atlas, nén và chọn định dạng ảnh hợp lý, bật R8 trên Android và kiểm tra kích thước trong App Store Connect và Play Console.

    Nên làm

    • Đo hiệu năng trên build release và máy Android tầm trung, không đo trên debug build.
    • Đặt ngân sách cho thời gian khởi động và kích thước app, kiểm tra trong CI.
    • Profile trước khi tối ưu và ghi lại số liệu trước và sau mỗi thay đổi.
    • Chuyển animation và gesture sang UI thread bằng Reanimated và Gesture Handler.

    Dễ sai

    • Kết luận app chậm hay nhanh dựa trên chế độ dev, vốn chậm hơn bản release nhiều.
    • Bọc useMemo và useCallback khắp nơi mà không đo, trong khi vấn đề thật nằm ở ảnh quá lớn.

    Project chốt chặng: Feed ảnh mượt

    Dựng feed khoảng 5.000 item có ảnh, like và vô hạn cuộn, sau đó profile và tối ưu. Đạt yêu cầu khi có số liệu thời gian khởi động và FPS lúc cuộn trước và sau tối ưu trên một máy Android tầm trung, kèm ghi chú các thay đổi có tác dụng.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Trung cấpVì sao không nên đánh giá hiệu năng app React Native trên debug build?
    2. Câu 2/4Trung cấpVì sao FlashList hay Legend List thường mượt hơn FlatList với danh sách rất dài?
    3. Câu 3/4Trung cấpProfiler cho thấy danh sách giật vì phải decode ảnh gốc 4000px để hiện thumbnail 64px. Cách sửa đúng?
    4. Câu 4/4Trung cấpThanh tiến trình được animate bằng setInterval(() => setProgress((p) => p + 1), 16) và bị giật khi app tải dữ liệu. Cách sửa đúng?

Nâng cao08–10

  1. 08Chặng 08, 3 tuầnKiểm thử: unit, component và E2ECó bộ test đủ tin để phát hành thường xuyên, từ unit test tới E2E chạy trên thiết bị thật trong CI.
    Mình quen Vitest và Playwright trên web, nên bất ngờ khi E2E mobile chậm và dễ flaky hơn nhiều. Chọn đúng tầng test cho từng loại lỗi giúp tiết kiệm rất nhiều công sức.

    Kiến thức cần nắm

    Unit test logic
    Tách logic nghiệp vụ, đồng bộ dữ liệu và format ra hàm thuần để test nhanh bằng Jest với preset jest-expo. Mock tối thiểu các module native, ưu tiên test hành vi thay vì chi tiết hiện thực. Phần đồng bộ offline và xử lý xung đột là nơi unit test có giá trị nhất.
    Component test
    React Native Testing Library render component và truy vấn theo vai trò, nhãn, text giống cách người dùng nhìn thấy. Truy vấn bằng role và accessibility label vừa test được hành vi vừa ép bạn làm accessibility tốt hơn. Test các trạng thái loading, lỗi, rỗng chứ không chỉ trường hợp đẹp.
    E2E với Maestro
    Maestro viết flow bằng YAML, chạy trên simulator, emulator hoặc thiết bị thật, tự chờ UI ổn định nên ít flaky. Phù hợp để test các luồng chính như đăng nhập, mua hàng, onboarding. Có thể chạy trong EAS Workflows hoặc CI riêng trên mỗi pull request.
    E2E với Detox
    Detox là framework gray-box viết bằng JavaScript, đồng bộ với trạng thái app (network, animation, timer) để giảm flaky. Mạnh khi cần kiểm soát sâu, nhưng cấu hình phức tạp hơn Maestro. Chọn một công cụ và đầu tư cho nó thay vì dùng cả hai.
    Thiết bị và ma trận test
    Không thể test mọi máy, hãy chọn ma trận dựa trên analytics: phiên bản hệ điều hành cũ nhất hỗ trợ, một máy Android yếu, một máy màn hình nhỏ, tablet nếu có. Device farm như Firebase Test Lab hoặc dịch vụ tương tự bổ sung cho máy thật trong tay.

    Nên làm

    • Giữ E2E cho vài luồng quan trọng nhất, phần còn lại đẩy xuống unit và component test.
    • Gắn testID hoặc accessibility label ổn định cho các phần tử E2E cần tương tác.
    • Chạy test trên build release-like trong CI cho mỗi pull request vào nhánh chính.
    • Seed dữ liệu và reset trạng thái app trước mỗi flow E2E.

    Dễ sai

    • Dùng sleep cố định trong E2E thay vì chờ điều kiện, khiến test chậm và flaky.
    • Mock quá nhiều tới mức test pass nhưng app thật vẫn hỏng.

    Project chốt chặng: Pipeline test cho app todo

    Thêm unit test cho logic đồng bộ, component test cho màn danh sách và ba flow Maestro (đăng nhập, tạo việc offline, đồng bộ lại). Đạt yêu cầu khi tất cả chạy tự động trong CI cho mỗi pull request và flow E2E pass ổn định mười lần liên tiếp.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Nâng caoVì sao chỉ nên giữ E2E cho vài hành trình quan trọng như đăng nhập hay thanh toán?
    2. Câu 2/4Nâng caoVì sao logic offline sync và giải quyết xung đột là nơi unit test có giá trị nhất?
    3. Câu 3/4Nâng caoFlow Maestro chờ cố định 5 giây sau khi bấm Login rồi mới kiểm tra màn hình Home, và thỉnh thoảng fail trên CI. Cách sửa đúng?
    4. Câu 4/4Nâng caoTeam chỉ có ngân sách cho vài thiết bị test. Ma trận nào hợp lý nhất?
  2. 09Chặng 09, 4 tuầnPhát hành: ký app, store review và OTAĐưa app lên App Store và Google Play một cách lặp lại được, với OTA update, staged rollout và theo dõi crash.
    Trên web mình deploy vài lần một ngày và rollback trong vài giây. Mobile thì một bản lỗi có thể nằm trên máy người dùng nhiều tuần, nên quy trình phát hành phải cẩn trọng hơn hẳn.

    Kiến thức cần nắm

    Code signing
    iOS cần Apple Developer account, certificate, provisioning profile và entitlement; Android cần upload key và Play App Signing giữ app signing key. EAS có thể quản lý credentials, nhưng bạn vẫn phải hiểu chúng để xử lý khi hết hạn hay đổi đội. Từ 2026 Google còn yêu cầu xác minh developer cho app cài trên thiết bị Android chứng nhận, bắt đầu ở một số quốc gia.
    Store review và chính sách
    App Store Review Guidelines và chính sách Google Play quy định về quyền riêng tư, thanh toán trong app, nội dung và đăng nhập. Cần privacy manifest và nhãn quyền riêng tư bên iOS, Data safety form bên Play, và đáp ứng yêu cầu target API level mới của Play mỗi năm. Hãy chuẩn bị tài khoản demo cho reviewer và giải thích rõ tính năng nhạy cảm.
    OTA update
    EAS Update cho phép đẩy thay đổi JS và asset mà không qua review, nhưng chỉ khi tương thích runtime version với native code đang chạy. Dùng fingerprint để tự xác định runtime version, chia channel cho preview và production, và luôn có kế hoạch rollback. OTA dùng để sửa lỗi và cải tiến nhỏ, không để lách chính sách store.
    Staged rollout và feature flag
    Google Play hỗ trợ phát hành theo phần trăm, App Store có phased release trong 7 ngày, EAS Update cũng rollout theo tỉ lệ. Kết hợp với feature flag để bật tắt tính năng mà không cần build mới. Theo dõi tỉ lệ crash-free trước khi tăng phần trăm.
    Crash reporting và quan sát
    Dùng Sentry, Firebase Crashlytics hoặc EAS Observe để thu crash JS lẫn native, upload source map và dSYM để đọc được stack trace. Theo dõi crash-free users, ANR, thời gian khởi động và lỗi network theo phiên bản. Tự động hóa toàn bộ chuỗi build, test, submit bằng EAS Workflows hoặc Fastlane trong CI.

    Nên làm

    • Tự động hóa build, tăng version và submit trong CI thay vì build tay trên máy cá nhân.
    • Luôn upload source map và debug symbol cho mỗi bản phát hành.
    • Phát hành theo phần trăm và đặt ngưỡng crash-free rõ ràng để dừng rollout.
    • Ghi lại runtime version của mỗi bản build để OTA không gửi nhầm JS cho native không tương thích.

    Dễ sai

    • Đẩy OTA có thay đổi cần native mới, làm app crash ngay khi mở.
    • Để certificate hoặc provisioning profile hết hạn đúng lúc cần phát hành gấp.

    Project chốt chặng: Pipeline phát hành hoàn chỉnh

    Thiết lập pipeline với EAS Workflows hoặc GitHub Actions: build, test, submit lên TestFlight và internal testing track, kèm EAS Update theo channel và Sentry. Đạt yêu cầu khi một commit vào nhánh release tự động ra bản test, crash có stack trace đọc được và thử rollback một bản OTA thành công.

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Nâng caoVì sao một bản OTA dùng native module mới thêm có thể làm app crash ngay khi mở?
    2. Câu 2/4Nâng caoApp dùng Play App Signing và team làm mất upload key. Chuyện gì xảy ra?
    3. Câu 3/4Nâng caoBản mới đang rollout 10% và crash-free users giảm từ 99,8% xuống 98,5%. Nên làm gì?
    4. Câu 4/4Nâng caoBáo cáo crash chỉ hiện JS đã minify và frame native chưa được symbolicate. Cách sửa đúng?
  3. 10Chặng 10, 4 tuầnBảo mật và AI trên thiết bịBảo vệ dữ liệu người dùng theo OWASP MASVS và tích hợp được tính năng AI chạy trên thiết bị một cách có trách nhiệm.
    Lúc đầu mình nghĩ code app là bí mật, cho tới khi hiểu rằng ai cũng giải nén được file app. Còn AI trên thiết bị là phần mình thấy thú vị nhất khi làm việc với LLM, vì nó giải quyết được cả độ trễ lẫn quyền riêng tư.

    Kiến thức cần nắm

    Lưu trữ an toàn
    Token và bí mật lưu trong iOS Keychain và Android Keystore, qua expo-secure-store hoặc thư viện tương đương, không để trong AsyncStorage hay MMKV không mã hóa. Có thể gắn khóa với sinh trắc học cho dữ liệu nhạy cảm. Mọi thứ trong bundle JS đều đọc được, nên không bao giờ nhúng API key bí mật vào app.
    Bảo mật mạng
    Bắt buộc HTTPS, App Transport Security trên iOS và Network Security Config trên Android. Certificate pinning (pin public key) giảm rủi ro man-in-the-middle nhưng cần kế hoạch xoay khóa, nếu không app có thể mất kết nối hàng loạt. Dùng OAuth 2.0 với PKCE qua trình duyệt hệ thống cho đăng nhập.
    OWASP MASVS và toàn vẹn app
    OWASP MASVS chia yêu cầu thành các nhóm như storage, crypto, auth, network, platform, code, resilience và privacy, đi kèm MASTG để kiểm thử. Dùng Play Integrity API và App Attest để server xác minh request đến từ app thật. Obfuscation và phát hiện root hay jailbreak chỉ làm chậm kẻ tấn công, logic quan trọng phải kiểm tra ở server.
    AI trên thiết bị: API của hệ điều hành
    Apple có Foundation Models framework cho phép gọi model on-device của Apple Intelligence bằng Swift, có guided generation và tool calling. Android có ML Kit GenAI API và Prompt API dùng Gemini Nano qua AICore, hiện chỉ trên một số thiết bị và chỉ khi app ở foreground. Cả hai đều cần kiểm tra thiết bị có hỗ trợ và có phương án dự phòng.
    AI trên thiết bị: model tự mang
    Với React Native, có thể chạy model nhỏ bằng ExecuTorch, LiteRT (TensorFlow Lite) hoặc Core ML qua native module. Cân nhắc kích thước model, RAM, nhiệt độ máy và pin; thường tải model sau khi cài thay vì nhúng vào app. Thiết kế kiến trúc lai: việc nhẹ và nhạy cảm chạy trên máy, việc nặng gọi lên cloud.

    Nên làm

    • Rà app theo checklist OWASP MASVS trước mỗi bản phát hành lớn.
    • Giữ mọi bí mật ở server và cấp token ngắn hạn cho app.
    • Kiểm tra khả năng AI của thiết bị lúc runtime và luôn có đường dự phòng khi không hỗ trợ.
    • Ghi rõ trong chính sách quyền riêng tư dữ liệu nào xử lý trên máy và dữ liệu nào gửi lên server.

    Dễ sai

    • Nhúng API key của dịch vụ LLM vào bundle JS rồi bị lấy ra dùng miễn phí.
    • Bật certificate pinning mà không có khóa dự phòng, đến khi đổi chứng chỉ thì app mất kết nối.

    Project chốt chặng: Tóm tắt ghi chú an toàn

    Thêm vào app ghi chú tính năng khóa bằng sinh trắc học, lưu token trong secure store và tóm tắt ghi chú bằng model on-device (Foundation Models hoặc Gemini Nano), dự phòng gọi API qua backend của bạn. Đạt yêu cầu khi không có bí mật nào trong bundle, app pass các mục storage và network của MASVS, và tính năng AI hoạt động hoặc dự phòng đúng trên máy không hỗ trợ.

    • Security
    • MASVS
    • On-device AI

    Kiểm tra nhanh

    Đã trả lời 0/4 · đúng 0

    Trả lời đúng ít nhất 3/4 câu để đánh dấu đã qua chặng này.

    1. Câu 1/4Nâng caoVì sao không được nhúng API key của nhà cung cấp LLM vào app, kể cả khi đã obfuscate?
    2. Câu 2/4Nâng caoApp có phát hiện root, jailbreak và obfuscate code. Vì sao các logic quan trọng như kiểm tra số dư vẫn phải nằm ở server?
    3. Câu 3/4Nâng caoTeam chuẩn bị bật certificate pinning. Điều gì bắt buộc phải có kèm theo?
    4. Câu 4/4Nâng caoTính năng tóm tắt dùng Gemini Nano qua ML Kit GenAI trên Android. Cách xử lý đúng cho thiết bị không hỗ trợ là gì?

Những gì đang quan trọng lúc này và hướng đi trong vài năm tới.

Hiện tại

  • New Architecture là mặc định duy nhất

    Từ React Native 0.82 kiến trúc cũ đã bị loại bỏ, Hermes V1 thành mặc định từ 0.84. Việc cần làm bây giờ là dọn các thư viện cũ, tận dụng tính năng concurrent của React và JSI cho native module.

  • Expo là cách bắt đầu mặc định

    Expo Router, development build, config plugin và EAS đã thành quy trình chuẩn cho dự án React Native mới. Phần lớn đội không còn duy trì thư mục native thủ công mà sinh ra bằng CNG.

  • UI native ngay trong cross-platform

    Expo UI và native tabs cho phép dùng component SwiftUI và Jetpack Compose trực tiếp, trong khi Liquid Glass trên iOS và Material 3 Expressive trên Android nâng kỳ vọng về cảm giác native. App cross-platform bị đánh giá khắt khe hơn về độ đúng chất nền tảng.

  • Quy trình phát hành chặt hơn

    Google yêu cầu xác minh developer cho app Android bắt đầu từ cuối 2026, các store siết dần yêu cầu về quyền riêng tư. Theo dõi crash, OTA có kiểm soát và staged rollout giờ là kỹ năng cơ bản chứ không còn là điểm cộng.

Sắp tới

  • AI on-device thành tính năng chuẩn

    Foundation Models của Apple và Gemini Nano trên Android sẽ phủ thêm nhiều thiết bị, kéo theo kỳ vọng về tóm tắt, phân loại và trợ lý chạy offline. Hãy học cách thiết kế kiến trúc lai on-device và cloud, cùng cách đánh giá chất lượng đầu ra.

  • Ranh giới cross-platform mờ dần

    Compose Multiplatform trên iOS đã ổn định, Expo đưa SwiftUI và Compose vào React, và React Native tiến gần chuẩn web hơn. Mobile engineer có giá trị là người hiểu cả tầng JS lẫn native và chọn đúng công cụ cho từng màn.

  • Agent viết code và test cho mobile

    Xcode, Android Studio và các công cụ như Expo đang tích hợp AI agent để sinh code, chạy build và test trên simulator. Người làm mobile sẽ dành nhiều thời gian hơn cho review, kiến trúc và kiểm thử trên thiết bị thật, nên kỹ năng đọc code native và viết E2E càng quan trọng.

  • Quyền riêng tư và bảo mật theo luật

    Các quy định về dữ liệu và an toàn ứng dụng ngày càng chặt, MASVS dần trở thành yêu cầu trong hợp đồng và kiểm toán. Chuẩn bị bằng cách đưa kiểm tra bảo mật và quyền riêng tư vào CI ngay từ bây giờ.

Đi hết roadmap, bạn sẽ

  • Chọn và bảo vệ được lựa chọn giữa React Native + Expo, Flutter, native và Kotlin Multiplatform cho một sản phẩm cụ thể.
  • Xây app Expo trên New Architecture với điều hướng, deep link và accessibility đạt chuẩn nền tảng.
  • Thiết kế lớp dữ liệu offline-first với SQLite, đồng bộ an toàn và migration có version.
  • Viết native module nhỏ bằng Swift và Kotlin, đọc được crash log native.
  • Profile và cải thiện thời gian khởi động, độ mượt khi cuộn trên máy thật cấu hình thấp.
  • Vận hành pipeline test và phát hành với code signing, OTA, staged rollout và theo dõi crash.
  • Bảo vệ dữ liệu theo OWASP MASVS và tích hợp tính năng AI on-device có phương án dự phòng.

Bài viết liên quan

Tất cả bài viết

List virtualization từ con số 0: render 100.000 dòng mà vẫn mượt

Tự tay viết một virtual list bằng React + TypeScript: toán windowing với số liệu cụ thể, overscan, chiều cao thay đổi với prefix sum và binary search, đo bằng ResizeObserver, chống giật khi scroll, accessibility — và khi nào không nên virtualize.

Hiệu năng18 phút

Các role khác

Roadmap

Frontend Engineer

Role chính của mình

Xây giao diện web nhanh, dễ tiếp cận và bền vững khi sản phẩm lẫn team cùng lớn lên

10 chặng, 51 chủ đề, ~34 tuần

Thiết kế, xây dựng và vận hành API, dữ liệu và service phía sau mỗi sản phẩm

10 chặng, 52 chủ đề, ~37 tuần

Tìm rủi ro trước người dùng và biến chất lượng thành thứ cả team đo được

10 chặng, 50 chủ đề, ~36 tuần

Xây hạ tầng, pipeline và nền tảng để team ship nhanh, chạy ổn định, an toàn

10 chặng, 56 chủ đề, ~42 tuần

Xây sản phẩm trên nền LLM: từ gọi API, RAG, agent đến eval và vận hành production

10 chặng, 58 chủ đề, ~31 tuần