shouldShowRequestPermissionRationale คือเมธอดของ Android API ที่บอกนัก开发ว่า จำเป็นต้องแสดงคำอธิบายให้ผู้ใช้ดูหรือไม่ก่อนที่จะขอสิทธิ์อันตราย ตาม Android Developer Reference, 2024 เมธอด จะคืนค่า true หากผู้ใช้เคยปฏิเสธคำขอ แต่ไม่ได้ตั้งค่าแฟล็ก Never Ask Again นี่เป็นเครื่องมือสำคัญสำหรับสร้าง UX ที่สุภาพเมื่อทำงานกับสิทธิ์แบบรันไทม์
ประเด็นสำคัญ
shouldShowRequestPermissionRationale คือเมธอดของคลาส Activity และ Fragment ใน Android ที่เข้าถึงได้ผ่าน ActivityCompat เพื่อความเข้ากันได้ เมธอดรับชื่อสิทธิ์และคืนค่า Boolean ที่บ่งชี้ว่าควรแสดงคำอธิบายเพิ่มเติมให้ผู้ใช้ก่อนขออีกครั้งหรือไม่ เมธอดนี้ปรากฏใน Android 6.0 Marshmallow พร้อมกับโมเดลสิทธิ์แบบรันไทม์
กลไกคำอธิบายสร้างขึ้นจากการติดตามประวัติการโต้ตอบของผู้ใช้กับกล่องโต้ตอบสิทธิ์ ระบบจะจดจำว่าผู้ใช้ปฏิเสธคำขอมาก่อนหรือไม่ หากการปฏิเสธเกิดขึ้นโดยไม่ได้ตั้งค่าแฟล็ก Never Ask Again shouldShowRequestPermissionRationale จะคืนค่า true นี่เป็นสัญญาณไปยังนักพัฒนา: ผู้ใช้ไม่เข้าใจว่าทำไมจึงจำเป็นต้องใช้สิทธิ์ และต้องมีคำอธิบายเพิ่มเติม ตาม แนวทางการออกแบบ Material Design ของ Google การแสดงกล่องโต้ตอบคำอธิบายหลังจากการปฏิเสธครั้งแรกจะเพิ่มความน่าจะเป็นในการให้สิทธิ์อีกครั้งถึง 35 เปอร์เซ็นต์
สิ่งสำคัญคือต้องเข้าใจความหมายของค่าที่คืนมา: true หมายถึงการแสดงกล่องโต้ตอบนั้นสมเหตุสมผล false หมายถึงกล่องโต้ตอบไม่จำเป็น (สิทธิ์ถูกให้แล้วหรือไม่เคยถูกขอ) หรือไร้ประโยชน์ (Never Ask Again ทำงานอยู่) เมธอดนี้ไม่ได้รับประกันว่ากล่องโต้ตอบจะถูกแสดง — มันให้เพียงคำแนะนำ นัก開発เป็นผู้ตัดสินใจว่าจะแสดง UI ใดเป็นการตอบสนอง
shouldShowRequestPermissionRationale ถูกนำมาใช้ใน API Level 23 พร้อมกับกลุ่มเมธอดสำหรับสิทธิ์แบบรันไทม์ ก่อน Android 6.0 สิทธิ์ทั้งหมดถูกขอขณะติดตั้ง และไม่จำเป็นต้องมีกลไกคำอธิบาย — ผู้ใช้ยอมรับหรือปฏิเสธรายการทั้งหมดในครั้งเดียว โมเดลรันไทม์ ทำให้ผู้ใช้สามารถปฏิเสธคำขอโดยไม่เข้าใจบริบทได้ และนี่คือเหตุผลที่จำเป็นต้องมีคำอธิบาย
ตรรกะของเมธอด ทำงานดังนี้ ในการเรียก requestPermissions ครั้งแรกสำหรับสิทธิ์เฉพาะ shouldShowRequestPermissionRationale จะคืนค่า false — ผู้ใช้ยังไม่เคยเจอกล่องโต้ตอบ หากผู้ใช้ปฏิเสธคำขอ (กดปฏิเสธ) เมธอดจะเริ่มคืนค่า true หลังจากการปฏิเสธซ้ำด้วยแฟล็ก Never Ask Again เมธอดจะคืนค่า false
ตารางสถานะทั้งหมด:
| สถานะ | shouldShowRationale | checkSelfPermission | การดำเนินการของนักพัฒนา |
|---|---|---|---|
| ไม่เคยขอ | false | DENIED | แสดงกล่องโต้ตอบระบบ |
| ให้แล้ว | false | GRANTED | ดำเนินการฟังก์ชัน |
| ปฏิเสธครั้งแรก | true | DENIED | แสดงคำอธิบาย จากนั้นกล่องโต้ตอบระบบ |
| Never Ask Again | false | DENIED | นำทางไปที่การตั้งค่า |
การรวมกันของ shouldShowRequestPermissionRationale = false และ checkSelfPermission = DENIED เป็นกรณีที่จัดการยากที่สุด หมายความว่าสิทธิ์ไม่เคยถูกขอหรือตั้งค่า Never Ask Again นักพัฒนาจำเป็นต้องแยกแยะสองสถานะนี้ วิธีเดียวคือเก็บแฟล็ก isFirstRequest ใน SharedPreferences หรือใช้ SavedStateHandle ตั้งค่าแฟล็กในการขอครั้งแรก และหาก shouldShowRationale คืนค่า false ในขณะที่แฟล็กเป็น true อยู่แล้ว — นั่นหมายถึง Never Ask Again
shouldShowRequestPermissionRationale จะถูกรีเซ็ตหากผู้ใช้ถอนการติดตั้งและติดตั้งแอปใหม่ ล้างข้อมูลแอป หรือรีเซ็ตการตั้งค่าสิทธิ์ หลังจากติดตั้งใหม่ เมธอดจะคืนค่า false สำหรับการขอครั้งแรกอีกครั้ง การอัปเดตระบบ และการเปลี่ยนเวอร์ชัน Android จะไม่รีเซ็ตประวัติ — ประวัติจะถูกเก็บไว้ในข้อมูลแอป
การนำคำอธิบายไปใช้อย่างถูกต้อง ประกอบด้วยสามองค์ประกอบ: การตรวจสอบ shouldShowRequestPermissionRationale หลังจากการปฏิเสธ การแสดงกล่องโต้ตอบที่กำหนดเองพร้อมคำอธิบาย และการเรียก requestPermissions อีกครั้งหลังจากผู้ใช้ตอบรับในเชิงบวก กล่องโต้ตอบควรสั้น เฉพาะเจาะจง และอธิบายว่าเหตุใดแอปจึงจำเป็นต้องใช้สิทธิ์นี้โดยเฉพาะ
private fun requestLocationWithRationale() {
val permission = Manifest.permission.ACCESS_FINE_LOCATION
when {
ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED -> {
startLocationTracking()
}
ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
) -> {
showRationaleDialog(permission)
}
else -> {
requestPermissionLauncher.launch(permission)
}
}
}
private fun showRationaleDialog(
permission: String
) {
AlertDialog.Builder(this)
.setTitle("เหตุใดจึงต้องการการเข้าถึงตำแหน่ง")
.setMessage(
"แอปใช้ตำแหน่งเพื่อทำเครื่องหมาย" +
" สถานที่บนแผนที่ หากไม่มีสิทธิ์นี้" +
" ฟังก์ชันจะไม่ทำงาน"
)
.setPositiveButton("อนุญาต") { _, _ ->
requestPermissionLauncher.launch(permission)
}
.setNegativeButton("ยกเลิก", null)
.show()
}
แนวปฏิบัติที่ดีที่สุดของ Material Design แนะนำให้ใช้แผ่นด้านล่างหรือแบนเนอร์ในบรรทัดแทนกล่องโต้ตอบแบบโมดอลสำหรับคำอธิบาย แผ่นด้านล่างรบกวนน้อยกว่าและให้บริบทแก่ผู้ใช้ องค์ประกอบในบรรทัด บนหน้าจอ (เช่น การ์ดที่มีคำอธิบายและปุ่มอนุญาต) แสดงว่าฟังก์ชันไม่สามารถใช้งานได้หากไม่มีสิทธิ์ แต่ไม่ได้ปิดกั้นส่วนที่เหลือของอินเทอร์เฟซ
ข้อความคำอธิบายควรได้รับการแปลและปรับให้เข้ากับฟังก์ชันเฉพาะ อย่าใช้วลีทั่วไปเช่น “จำเป็นสำหรับการทำงานของแอป” ระบุอย่างเป็นรูปธรรม: “เพื่อแสดงสภาพอากาศใกล้คุณ” หรือ “เพื่อบันทึกรูปภาพในแกลเลอรี” คำอธิบายเฉพาะ เพิ่มความน่าจะเป็นในการให้สิทธิ์ถึง 50 เปอร์เซ็นต์ตามการวิจัย UX ของ Google
ความแตกต่างระหว่าง shouldShowRequestPermissionRationale = true (การปฏิเสธครั้งแรก) และ false กับ DENIED (Never Ask Again) เป็นจุดสำคัญในการจัดการสิทธิ์ ในกรณีแรก ผู้ใช้ลังเล และคำอธิบายเพิ่มเติมสามารถโน้มน้าวให้เขาให้การเข้าถึงได้ ในกรณีที่สอง ผู้ใช้ตัดสินใจครั้งสุดท้ายแล้ว และการทำซ้ำกล่องโต้ตอบระบบจะทำให้เกิดความหงุดหงิดเท่านั้น
อัลกอริทึมการจัดการหลังจากการปฏิเสธควรเป็นดังนี้:
สิ่งสำคัญคืออย่าสับสนลำดับ: ตรวจสอบ shouldShowRequestPermissionRationale ก่อน ไม่ใช่ checkSelfPermission checkSelfPermission จะยังคงคืนค่า DENIED ในทั้งสองกรณี มีเพียง shouldShowRequestPermissionRationale เท่านั้นที่แยกการปฏิเสธครั้งแรกออกจาก Never Ask Again ใช้ SavedStateHandle หรือ SharedPreferences เพื่อเก็บแฟล็ก “มีการขอครั้งแรกแล้ว” — นี่เป็นวิธีเดียวที่เชื่อถือได้ในการแยก “ไม่เคยขอ” ออกจาก “ถูกบล็อก”
แสดงคำอธิบายเพียงครั้งเดียว หากผู้ใช้ปฏิเสธคำขออีกครั้งหลังจากเห็นคำอธิบาย อย่าแสดงคำอธิบายอีก ให้ตรงไปที่การเสนอให้เปิดการตั้งค่า การแสดงคำอธิบายซ้ำๆ ถูกมองว่าเป็นการรบกวนและลดคะแนนแอป สถานการณ์ที่เหมาะสมที่สุด: คำขอ — ปฏิเสธ — คำอธิบาย — ขออีกครั้ง — ปฏิเสธ — การตั้งค่า
อย่าแสดงคำอธิบายก่อนการขอครั้งแรก นักพัฒนาบางคนแสดงคำอธิบายอย่างผิดพลาดก่อนกล่องโต้ตอบแรก โดยให้เหตุผลว่า “ผู้ใช้ต้องเข้าใจ” สิ่งนี้ทำให้ UX แย่ลง: ผู้ใช้เห็นกล่องโต้ตอบสองกล่องติดต่อกันแทนที่จะเป็นหนึ่งเดียว Google แนะนำให้แสดงกล่องโต้ตอบระบบทันที และแสดงคำอธิบายหลังจากปฏิเสธเท่านั้น
ใช้คำอธิบายตามบริบทที่เชื่อมโยงกับช่วงเวลาที่จำเป็นต้องใช้ฟังก์ชันจริงๆ อย่าขอสิทธิ์ทั้งหมดเมื่อเปิดแอป — นี่มีอัตราการให้สิทธิ์ต่ำที่สุด ขอกล้องเมื่อผู้ใช้กดปุ่ม “ถ่ายรูป” และขอตำแหน่งเมื่อผู้ใช้เปิดแผนที่ การขอตามบริบท ร่วมกับคำอธิบายเพิ่มอัตราการให้สิทธิ์เป็น 80 เปอร์เซ็นต์ เทียบกับ 30 เปอร์เซ็นต์เมื่อขอขณะเริ่มต้นแอป
การทดสอบ shouldShowRequestPermissionRationale จำเป็นต้องตรวจสอบสี่สถานะในตาราง: ไม่เคยขอ ให้แล้ว ปฏิเสธ Never Ask Again ในการทดสอบหน่วย ใช้ FakePermissionHandler ที่มีพฤติกรรม shouldShowRationale ที่ปรับแต่งได้ ในการทดสอบเครื่องมือ ใช้ UiAutomator หรือ Espresso พร้อมการจำลองการตอบสนองของกล่องโต้ตอบ
class RationaleViewModelTest {
private val handler = FakePermissionHandler()
private val viewModel = PermissionsViewModel(handler)
fun testFirstDenial_shouldShowRationale() {
handler.shouldShowRationale = true
handler.cameraResult =
PermissionResult.DENIED(true)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.Denied(true),
viewModel.uiState.value
)
}
fun testNeverAskAgain_redirectToSettings() {
handler.shouldShowRationale = false
handler.cameraResult =
PermissionResult.DENIED(false)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.RedirectToSettings,
viewModel.uiState.value
)
}
}
สถานการณ์หลักสำหรับการทดสอบเครื่องมือคือการตรวจสอบว่ากล่องโต้ตอบคำอธิบายปรากฏขึ้นจริงหลังจากการปฏิเสธครั้งแรก ใช้ Espresso พร้อม idling resources เพื่อรอกล่องโต้ตอบระบบ จากนั้นกดปฏิเสธ ตรวจสอบการปรากฏของกล่องโต้ตอบคำอธิบายที่กำหนดเอง และกดอนุญาต — ตรวจสอบการให้สิทธิ์ UIAutomator ช่วยให้โต้ตอบกับกล่องโต้ตอบระบบด้วยข้อความปุ่ม ทำให้การทดสอบมีเสถียรภาพมากขึ้น
นอกจากนี้ควรทดสอบสถานการณ์การปฏิเสธภายในกล่องโต้ตอบคำอธิบาย หากผู้ใช้กดปฏิเสธในคำอธิบายที่กำหนดเอง shouldShowRequestPermissionRationale ควรคืนค่า true อีกครั้ง เนื่องจาก Never Ask Again ยังไม่ได้เปิดใช้งาน แนวปฏิบัติที่ดีที่สุดคือนำทางไปที่การตั้งค่าหลังจากการปฏิเสธสองครั้งติดต่อกัน เพื่อหลีกเลี่ยงการรบกวนผู้ใช้ด้วยคำอธิบายซ้ำๆ และลดคะแนนแอป
คำถามที่พบบ่อย
true — หากคำขอถูกปฏิเสธก่อนหน้านี้และไม่ได้ตั้งค่า Never Ask Again false — หากสิทธิ์ไม่เคยถูกขอ ถูกให้แล้ว หรือถูกบล็อกถาวร การรวมกันของ false + DENIED จำเป็นต้องตรวจสอบผ่าน แฟล็กเพิ่มเติม
แสดงคำอธิบายหลังจากผู้ใช้ปฏิเสธครั้งแรกเท่านั้น เมื่อ shouldShowRequestPermissionRationale คืนค่า true ก่อนการขอครั้งแรก ไม่จำเป็นต้องมีคำอธิบาย — ทำให้UXแย่ลงและสร้างกล่องโต้ตอบที่ไม่จำเป็น
เก็บแฟล็ก isFirstRequest ใน SharedPreferences หรือ SavedStateHandle หาก shouldShowRationale = false, checkSelfPermission = DENIED และแฟล็กเป็น true — แสดงว่า Never Ask Again ทำงานอยู่ หากแฟล็กเป็น false — นี่คือคำขอแรก
แสดงกล่องโต้ตอบพร้อมปุ่ม “เปิดการตั้งค่า” ที่นำผู้ใช้ไปที่ ACTION_APPLICATION_DETAILS_SETTINGS อย่าเรียก requestPermissions อีก — กล่องโต้ตอบจะไม่ปรากฏ และผลลัพธ์จะกลับมาเป็น DENIED โดยไม่มีข้อความ
ในการทดสอบหน่วย ใช้ FakePermissionHandler ที่มีฟิลด์ shouldShowRationale ที่ปรับแต่งได้ ในการทดสอบเครื่องมือ ใช้ Espresso หรือ UIAutomator พร้อมการจำลองกล่องโต้ตอบระบบ ตรวจสอบทั้ง 4 สถานะจากตาราง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม