ข้ามไปยังเนื้อหาหลัก

ตั้งค่า Android

หน้านี้รวบรวมโน้ต Android build ที่เคยกระจายอยู่หลายที่ใน repo

ตำแหน่งของแอป​

  • Mobile POS app: apps/mobile
  • POS terminal app: apps/pos-terminal

คำสั่งหลัก​

รันจาก monorepo root:

yarn mobile:start
yarn mobile:android
yarn mobile:apk
yarn mobile:aab
yarn terminal:start
yarn terminal:android

คำสั่ง Android run, APK, AAB และ clean ใน workspace จะเรียก script แก้ compatibility ของ native dependencies ก่อนรัน React Native หรือ Gradle ทุกครั้ง จึงรองรับกรณีคืน dependencies จาก cache โดยไม่ได้รัน postinstall ด้วย หากเรียก ./gradlew โดยตรง ให้รัน node scripts/patch-react-native-dependencies.js จาก repo root ก่อน

Mobile ใช้ Metro port 8081 ส่วนคำสั่ง start/run ของ Terminal ใช้ 8082 เป็นค่าเริ่มต้น คำสั่งสำหรับเครื่องจริงคือ yarn mobile:android:8081 และ yarn terminal:android:8082 ซึ่งเลือก 0074490208420196 และ A65C009P00400034 ตามลำดับ โดยต้องเชื่อมต่อและอนุญาต USB debugging ของเครื่องเป้าหมายก่อน

Desktop emulator สำหรับ Terminal (1920×1080)​

yarn terminal:emu # เปิดเฉพาะ emulator
yarn terminal:android:8082:emu # เปิด รอ boot แล้ว build/install Terminal

ทั้งสองคำสั่งใช้ AVD Large_Desktop ที่มีอยู่แล้ว (1920×1080 แนวนอน 160 dpi) และค้นหา serial ของ emulator ตัวนี้อัตโนมัติ จึงใช้ได้แม้ emulator อื่นครอง emulator-5554 อยู่ หรือเสียบเครื่องจริงพร้อมกัน helper จะปลุกจอ ปิดหน้าล็อก และตั้งพักจอเป็น 30 นาที หลังคำสั่งจบ emulator จะยังเปิดอยู่ log การเปิด emulator อยู่ที่ bsr-terminal-emulator.log ใน temporary directory ของระบบ

เมื่อเริ่ม emulator process ใหม่ helper จะใช้ DNS 1.1.1.1,8.8.8.8 เพื่อไม่ให้ emulator ส่งคำขอ DNS ไปยัง router เดิมเมื่อคอมเปลี่ยน Wi-Fi หากเครือข่ายต้องใช้ DNS อื่น ให้ตั้ง POS_TERMINAL_DNS_SERVERS แล้วปิด emulator และรันคำสั่งใหม่ เพื่อใช้ค่าที่เปลี่ยน โดยข้อมูลแอปยังอยู่ครบ หาก API ขึ้น Network Error ให้ตรวจว่า Android เชื่อม AndroidWifi และแปลงชื่อโดเมน API กับ Firestore ได้

หากเครื่องอื่นยังไม่มี AVD นี้ ให้สร้างชื่อ Large_Desktop ใน Android Studio Device Manager โดยใช้ hardware profile แบบ desktop ขนาด 1920×1080, 160 dpi และเลือก system image ที่ตรงกับ CPU ของเครื่อง หากต้องการใช้ AVD อื่นที่มีอยู่แล้ว:

POS_TERMINAL_AVD=My_Desktop yarn terminal:android:8082:emu

helper จะ build เฉพาะ CPU architecture ของ emulator ที่ใช้อยู่เพื่อลดเวลารอ ส่ง option ของ React Native เพิ่มได้ เช่น --no-packager โดย helper จะเลือก device และ Metro port 8082 ให้ บน macOS หากไม่ได้ตั้ง JAVA_HOME และเรียก java ไม่ได้ helper จะใช้ JDK ที่มาพร้อม Android Studio

เมื่อโปรเจกต์อื่นใช้พอร์ต 8081/8082 อยู่​

พอร์ตเริ่มต้นยังคงเดิม หากรันพร้อมโปรเจกต์ React Native อื่น เช่น SSK Smart City ให้ใช้คำสั่งพอร์ตทางเลือก โดยเปิดคำสั่ง Metro และ Android ของแต่ละแอปจากคนละ terminal:

# Mobile: Metro และ Android ต้องใช้ 8091 ตรงกัน
yarn mobile:start:8091
yarn mobile:android:8091

