{"id":540,"date":"2019-02-26T23:25:24","date_gmt":"2019-02-26T16:25:24","guid":{"rendered":"http:\/\/www.randomlog.org\/?p=540"},"modified":"2019-08-30T22:45:16","modified_gmt":"2019-08-30T15:45:16","slug":"android-kotlin-ke-bluetooth-printer","status":"publish","type":"post","link":"https:\/\/randomlog.org\/blog\/android-kotlin-ke-bluetooth-printer","title":{"rendered":"Android Kotlin ke Bluetooth Printer"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Topik ini belum banyak bahasannya. Ane kesulitan sewaktu ingin membuat aplikasi yang perlu melakukan print dengan baik ke printer bluetooth thermal POS\/receipt murah meriah, secara (ketika postingan ini dibuat) belum sampai 1 bulan yang lalu ane membuka developer.android.com dan mendownload Android Studio.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Karena itulah ane ingin membagi pengalaman, semoga bisa membantu seseorang di luar sana, atau semoga ada masukan-masukan yang dapat berguna untuk memperbaiki pemahaman ane atas android dan kotlin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dan juga sekalian nyoba fitur Post baru di WP ini \ud83d\ude00<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Alur UI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pada awalnya ane berniat agar pengguna dapat 100% mengatur printer (termasuk bluetooth discovery &amp; pairing) dari dalam aplikasi, tapi sepertinya target ini terlalu tinggi karena selang 2 hari kepala ane sakit dan ane putuskan untuk menurunkan targetnya, yang penting:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Pengguna bisa lihat textview yang berisi informasi-informasi soal printernya.<\/li><li>Kalau bluetooth aktif dan sudah pernah melakukan print sebelumnya, akan langsung dicoba untuk konek jadi bisa langsung print.<\/li><li>Kalau konek gagal, atau belum pernah print sebelumnya, akan ditampilkan pilihan printer yang ada di daftar pair.<\/li><li>Kalau tidak ada printer di daftar pair, tampilkan informasi (suruh pair).<\/li><\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Jadi, aktivasi bluetooth dan pairing tetap dilakukan di luar, namun si aplikasi harus dapat menangani sisanya dengan baik.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Layout<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yang sedang ane kembangkan adalah sebuah aplikasi POS di mana Transaksi tetap bisa dilakukan tanpa harus melakukan print nota. Ane akan jabarkan seminimal mungkin di sini supaya nggak rumit &amp; fleksibel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Elemen layoutnya:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Switch untuk mengaktifkan fitur Print<\/li><li>ProgressBar<\/li><li>Textview untuk info Print<\/li><li>Tombol untuk melakukan transaksi (dan print)<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?xml version=\"1.0\" encoding=\"utf-8\"?>\n&lt;LinearLayout\n        xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n        xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:context=\".MainActivity\" android:orientation=\"vertical\">\n    &lt;Switch\n            android:text=\"Print\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id\/printSwitch\"\/>\n    &lt;TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id\/printInfo\"\n            tools:text=\"Print info\"\/>\n    &lt;ProgressBar\n            android:layout_gravity=\"center\"\n            android:visibility=\"invisible\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:id=\"@+id\/printLoading\"\/>\n    &lt;Button\n            android:text=\"Button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id\/transactionButton\"\/>\n&lt;\/LinearLayout><\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Activity<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pada dasarnya di Activity:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Kita initialize class BtPrint.kt dengan memberitahukan View-view apa saja yang perlu diutak-atik olehnya. Dari layout di atas:<ul><li>printSwitch: Switch utama untuk mematikan\/menghidupkan fitur Print<\/li><li>printLoading: Progress bar<\/li><li>printInfo: TextView untuk info<\/li><li>transactionButton: Tombol untuk melakukan print.<\/li><\/ul><\/li><li>Meng-initialize class BtPrint juga akan otomatis melakukan test koneksi awal (dan lain-lain untuk persiapan\/memastikan printer terkoneksi). Namun ketika akan melakukan print beneran, kita tetap lakukan test lagi dari Activity untuk memastikan printer masih dalam keadaan tersambung (nggak mati, sedang dipakai, atau keluar jangkauan).<\/li><li>Setelah itu, baru beritahukan apa yang harus di-print.<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>package id.tigaer.gudang.cashier\n\nimport android.support.v7.app.AppCompatActivity\nimport android.os.Bundle\nimport android.widget.Toast\nimport kotlinx.android.synthetic.main.activity_main.*\n\nclass MainActivity : AppCompatActivity() {\n\n\n    \/\/ I'm putting the variable here in case it is needed outside onCreate() later\n\n    private lateinit var btPrint: BtPrint\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n\n        \/\/ Init and pass the Views\n\n        btPrint = BtPrint( printSwitch, printLoading, printInfo, transactionButton )\n\n\n        \/\/ Actual print\n\n        transactionButton.setOnClickListener{\n\n\n            \/\/ We do socket connect here so we can do some handling if something happen with the printer before\n            \/\/ the actual printing.\n\n            btPrint.socketConnect { result ->\n\n                if ( result[\"success\"] == false ){\n\n                    this@MainActivity.runOnUiThread {\n\n                        printInfo.text = result[\"text\"].toString()\n                        printSwitch.isChecked = false\n\n                        Toast.makeText(this, \"OOPS!!!\", Toast.LENGTH_SHORT).show()\n\n                        \/\/ TODO: Pooling?\n\n                    }\n\n                } else {\n\n                    btPrint.doPrint( android.os.Build.MODEL + \"\\n\", true )\n                    btPrint.doPrint( android.os.Build.BRAND + \"\\n\\n\\n\" )\n\n\n                    \/\/ I'll share how I handle printing format for regular receipts next... :)\n\n                }\n\n            }\n\n        }\n\n\n    }\n\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">BtPrint.kt<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Layout &amp; Activity sudah, ini class BtPrint() nya<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package id.tigaer.gudang.cashier\n\n\nimport android.app.Activity\nimport android.bluetooth.BluetoothAdapter\nimport android.bluetooth.BluetoothDevice\nimport android.bluetooth.BluetoothSocket\nimport android.content.Context\nimport android.support.v7.app.AlertDialog\nimport android.view.View\nimport android.widget.Button\nimport android.widget.ProgressBar\nimport android.widget.Switch\nimport android.widget.TextView\nimport java.io.IOException\nimport java.util.*\nimport kotlin.collections.HashMap\nimport kotlin.concurrent.thread\n\n\nclass BtPrint(\n\n    private var printSwitch: Switch,\n    private var printLoading: ProgressBar,\n    private var printInfo: TextView,\n    private var printButton: Button\n\n) {\n\n\n    \/\/ Define the caller context and activity (tips on optimization will be much appreciated :)\n\n    private val context = printSwitch.context\n    private val activity = context as Activity\n    private val sharedPrefs = context.getSharedPreferences(context.packageName + \".META\", Context.MODE_PRIVATE)\n\n\n    \/\/ Other initializer\n\n    private var bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()\n\n    private var printers = ArrayList&lt;BluetoothDevice>()\n\n    private lateinit var printer: BluetoothDevice\n    private lateinit var socket: BluetoothSocket\n\n\n    init {\n\n\n        \/\/ Set Views to initial value.\n\n        preCheckStart()\n\n\n        \/\/ Only proceed if there's a record in sharedPrefs...\n\n        if (sharedPrefs.getString(\"lastPrinter\", \"\") != \"\") {\n\n            preCheck()\n\n        } else {\n\n\n            \/\/ ...to skip (second) auto connection attempt if no printers active...\n\n            printInfo.text = \"Not going to print\"\n            preCheckDone()\n\n        }\n\n\n        \/\/ ...but of course proceed if switched manually.\n\n        printSwitch.setOnClickListener {\n\n            if (printSwitch.isChecked) {\n\n                preCheck()\n\n            } else {\n\n                printInfo.text = \"Not going to print\"\n\n            }\n\n        }\n\n    }\n\n\n    private fun preCheck() {\n\n        preCheckStart()\n\n\n        \/\/ No bluetooth\n\n        if (bluetoothAdapter == null) {\n\n            printInfo.text = \"This thing has no bluetooth\"\n            printSwitch.isChecked = false\n            preCheckDone()\n\n        } else {\n\n\n            \/\/ Bluetooth inactive\n\n            if (!bluetoothAdapter.isEnabled) {\n\n                printInfo.text = \"Bluetooth inactive\"\n                printSwitch.isChecked = false\n                preCheckDone()\n\n            } else {\n\n\n                \/\/ Bluetooth available and active, so refresh printers list.\n\n                refreshPrinters()\n\n                if (printers.size > 0) {\n\n\n                    \/\/ Loop the printers to crosscheck with sharedPrefs, and also to prepare arrays for printer selection\n                    \/\/ dialog if needed.\n\n                    val pNames = Array(printers.size) { \"\" }\n                    val pAddrs = Array(printers.size) { \"\" }\n\n                    var deviceFound = false\n\n                    for (i in 0 until printers.size) {\n\n                        pNames[i] = printers[i].name\n                        pAddrs[i] = printers[i].address \/\/ How to do this \"correctly\" in Kotlin? :D\n\n\n                        \/\/ Printer available in sharedPrefs, attempt connection and break.\n\n                        if (printers[i].address == sharedPrefs.getString(\"lastPrinter\", \"\")) {\n\n                            deviceFound = true\n                            printer = printers[i]\n                            testConnection()\n                            break\n\n                        }\n\n                    }\n\n\n                    \/\/ If it gets here\n\n                    if (!deviceFound) {\n\n\n                        \/\/ Show printer selection dialog\n\n                        val builder = AlertDialog.Builder(context)\n                        builder.setTitle(\"Select printer\")\n                            .setItems(\n                                pNames\n                            ) { _, which ->\n\n\n                                \/\/ On selected, save and rerun preCheck()\n\n                                sharedPrefs.edit().putString(\"lastPrinter\", pAddrs[which]).apply()\n                                preCheck()\n\n                            }\n                        builder.create()\n                        builder.setCancelable(false)\n                        builder.show()\n\n                    }\n\n                } else {\n\n\n                    \/\/ No printers\n\n                    printInfo.text = \"Please pair a printer\"\n                    printSwitch.isChecked = false\n                    preCheckDone()\n\n                }\n\n            }\n\n        }\n\n    }\n\n\n    private fun refreshPrinters() {\n\n\n        \/\/ Clean up first, but I found out that sometimes the device must be reset, and\/or Clear Data in\n        \/\/ Bluetooth Share app, to really refresh the paired devices list, after we Forget\/Add a bluetooth device.\n        \/\/ This probably a device issue (or just me lacking experience).\n\n        printers.clear()\n\n\n        \/\/ Filter the paired devices for printers\n\n        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() \/\/ this is my attempt to \"really refresh\" the list\n\n        val pairedDevices: Set&lt;BluetoothDevice>? = bluetoothAdapter.bondedDevices\n\n        if (pairedDevices != null) {\n\n            for (i in pairedDevices) {\n\n                if (i.bluetoothClass.deviceClass.toString() == \"1664\") { \/\/ Identifier (or something) for printers.\n\n\n                    \/\/ Now we have a list of printers\n\n                    printers.add(i)\n\n                }\n\n            }\n\n        }\n\n    }\n\n\n    private fun testConnection() {\n\n\n        \/\/ Make sure discovery isn't running\n\n        bluetoothAdapter?.cancelDiscovery()\n\n\n        \/\/ Socket connect will freeze the UI, so we're doing this in another thread and get the callback\n\n        printInfo.text = printer.name\n        printInfo.append(\"... \")\n\n        socketConnect { result ->\n\n\n            \/\/ Other threads can't touch UI without runOnUiThread()\n\n            activity.runOnUiThread {\n\n                printInfo.append(result[\"text\"].toString())\n\n\n                \/\/ Connection failed, delete from sharedPrefs\n\n                if (result[\"success\"] == false) {\n\n                    sharedPrefs.edit().putString(\"lastPrinter\", \"\").apply()\n                    printSwitch.isChecked = false\n\n                } else {\n\n                    printSwitch.isChecked = true\n\n                }\n\n                preCheckDone()\n\n            }\n\n\n            \/\/ Done checking. From here we assume the printer is active, nearby, and is ready for real printing.\n            \/\/ We close the socket to allow other devices to use the printer although it might require some more coding\n            \/\/ to imitate some kind of pooling service (no such service in my two test\/cheap printers here).\n\n            socket.close()\n\n        }\n\n    }\n\n\n    fun socketConnect(callback: (HashMap&lt;String, Any>) -> Unit) {\n\n\n        \/\/ Make sure socket is closed\n\n        if (::socket.isInitialized) socket.close()\n\n\n        \/\/ Google for explanation on this :D\n\n        socket = printer.createRfcommSocketToServiceRecord(UUID.fromString(\"00001101-0000-1000-8000-00805F9B34FB\"))\n\n        thread(start = true) {\n\n            val result = HashMap&lt;String, Any>()\n\n            try {\n                socket.connect()\n                result[\"success\"] = true\n                result[\"text\"] = \"connected.\"\n            } catch (e: IOException) {\n                result[\"success\"] = false\n                result[\"text\"] = e\n            }\n\n            callback(result)\n\n        }\n\n    }\n\n\n    private fun preCheckStart() {\n\n        printLoading.visibility = View.VISIBLE\n        printButton.isClickable = false\n        printSwitch.alpha = .25f\n\n    }\n\n    private fun preCheckDone() {\n\n        printLoading.visibility = View.INVISIBLE\n        printButton.isClickable = true\n        printSwitch.alpha = 1f\n\n    }\n\n\n    fun doPrint(stringToPrint: String, keepSocket: Boolean = false) {\n\n\n        \/\/ ESC\/POS default format\n\n        socket.outputStream.write(byteArrayOf(27, 33, 0))\n\n\n        \/\/ Print your string\n\n        socket.outputStream.write(stringToPrint.toByteArray())\n\n\n        if (!keepSocket) {\n            socket.close()\n        }\n\n    }\n\n\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Screenshots<\/h3>\n\n\n\n<ul class=\"wp-block-gallery columns-3 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"1024\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235352-576x1024.png\" alt=\"\" data-id=\"549\" data-link=\"http:\/\/www.randomlog.org\/article\/android-kotlin-ke-bluetooth-printer\/screenshot_20190301-235352\/\" class=\"wp-image-549\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235352-576x1024.png 576w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235352-169x300.png 169w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235352.png 720w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"1024\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235406-576x1024.png\" alt=\"\" data-id=\"550\" data-link=\"http:\/\/www.randomlog.org\/article\/android-kotlin-ke-bluetooth-printer\/screenshot_20190301-235406\/\" class=\"wp-image-550\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235406-576x1024.png 576w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235406-169x300.png 169w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235406.png 720w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"1024\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235518-576x1024.png\" alt=\"\" data-id=\"551\" data-link=\"http:\/\/www.randomlog.org\/article\/android-kotlin-ke-bluetooth-printer\/screenshot_20190301-235518\/\" class=\"wp-image-551\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235518-576x1024.png 576w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235518-169x300.png 169w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235518.png 720w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"1024\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235312-576x1024.png\" alt=\"\" data-id=\"552\" data-link=\"http:\/\/www.randomlog.org\/article\/android-kotlin-ke-bluetooth-printer\/screenshot_20190301-235312\/\" class=\"wp-image-552\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235312-576x1024.png 576w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235312-169x300.png 169w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/Screenshot_20190301-235312.png 720w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Discovery &amp; pairing dilakukan di luar, tapi app harus sebisa mungkin membantu kasir dalam melakukan koneksi ke printer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Walaupun, dari yang saya coba-coba, ada kalanya (test koneksi) tidak berhasil saat; bluetooth baru saja dinyalakan, pairing baru dilakukan, dan semacamnya, yang saya simpulkan disebabkan oleh HP-nya.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kadang kan Bluetooth memang suka aneh, harus di-forget dulu, di-reset dulu, dkk.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Walau demikian, app tetap akan memberitahukan bahwa test koneksi tidak berhasil, dan menon-aktifkan fitur print. Kasir bisa memilih untuk lanjut Transaksi tanpa print, atau benerin koneksi dulu.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Di sebagian besar kasus (bluetooth lagi ga abis diapa2in) sih aman-aman saja, print lancar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ini 2 printer yang saya test btw:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/03\/IMG_20190301_235238-1024x768.jpg\" alt=\"\" class=\"wp-image-553\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/IMG_20190301_235238-1024x768.jpg 1024w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/IMG_20190301_235238-300x225.jpg 300w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/03\/IMG_20190301_235238-768x576.jpg 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Harga sekitar 500 ribu di Tokped, mending yang kanan \ud83d\ude00<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Demikianlah, semoga bermanfaat, masukan sangat dihargai \ud83d\ude00<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Update<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Beberapa bulan lalu kita mengganti penggunaan HP2 standar + bluetooth printer dengan <a href=\"https:\/\/www.sunmi.com\/en\/V1\/\">benda ini<\/a>:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"http:\/\/www.randomlog.org\/wp-content\/uploads\/2019\/08\/IMG_20190531_221315-1024x768.jpg\" alt=\"\" class=\"wp-image-564\" srcset=\"https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/08\/IMG_20190531_221315-1024x768.jpg 1024w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/08\/IMG_20190531_221315-300x225.jpg 300w, https:\/\/randomlog.org\/blog\/wp-content\/uploads\/2019\/08\/IMG_20190531_221315-768x576.jpg 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Sunmi V1<br>Kisaran 3 juta di Tokopedia, masih lebih murah dari 1 HP Android + 1 barcode scanner + 1 bluetooth printer.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">&#8230; yang secara efektif menghilangkan masalah ketidak-stabilan pada koneksi bluetooth.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Benda tersebut memiliki printer built-in yang tetap menggunakan koneksi bluetooth dengan kestabilan yang tak tertandingi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Benda-benda sejenis yang lebih murah ada, tapi untuk urusan &#8220;murah berkualitas&#8221; saya merekomendasikan Xiaomi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dan btw, kodingan di atas ada yang perlu diubah sedikit, di bagian&#8221;pengenalan kode bluetooth untuk printer&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Printer built-in Sunmi ini walaupun menggunakan bluetooth namun tidak dikenali sebagai printer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kalau gak salah saya cuma hapus aja bagian utk pengenalan tsb, beres. Jadi deteksi <em>bluetooth device<\/em> nya jangan cuma dikhususkan untuk printer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Topik ini belum banyak bahasannya. Ane kesulitan sewaktu ingin membuat aplikasi yang perlu melakukan print dengan baik ke printer bluetooth thermal POS\/receipt murah meriah, secara (ketika postingan ini dibuat) belum sampai 1 bulan yang lalu ane membuka developer.android.com dan mendownload Android Studio. Karena itulah ane ingin membagi pengalaman, semoga bisa membantu seseorang di luar sana, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[21,39,44,38,43,40,42],"class_list":["post-540","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-android","tag-bluetooth","tag-ecs","tag-kotlin","tag-pos","tag-printer","tag-thermal"],"_links":{"self":[{"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/posts\/540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/comments?post=540"}],"version-history":[{"count":13,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/posts\/540\/revisions"}],"predecessor-version":[{"id":567,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/posts\/540\/revisions\/567"}],"wp:attachment":[{"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/media?parent=540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/categories?post=540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomlog.org\/blog\/wp-json\/wp\/v2\/tags?post=540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}