Izin Bluetooth Flutter di Android 12+ untuk Printer

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.
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.
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.
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.
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.

Ringkasan Utama
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.
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.
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.

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.
Ketika toko melapor printer tidak ditemukan, inilah penyebabnya berdasarkan urutan saya memeriksanya.
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.

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.
Sebelum merilis versi apa pun yang menyentuh Bluetooth, saya menjalankan empat pemeriksaan ini di perangkat fisik.
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.