# POS Terminal: Metro และ Android ต้องใช้ 8092 ตรงกัน
yarn terminal:start:8092
yarn terminal:android:8092
# หรือใช้ emulator-5554:
yarn terminal:android:8092:emu

คำสั่ง Android ทางเลือกเลือกเครื่องเดียวกับคำสั่ง 8081/8082 หากใช้เครื่องอื่น ให้รัน ANDROID_SERIAL=<serial> yarn mobile:android --device <serial> --port 8091 หรือ ANDROID_SERIAL=<serial> yarn terminal:android --device <serial> --port 8092 คำสั่ง Android จะสร้าง ADB reverse forwarding ให้ตรงกับพอร์ตที่ระบุ

ตรวจว่า Metro ที่ใช้พอร์ตอยู่เป็นของโปรเจกต์ใดก่อนใช้งาน:

lsof -nP -iTCP:8081 -iTCP:8082 -sTCP:LISTEN
curl -i http://127.0.0.1:8081/status
curl -g -i 'http://[::1]:8081/status'

header X-React-Native-Project-Root ต้องชี้ไปยังแอปที่ต้องการ IPv4 และ IPv6 อาจเชื่อมต่อ Metro คนละโปรเซสแม้ใช้เลขพอร์ตเดียวกัน จึงดูเพียง packager-status:running ไม่ได้ว่าเครื่อง Android จะได้รับ bundle จากโปรเจกต์ใด หากที่อยู่ใดเป็นโปรเจกต์อื่น ให้เลือกพอร์ตทางเลือก

รัน Mobile และ Terminal บน Android 2 เครื่อง​

เมื่อเสียบ Android พร้อมกัน 2 เครื่อง ให้ระบุเครื่องเป้าหมายของแต่ละคำสั่งด้วย --device และ ANDROID_SERIAL

ดู serial ของเครื่องที่ต่ออยู่ก่อน:

adb devices

ตัวอย่าง:

List of devices attached
R58M123456A device
ZY22ABCDE9 device

รัน Mobile POS บนเครื่องหนึ่ง และ POS Terminal บนอีกเครื่องจากคนละ terminal:

ANDROID_SERIAL=R58M123456A yarn mobile:android --device R58M123456A
ANDROID_SERIAL=ZY22ABCDE9 yarn terminal:android --device ZY22ABCDE9

คำสั่งเดียวกันเมื่อเรียกผ่าน workspace โดยตรง:

yarn workspace @bsr/mobile android --device R58M123456A
yarn workspace @bsr/pos-terminal android --device ZY22ABCDE9

--device กำหนดเครื่องเป้าหมายให้ขั้นตอนเปิดแอปและส่งต่อ port ของ React Native ด้วย จึงไม่ควรใช้ ANDROID_SERIAL เพียงอย่างเดียวเมื่อเชื่อมต่อหลายเครื่อง script แยก Mobile ไว้ที่ 8081 และ Terminal ที่ 8082 อยู่แล้ว หากต้องสร้าง port forwarding ใหม่ด้วยตัวเอง ให้รัน:

adb -s R58M123456A reverse tcp:8081 tcp:8081
adb -s ZY22ABCDE9 reverse tcp:8082 tcp:8082

รันจากใน mobile app เมื่อจำเป็นต้องใช้ Gradle โดยตรง:

cd apps/mobile/android
./gradlew clean assembleRelease
./gradlew clean bundleRelease

ติดตั้ง Mobile release APK ที่ build ไว้แล้วจาก root ของ repository โดยระบุ เครื่องให้ชัดเจน (แทน serial ตัวอย่างด้วยค่าจาก adb devices):

adb -s R58M123456A install -r apps/mobile/android/app/build/outputs/apk/release/app-release.apk

สำหรับ Terminal ให้ใช้ serial ของเครื่อง Terminal และเปลี่ยน path เป็น apps/pos-terminal/ โดยใช้ตำแหน่ง output เดียวกัน

เครื่องมือที่ต้องมีในเครื่อง​

  • Node.js ที่รองรับ >=22.11.0
  • Yarn Classic 1.22.x
  • Java และ JAVA_HOME ที่ชี้ไป JDK ที่ถูกต้อง
  • Android SDK ที่ Gradle เข้าถึงได้
  • Android build-tools และ platform SDK ตามที่โปรเจกต์ต้องใช้
  • Android NDK version ตามที่ Gradle คาดหวัง
  • adb อยู่ใน PATH

ค่า signing สำหรับ release​

