Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.
01Izin Bluetooth apa yang dibutuhkan aplikasi printer di Android 12?
BLUETOOTH_CONNECT selalu diperlukan karena izin itulah yang memungkinkan komunikasi dengan perangkat yang sudah dipasangkan. BLUETOOTH_SCAN hanya perlu kalau aplikasi menemukan perangkat, dan sebaiknya membawa flag neverForLocation. BLUETOOTH_ADVERTISE tidak dibutuhkan aplikasi printer, dan ACCESS_FINE_LOCATION sebaiknya dibatasi pada API 30.
02Apa fungsi flag neverForLocation?
Ia menyatakan bahwa aplikasi Anda tidak menurunkan lokasi fisik dari hasil pemindaian Bluetooth, sehingga BLUETOOTH_SCAN tidak masuk ke keranjang izin lokasi. Pengguna kemudian melihat dialog Perangkat di sekitar alih-alih dialog lokasi, dan Play Console tidak meminta Anda menjelaskan fitur lokasi yang memang tidak dimiliki aplikasi pencetakan Anda.
03Kenapa aplikasi Flutter saya tiba-tiba tidak menemukan printer di tablet baru?
Hampir selalu karena aplikasi menarget API 31 ke atas sementara hanya mendeklarasikan izin BLUETOOTH lama. Pemindaian lalu tidak mengembalikan apa pun dan penyambungan melempar SecurityException, tanpa dialog izin yang terlihat di mana pun. Tambahkan izin runtime dan batasi izin lama pada API 30.
04Kapan aplikasi sebaiknya meminta izin Bluetooth?
Tepat saat pengguna menekan sesuatu yang jelas berkaitan dengan printer, misalnya tombol sambungkan printer di layar pengaturan — jangan pernah saat aplikasi diluncurkan. Dialog tanpa sebab yang terlihat adalah sumber terbesar penolakan permanen, dan begitu sebuah izin ditolak permanen, sistem tidak akan menampilkan dialognya lagi.
05Apakah model izinnya berbeda di iOS?
Sepenuhnya berbeda. iOS tidak punya pemisahan setara karena ia memang tidak membuka Bluetooth Classic serial port profile untuk aplikasi pihak ketiga sama sekali; aksesori menjangkaunya lewat sertifikasi MFi dan External Accessory framework, sisanya memakai Bluetooth Low Energy dengan usage description di Info.plist. Itu menjadikan pencetakan di iOS sebagai keputusan produk, bukan keputusan izin.
Izin Bluetooth Flutter di Android 12+ untuk Printer
BLUETOOTH_SCAN, BLUETOOTH_CONNECT, dan neverForLocation dijelaskan untuk aplikasi POS Flutter, plus manifest dan permintaan runtime yang lolos review Play.
Android 12 mengganti izin BLUETOOTH lama dengan BLUETOOTH_SCAN dan BLUETOOTH_CONNECT berbasis runtime, yang tampil bagi pengguna sebagai Perangkat di sekitar. Aplikasi printer struk sebaiknya membatasi izin lama dan izin lokasi pada API 30, mendeklarasikan BLUETOOTH_SCAN dengan neverForLocation, dan meminta izin tepat saat kasir menyambungkan printer.
POS Flutter yang tadinya mencetak lancar di tablet lama tiba-tiba tidak menemukan printer apa pun di tablet baru, melempar SecurityException di dalam stack trace yang tak seorang pun baca, dan toko menyimpulkan aplikasinya rusak. Sembilan dari sepuluh kali penyebabnya adalah pemisahan izin Android 12, dan perbaikannya dua puluh baris manifest ditambah satu permintaan yang waktunya tepat.
Ini seluruh modelnya dalam satu tempat — apa yang berubah, apa yang benar-benar dibutuhkan aplikasi printer, dan kenapa meminta izin lokasi akan memakan waktu Anda di review Play Console.
Apa yang berubah di Android 12
Sebelum API 31, Bluetooth ditangani izin saat pemasangan ditambah izin lokasi, karena hasil pemindaian bisa mengungkap keberadaan pengguna. Mulai API 31, kemampuannya dipecah menjadi izin runtime yang memetakan apa yang benar-benar dilakukan aplikasi.
Izin
Kapan dibutuhkan
Catatan untuk aplikasi printer
BLUETOOTH_CONNECT
Berkomunikasi dengan perangkat yang sudah dipasangkan
Selalu diperlukan — inilah izin yang memungkinkan Anda membuka koneksi dan mencetak
BLUETOOTH_SCAN
Menemukan perangkat di sekitar
Hanya perlu kalau Anda memindai; deklarasikan dengan neverForLocation agar bukan permintaan lokasi
BLUETOOTH_ADVERTISE
Membuat ponsel dapat ditemukan perangkat lain
Tidak dibutuhkan aplikasi printer — jangan dideklarasikan
ACCESS_FINE_LOCATION
Menurunkan lokasi fisik dari hasil pemindaian
Batasi di API 30. Printer struk bukan fitur lokasi dan Play akan meminta Anda menjelaskannya
Perbedaan yang terlihat pengguna sama pentingnya dengan perbedaan teknisnya. Di Android 12 ke atas, dialognya berbunyi Perangkat di sekitar, yang dipahami kasir dalam konteks printer. Alur lama meminta lokasi, dan justru permintaan semacam itulah yang ditolak lalu ditolak permanen.
Manifest yang bekerja di semua versi Android
Anda tetap membutuhkan izin lama untuk perangkat lawas, dan Anda butuh izin itu tidak berlaku di perangkat baru. Untuk itulah maxSdkVersion ada.
<!-- android/app/src/main/AndroidManifest.xml -->
<!-- Legacy permissions: capped at API 30 so Android 12+ ignores them -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- Android 12+ runtime permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<!-- A receipt printer is not a location feature. Cap location at 30 so
Play Console never asks you to justify a location permission you
do not use, and users see "Nearby devices" instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
Dua baris memikul sebagian besar nilainya. Flag neverForLocation pada BLUETOOTH_SCAN memberi tahu Android bahwa hasil pemindaian Anda tidak dipakai menurunkan lokasi, sehingga permintaan itu tidak masuk ke keranjang lokasi. Membatasi ACCESS_FINE_LOCATION pada API 30 berarti perangkat modern sama sekali tidak pernah melihat permintaan lokasi dari aplikasi pencetakan.
Mendeklarasikan ACCESS_FINE_LOCATION tanpa batas adalah kesalahan paling umum di ranah ini sekaligus yang paling mahal. Ia memicu pertanyaan review izin lokasi di Play Console untuk aplikasi yang tidak punya fitur lokasi, dan ia menyodorkan kepada kasir sebuah dialog yang hubungannya dengan pencetakan tidak terlihat sama sekali.
Di sinilah setiap keputusan izin dalam artikel ini sebenarnya diambil: di perangkat seperti ini, oleh kasir tanpa konteks teknis. Itulah sebabnya dialognya harus muncul tepat setelah mereka menekan sambungkan printer, dan sebaiknya berbunyi Perangkat di sekitar alih-alih lokasi. Foto: X-SHLIED / Wikimedia Commons, CC BY-SA 4.0
Meminta izin pada momen yang tepat
Permintaan izin adalah masalah pengalaman pengguna yang menyamar sebagai API. Aturan yang berhasil bagi saya: minta hanya sebagai respons langsung terhadap aksi yang tujuannya jelas-jelas printer, dan tangani penolakan permanen sebagai status tersendiri alih-alih mengulang terus.
import 'package:permission_handler/permission_handler.dart';
/// Ask at the moment the cashier taps "connect printer", never at
/// launch. A permission dialog with no visible cause is the single
/// biggest source of permanent denials.
Future<bool> ensureBluetoothPermissions() async {
final statuses = await [
Permission.bluetoothConnect,
Permission.bluetoothScan,
].request();
final granted = statuses.values.every((s) => s.isGranted);
if (granted) return true;
// Permanently denied cannot be re-prompted: the only path forward is
// the system settings screen, so say that plainly instead of looping.
if (statuses.values.any((s) => s.isPermanentlyDenied)) {
await openAppSettings();
}
return false;
}
Perhatikan apa yang tidak dilakukan kode itu: ia tidak meminta saat aplikasi diluncurkan, tidak meminta sebelum layar pengaturan printer, dan tidak mengulang permintaan setelah penolakan permanen. Begitu sebuah izin ditolak permanen, sistem tidak akan menampilkan dialognya lagi, jadi satu-satunya respons yang jujur adalah menjelaskan dan menawarkan membuka pengaturan.
Empat kegagalan yang terlihat seperti printer rusak
Ketika toko melapor printer tidak ditemukan, inilah penyebabnya berdasarkan urutan saya memeriksanya.
Aplikasi menarget API 31 ke atas tetapi hanya mendeklarasikan izin lama. Pemindaian tidak mengembalikan apa pun dan penyambungan melempar exception, tanpa dialog terlihat di mana pun.
Izin sudah ditolak sekali saat onboarding, sebelum pengguna tahu aplikasinya untuk apa. Semua yang terjadi setelah itu terlihat seperti kerusakan perangkat keras.
Printer belum dipasangkan di tingkat sistem operasi. Perangkat serial klasik harus di-bonding di pengaturan Android; aplikasi hanya bisa menyambung ke yang sudah ada di sana.
Bluetooth-nya sendiri mati. Periksa status adapter secara eksplisit dan sampaikan itu, jangan melaporkan kegagalan koneksi yang generik.
Keempatnya menghasilkan keluhan yang sama dari toko dan punya perbaikan yang sama sekali berbeda — itulah sebabnya layar pengaturan printer yang melaporkan tiap kondisi secara terpisah menghemat lebih banyak waktu dukungan daripada logika percobaan ulang sebanyak apa pun.
Model izinnya berbeda menurut profil, bukan hanya menurut versi Android: koneksi serial klasik dan pemindaian low energy menempuh jalur berbeda melalui himpunan izin runtime yang sama. Gambar: Jnmasek / Wikimedia Commons, Public domain
iOS adalah pertanyaan yang sama sekali lain
Di iOS tidak ada pemisahan izin yang setara, karena kemampuan setaranya memang tidak ada. Apple tidak membuka Bluetooth Classic serial port profile untuk aplikasi pihak ketiga; aksesori menjangkaunya lewat sertifikasi MFi dan External Accessory framework, dan sisanya lewat low energy dengan usage description di Info.plist.
Bagi sebuah produk POS, konsekuensi praktisnya adalah keputusan produk, bukan keputusan kode: entah Anda mensyaratkan printer yang menyediakan antarmuka low energy, atau Anda mendukung Android untuk pencetakan dan memperlakukan iOS sebagai klien lihat-saja. Memutuskannya sejak awal menghindarkan Anda menjanjikan fitur yang tidak akan diberikan platformnya.
Taruh satu baris diagnostik di layar pengaturan printer yang menampilkan empat boolean: Bluetooth aktif, izin diberikan, perangkat terpasang, koneksi terakhir berhasil. Percakapan dukungan menyusut dari dua puluh menit menjadi satu tangkapan layar.
Checklist sebelum rilis
Sebelum merilis versi apa pun yang menyentuh Bluetooth, saya menjalankan empat pemeriksaan ini di perangkat fisik.
Pasang baru di perangkat Android 12 atau lebih baru, tolak izinnya sekali, dan pastikan aplikasi menjelaskan alih-alih crash.
Tolak permanen, lalu pastikan aplikasi menawarkan membuka pengaturan alih-alih gagal diam-diam.
Jalankan di perangkat Android 11 dan pastikan jalur lama masih bisa pairing dan mencetak.
Periksa manifest hasil merge di keluaran build, bukan manifest sumber Anda — sebuah dependensi bisa menambahkan izin yang tidak pernah Anda deklarasikan.
Model izin Android 12 sebenarnya lebih ramah daripada yang digantikannya: aplikasi printer kini bisa meminta persis apa yang dibutuhkan printer, dan pengguna melihat dialog tentang perangkat di sekitar alih-alih tentang lokasi mereka. Mendeklarasikan empat baris yang tepat dan meminta pada momen yang tepat mengubah panggilan dukungan Bluetooth yang paling umum menjadi bukan peristiwa sama sekali.