Android app อ่านค่า release signing จาก Gradle properties:

  • MYAPP_UPLOAD_STORE_FILE
  • MYAPP_UPLOAD_STORE_PASSWORD
  • MYAPP_UPLOAD_KEY_ALIAS
  • MYAPP_UPLOAD_KEY_PASSWORD

ให้เช็กว่าค่าเหล่านี้มาจากที่ไหนในแต่ละเครื่อง:

  • ~/.gradle/gradle.properties
  • project gradle.properties
  • secret injection ของ CI/CD

ปัญหา APK signature ไม่ตรงตอน install​

ถ้าการติดตั้ง APK ล้มเหลวด้วยข้อความ:

INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package th.co.boonsiri.pos signatures do not match newer version

แปลว่า device มี th.co.boonsiri.pos ติดตั้งอยู่แล้ว แต่ถูก sign ด้วย key คนละตัวกับ APK ใหม่ Android จึงไม่ยอม update ทับ

ถ้าต้องเก็บข้อมูลเดิม ให้ใช้ build ที่ลงนามด้วย key เดิมหรือทดสอบบน emulator แยกต่างหาก ถอนแอปได้เมื่อได้รับอนุญาตให้ลบข้อมูลในเครื่องแล้วเท่านั้น:

adb -s R58M123456A uninstall th.co.boonsiri.pos
adb -s R58M123456A install -r apps/mobile/android/app/build/outputs/apk/release/app-release.apk

Terminal ใช้ package th.co.boonsiri.posterminal ให้ระบุ serial และ APK ของ Terminal โดยตรง การถอนแอปจะลบข้อมูล local และต้องเข้าสู่ระบบใหม่

R8 สำหรับ Release​

Release ของ Mobile และ Terminal เปิด minifyEnabled และ shrinkResources โดยใช้ proguard-android-optimize.txt ร่วมกับ proguard-rules.pro ของแต่ละแอป เพื่อแก้รายงาน DEX optimization ต่ำ / obfuscation 1% ของ Mobile 0.5.24 ใน Play Console R8 ปรับโค้ด Java/Kotlin ใน DEX การตั้งค่านี้ไม่ได้ทดแทน JavaScript bundling หรือการตรวจ native library สำหรับ memory page ขนาด 16 KB

Keep rules ของ Time4J และ date-picker​

ทั้งสองแอปเก็บ rules เฉพาะส่วนดังนี้:

-keep public class net.time4j.android.ApplicationStarter
-keep public class net.time4j.PrettyTime
-keep class net.time4j.android.spi.AndroidResourceLoader {
public <init>();
}

ระหว่างทดสอบ Release พบแอปปิดตอนเริ่มต้น พร้อมข้อความ react-native-date-picker is not installed correctly สาเหตุจริงคือ AndroidResourceLoader ไม่มี constructor แบบไม่รับ argument เพราะ Time4J สร้าง loader ผ่าน reflection และ consumer rule ที่เก็บเฉพาะ class ยังไม่พอ สำหรับ R8 full mode ใน build นี้ จึงต้องเก็บ constructor โดยตรง ไม่แก้ด้วยการซ่อน error หรือปิด R8

Build และตรวจสอบ​

จาก root ของ repository ให้รัน dependency patches แล้วเลือก build แอป:

node scripts/patch-react-native-dependencies.js
cd apps/pos-terminal/android
./gradlew :app:bundleRelease :app:assembleRelease

Mobile ใช้ apps/mobile/android ไฟล์ AAB และ APK ที่ลงนามแล้วอยู่ที่ app/build/outputs/bundle/release/app-release.aab และ app/build/outputs/apk/release/app-release.apk ภายใต้ Android directory ของแอปนั้น เก็บ app/build/outputs/mapping/release/mapping.txt คู่กับแต่ละ Release สำหรับแปลง crash stack trace กลับเป็นชื่อโค้ดเดิม

ใน AAB ต้องมี BUNDLE-METADATA/com.android.tools/r8.json และ BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map ตรวจ options และ stats ใน r8.json โดย coverage ที่เปิดใช้งานคือ 100 - no*Percentage ค่า coverage เป็นคนละค่ากับเปอร์เซ็นต์ขนาด DEX ที่ลดลงจริง

AAB ติดตั้งด้วย adb install โดยตรงไม่ได้ ให้ใช้ bundletool ของ Google สร้างชุด APK ที่ลงนามจาก AAB นั้นด้วย build-apks --connected-device --device-id=... แล้วติดตั้งด้วย install-apks --apks=... --device-id=... ระบุ release keystore ของโปรเจกต์และอ่านรหัสผ่านจากไฟล์ส่วนตัว ไม่ใส่รหัสผ่านใน command arguments หรือ log หากลายเซ็นไม่ตรง ให้ทำตามขั้นตอนด้านบน

ผลตรวจวันที่ 2026-09-28​

ค่าจากไฟล์ build ในเครื่องMobileTerminal
DEX ของ AAB เดิมในเครื่อง (bytes)32,714,31632,905,052
DEX ของ AAB หลัง optimize (bytes)6,208,1926,299,520
Optimization coverage80.64%80.66%
Obfuscation coverage81.18%81.19%
Shrinking coverage81.21%81.22%

ทั้งสองแอป build Release AAB/APK ที่ลงนามด้วย R8 9.2.14 ผ่าน release lint และการตรวจลายเซ็น APK พร้อม R8 metadata และ mapping ครบ ตัวเลขนี้เป็นผลจาก ไฟล์ในเครื่อง ยังไม่ใช่ผลประเมินหลังอัปโหลดเข้า Play Console

  • Mobile 525 / 0.5.25: ทดสอบเปิดแอปใหม่สองรอบ แสดงเวอร์ชันจาก native ตรวจกรอก login ไม่ครบ และสลับธีมบน Android 17 emulator ที่ใช้ page 16,384 bytes ต่อมาแปลง AAB 526 / 0.5.26 แล้วติดตั้งบนมือถือ ตรวจหน้า login และเวอร์ชันผ่าน ถอนแอปเดิมที่ลายเซ็นไม่ตรงหลังได้รับอนุญาตให้ลบข้อมูลแล้ว
  • Terminal 423 / 0.4.23: ทดสอบ Release เปิดถึงหน้าเลือกสาขา และแสดงโฆษณา ด้วย native Customer Display บนจอที่สองจำลอง โดยใช้ page 16,384 bytes เช่นกัน ไม่พบ ReactNativeJS/AndroidRuntime error ใน process สุดท้ายที่ตรวจ
  • หลัง push งาน Terminal รัน yarn terminal:android:8082 เพื่อ build และติดตั้ง Debug บน C9D และเชื่อม Metro ที่เปิดอยู่บน port 8082 การติดตั้ง Debug เป็นคนละการตรวจจาก R8 Release ซึ่งใช้ JavaScript ที่ bundle มาโดยไม่ต้องใช้ Metro
  • ยังต้องตรวจงานขายหลังเข้าสู่ระบบ การกด date-picker สแกน พิมพ์ การแจ้งเตือน ธุรกรรมครบทั้งสองจอ และผลประเมินบน Play Console
  • ยังมี warning ที่ไม่หยุด build จาก play-services-auth:21.5.0 (stack maps) และ Time4J (ไม่ใช้ -useuniqueclassmembernames) ให้ตรวจซ้ำเมื่ออัปเดต dependency

อ้างอิง implementation: Mobile 3245dc66, เพิ่มเวอร์ชัน 25b2fa23, merge เข้า uat e343b482 และ Terminal บน uat 26f222ae

โน้ตการอัปเกรด React Native core​

Android baseline ปัจจุบันของทั้งสองแอปคือ:

  • React Native 0.87.1 โดยเปิด new architecture และ Hermes
  • Build Tools และ compile SDK 37; target SDK 36; minimum SDK 24
  • NDK 28.2.13676358, Kotlin 2.2.10 และ Gradle 9.4.1
  • Node >=22.11.0; การทดสอบบนเครื่องจริงรอบนี้ใช้ Node 26.7.0 ผ่าน nvm

เมื่ออัปเกรด React Native core ให้ใช้ React Native Upgrade Helper เป็น baseline ของ template diff เช่น:

https://react-native-community.github.io/upgrade-helper/?from=0.85.2&to=0.87.1

ห้าม copy template ทับ repo นี้ตรง ๆ เพราะ mobile app มี native configuration เฉพาะโปรเจกต์ที่ต้องรักษาไว้ เช่น monorepo paths ใน android/app/build.gradle, explicit Node/Hermes paths, font linking ของ react-native-vector-icons, Firebase Google Services, release signing, app versioning และ Gradle task workaround ของ Reanimated/Worklets

React Native 0.87 ใช้ Android Gradle Plugin 9 ให้คง android.builtInKotlin=false และ android.newDsl=false ไว้ จนกว่า native dependencies ที่เหลือจะเลิกใช้ Kotlin และ variant API รุ่นเก่า