Membuat App Bluetooth di Android (3): Komunikasi Antar Devices

Bagian ini tingkat kesulitannya cukup tinggi. Sebelumnya diharapkan pembaca telah memahami prinsip-prinsip thread (lihat kembali tutorialnya). Penting: Tutorial ini akan menggunakan project dari tutorial tentang Bluetooth sebelumnya.

Bluetooth menggunakan prinsip master-slave atau client server, dan dapat bertukar posisi. Satu server dapat terhubung sampai dengan tujuh client. Dalam tutorial ini kita akan membuat satu app dapat berfungsi sebagai server maupun sebagai client. Sebaiknya gunakan dua HP dan hubungkan keduanya dengan laptop dalam mode USB debugging, sehingga bisa langsung dicoba. Setiap dicompile, deploy ke kedua device, lalu satu menjadi server dan satu menjadi client.

ui_bluetooth_client_server

Supaya lebih jelas, video eksekusi app dapat dilihat di:[youtube.com]

Tahapan di client adalah sebagai berikut:

  1. Aktifkan BT jika dalam kondisi off
  2. Tampilkan daftar device yang sudah dipair dalam listview
  3. Jika server belum ada (belum pernah dipair sebelumnya) tekan tombol “Start Discovery” → listview akan bertambah dengan device baru. Jika sudah ada, langsung ke langkah berikutnya.
  4. User men-tap device server yang dikehendaki di listview. Jika belum di-pair maka dialog untuk pairing akan otomatis muncul.
  5. Setelah terhubung, user menekan button untuk mengirim data.

Tahap 1 sampai 3 sudah dilakukan di tutorial sebelumnya.

Tahapan di server adalah sebagai berikut:

  1. Aktifkan BT jika dalam kondisi off
  2. Nyalakan mode discoverablity, ini wajib jika client-server belum di-pair sebelumnya. Karena kalau tidak, maka server tidak akan terlihat oleh client.
  3. Tunggu sampai ada client yang terhubung.
  4. Jika ada client yang terhubung, terima data dan tampilkan di user interface.

Akan ada tiga buah thread:

  1. Thread di server untuk menerima koneksi dari client
  2. Thread di client untuk menghubungi server.
  3. Thread di server dan client untuk berkomunikasi data.

 

 

Kita akan mulai dari sisi server. Buka kembali project pada tutorial sebelumnya. Project tersebut akan kita jadikan client dan server.

Pertama tambahkan button, untuk mengenable server. Jika user men-tap button ini, maka device akan masuk ke mode discoverable dan masuk ke loop untuk menerima koneksi.

Tambahkan aksi saat user mentap tombol enable server sebagai berikut:

public static final int REQUEST_ENABLE_DISCOVERY=998;//konstanta

//saat server di enable
public void klikServer(View v) {

   // nyalakan discoverable, agar client dapat melihat (jika belum pair sebelumnya)
   // otomatis akan menyalakan bluetooth
   // hanya untuk server, kalau sebagai client tidak perlu

   Intent discoverableIntent = new
           Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
   discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
   startActivityForResult(discoverableIntent, REQUEST_ENABLE_DISCOVERY);
   //hasil ankan ditangkap di onActivityResult
}

Akan muncul dialog seperti ini

dialog_ijin_phone_visible

Saat user selesai mengenable discoverable, maka onActivityResult akan dipanggil. Pada onActivityResult kita akan mulai menerima koneksi (tambahan code ditunjukkan dengan panah

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
           tampilkanPaired();  
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
       toast.show();
   } else {
       // <----------------------  untuk server
       //service discovery sudah nyala, lanjutkan dengan menerima koneksi
       if (requestCode == REQUEST_ENABLE_DISCOVERY) {
           if (resultCode != RESULT_CANCELED) {
               //OK, nyala sekarang, panggil method server untuk menerima koneksi
               server();
           }
       }
   }
}

Sekarang untuk menerima koneksi dari client. Pertama siapkan UUID, UUID adalah id yang dianggap akan unik (karena random dan ukurannya yang besar). UUID berfungsi untuk menghubungkan app server dan client. Gunakan situs seperti www.uuidgenerator.net untuk mendapatkan UUID. Ganti UUID di contoh dibawah.

public static final String NAME ="COBASERVER"; //service name
//ganti UUID ini! gunakan generator spt www.uuidgenerator.net
public UUID MY_UUID=UUID.fromString("eb5a63b9-a32b-4ca9-896c-7204f3bfc55b");
 

Karena proses menerima koneksi dilakukan dalam loop, maka tidak mungkin diakukan di thread utama (akan menyebabkan “app not responsive”). Oleh karena itu kita perlu membuat thread terpisah. Buat kelas AcceptThread seperti berikut dan letakkan di kelas MainActivity. Method manageConnectedSocket akan dibuat kemudian.

/**
*    Untuk server.
*    Thread berisi loop sampai menerima hubungan dari client
*/
private  class AcceptThread extends Thread {

       private BluetoothServerSocket mmServerSocket=null;
       
       public AcceptThread() {
           //constructor
           //siapkan socket
           mmServerSocket = null;
           try {
               mmServerSocket = 
                     mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
           }
       }

       public void run() {
           BluetoothSocket socket = null;
           // loop sampai socket terisi (tidak null)
           while (true) {
               try {
                   socket = mmServerSocket.accept();
               } catch (IOException e) {
                   Log.e("ywlog",e.getMessage());
                   break;
               }
               // ada koneksi yang diterima dari client
               if (socket != null) {
                   //proses di thread terpisah
                   manageConnectedSocket(socket);

                   //karena bluetooth cuma menerima satu channel, close saja
                   try {
                       mmServerSocket.close();
                   } catch (IOException e) {
                       Log.e("ywlog",e.getMessage());
                       e.printStackTrace();
                   }
                   break;
               }
           }
       }

       /**  Untuk mematikan koneksi server */
       public void cancel() {
           try {
               mmServerSocket.close();
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
           }
       }

}


public void server()  {
   //mulai jalankan thread server
   (new AcceptThread()).start();
}

Jika socket sudah terhubung, maka akan dipanggil method manageConnecteSocket, dalam method ini akan dipanggil thread yang menangani komunikasi antara server dan client. Artinya thread ini juga nanti akan digunakan untuk bagian client. Buat thread bernama ConnectedThread dan letakkan di kelas MainActivity.

ConnectedThread ct;

/*
  saat server dan client sudah terhubung, thread yang menangani komunikasi data
*/

private class ConnectedThread extends Thread {
   private BluetoothSocket mmSocket;
   private InputStream mmInStream;
   private OutputStream mmOutStream;
   private String strData;

   public ConnectedThread(BluetoothSocket socket) {
       //siapkan input dan output stream
       mmSocket = socket;
       InputStream tmpIn = null;
       OutputStream tmpOut = null;

       //ambil input dan output stream
       try {
           mmInStream = socket.getInputStream();
           mmOutStream = socket.getOutputStream();
       } catch (IOException e) {
           Log.e("ywlog", e.getMessage());
           e.printStackTrace();
       }
   }

   public void run() {
       byte[] buffer = new byte[1024];  // buffer stream
       int bytes; // jumlah bytes yang dibaca read()

       // SERVER: mendengarkan InputStream
       while (true) {
           try {
               bytes = mmInStream.read(buffer);
               if (bytes>0) {
                   //ada data dari client!
                   
                   String tempS = new String(buffer);
                   strData = tempS;
                   //tampilkan ke textview, harus dengan post 
                   //karena ini thread yg terpisah dengan thread UI
     tvHasil.post(new Runnable() {
                       public void run() {
                           tvHasil.setText(strData);
                       }
                   });
               }
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
               break;
           }
       }
   }

   
   /* Menulis ke server (string) */
   public void write(String data) {
       //konversi string ke stream
       try {
           mmOutStream.write(data.getBytes());
       } catch (IOException e) {
          e.printStackTrace();
          Log.e("ywlog",e.getMessage());
       }
   }


   /* matikan connection */
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
           e.printStackTrace();
       }
   }
}


//digunakan baik oleh server maupun client
private void manageConnectedSocket(BluetoothSocket socket) {
   //socket terhubung, mulai terima bagi server dan kirim data bagi client
   ct = new ConnectedThread(socket);
   ct.start();
}

Sekarang kita akan membuat bagian client. Untuk client, user melakukan discovery jika server belum di-pair. Lalu memilih server dari ListView. Jika sudah terhubung. User mentap button untuk mengirim data.

Di onCreate tambahkan code untuk Listview. Jika ditap maka thread client akan dijalankan dengan parameter BlueTooth device server. Thread ini berfungsi menghubungi server.

//siapkan onclik pada listview
//artinya client menghubungi server
lvServer = (ListView) findViewById(R.id.listView);
lvServer.setClickable(true);
lvServer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   @Override
   public void onItemClick(AdapterView<?>; arg0, 
                           View arg1, int position, long arg3) {
       BluetoothDevice devicePilih = arrayPairedDevices.get(position);
        //devicePilih berisi server yang dipilih di listview
        client(devicePilih); 
   }
});

Code untuk client adalah sebagai berikut. Buat kelas ConnectThread yang juga diletakkan di kelas MainActivity.

/*
   thread untuk client

*/
private class ConnectThread extends Thread {
   private BluetoothSocket mmSocket;
   private BluetoothDevice mmDevice;

   //bluetoothdevice didapat dari proses device discovery atau dari daftar
   public ConnectThread(BluetoothDevice device) {
       mmDevice = device;
        //ambil socket
       try {
          mmSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
       }
   }

   public void run() {
       //Jika discovery sedang jalan, batalkan karena memperlambat koneksi
       mBluetoothAdapter.cancelDiscovery();
       try {
           //terhubung ke server
           mmSocket.connect();
       } catch (IOException connectException) {
           try {
               mmSocket.close();
           } catch (IOException e) {
              Log.e("ywlog",e.getMessage());
              e.printStackTrace();
           }
           return;
       }

       //terhubung, tangani kirim data
       manageConnectedSocket(mmSocket);
   }

   /* batalkan koneksi  */
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
           e.printStackTrace();
       }
   }
}

//thread dipanggil saat user mengklik listview, passing device
public void client(BluetoothDevice device) {
   //start thread client
   (new ConnectThread(device)).start();
}

Terakhir, jika sudah terhubung, user akan mengirimkan data melalui ConnectThread , tambahkan button “kirim data”. Lalu berikut aksinya:

public void klikKirimData(View v) {
   ct.write("Halo dari client!");
}

Jika user menakan tombol ini, maka textview di server akan berisi “Halo dari client”. Di saat yang bersamaan, anda juga bisa menjadikan client menjadi server dengan menekan tombol “Enable Server”, lalu gantian tekan tombol kirim data di server.

Source code tutorial 1 sd 3 dapat diambil di: https://github.com/yudiwbs/CobaBluetooth

Perbandingan Dell 14-7447 dengan Asus X450J

Update: batere Asus X450J  agak mengecewakan, dengan opsi Power Saver dan hanya digunakan  untuk mengetik tanpa internet, batere hanya tahan sekitar 3 jam atau sekitar separuhnya dari Dell 14-7447.

Laptop lama saya sudah mulai kesulitan untuk digunakan sehari-hari.   Membuat program dengan Java, berurusan dengan data yang besar, browser yang semakin rakus membuat laptop semakin keteteran. Ditambah batere yang sudah mati, lengkap sudah penderitaan 🙂

Beruntung istri dapat laptop Dell 14-7447 dari proyek penelitiannya. Karena dia masih punya laptop juga, maka saya culik eh pinjam hehe.  Tapi lalu istri juga mendapat kerjaan yang memerlukan proses berat ditambah laptop ini akan  dibawa ke Palembang, ya sudah [terpaksa] beli saja.

 

dell_147447_asus_x450j

Keterangan gambar:  Asus X450J (depan) dan Dell 14-7447

Spesifikasi minimal yang saya perlukan adalah prosesor  i7 dan memori minimal 8GB tapi kalau bisa lebih.  Setelah cari online, sepertinya hanya Dell yang memenuhi syarat tersebut dan jenisnya sama persis dengan punya istri. Sayangnya bagi saya harganya masih terlalu mahal, di kisaran 14jt (ingat istri dapat laptop dari penelitian, jadi tidak terasa hehe), tapi apa boleh buat.

Berbekal info tersebut, kami mampir ke BEC, iseng-iseng saya lihat laptop Asus X450JB, ternyata prosesornya sudah cocok, tinggal memori (default yang terpasang hanya 4GB). Harganya juga relatif jauh lebih murah dibandingkan Dell, 9.4jt. Saya tanya ke pegawainya, ternyata memori bisa diupgrade ke 16GB!  Yang 4GB dicopot, diganti dengan dua keping 8GB. Tukar tambah memori ini membutuhkan 1.1jt. Jadi total 10.5jt, tetap masuk akal.

Rasanya menggunakan memori 16GB? bisa dilihat pada gambar bawah, memori saat Windows dimulai.  Saat browser, IntelliJ dijalankanpun penggunaan memori tidak pernah melebihi 50%.

memori

Lalu apa kelebihan Dell 14-7447 dibandingkan Asus X450J? Pertama, GPU Dell adalah GTX 950M sedangkan Asus 940M, memang lebih bagus untuk main game, tapi karena saya hanya main CS-GO saja, perbedaaannya tidak terlalu terlihat. Anak saya melaporkan untuk game Dotta, dengan Dell bisa dapat 100 fps sedangkan Asus hanya 70fps saja. Kemudian layar Dell berjenis matte yang tidak memantul, sangat enak kalau digunakan di luar ruangan. Dell juga lebih hening kipasnya. Warna yang merah menyala juga lebih menarik. Batere juga lebih lama 45mWH vs 42mWH, atau sekitar 5 jam vs  3 jam (selisih kapasitas tidak jauh tapi efeknya kok besar, mungkin power management Dell lebih bagus?). Windows sudah tersedia juga.

Kelebihan Asus X450JB?  Nomor satu adalah harga 🙂  Selisih 4jt tapi memori bertambah dari 8GB ke 16GB dengan prosesor sama.  Garansi Asus juga lebih panjang, 2thn vs 1thn. Keyboard menurut saya lebih nyaman. Asus memiliki VGA port selain HDMI, yang penting bagi dosen yang sering presentasi.  Bloatware Asus juga lebih sedikit. Dell memiliki banyak program bawaan yang tidak bisa di-uninstall.

Kesimpulannya, bagi saya Asus X450J yang diupgrade memorinya sudah paling optimal berdasarkan spesifikasi dan harga.

Membuat App Bluetooth di Android (2): Daftar Paired Devices dan Device Discovery

Menampilkan Paired Devices

Catatan: lanjutan dari tutorial sebelumnya (menyalakan Bluetooth), code yang digunakan dalam tutorial ini adalah lanjutan dari tutorial sebelumnya.

Sebelum dapat bertukar data, device harus ditemukan dan di-pair terlebih dulu (untuk alasan keamanan). Proses ini memperlukan waktu dan daya batere besar.  Oleh karena itu Android menyimpan data device yang sudah dipair (bonded) seperti nama device, class, MAC address  sehingga dapat langsung digunakan tanpa perlu mencari dan mem-pair lagi.

Untuk mendapatkan data device yang sudah di-pair atau bonded, dapat digunakan method BluetoothAdapter.getBondedDevices().

Pada app berikut kita akan menampilkan device yang sudah di-pair dalam listview. Kita perlu siapkan listiviewnya terlebih dulu. Tambahkan widget  Listview (gambar bawah)

Rancagan UI listview menampilkan paired device

Tambahkan ArrayList untuk menyimpan data beserta adapter.


 //untuk listview
 private ArrayList<String> items = new ArrayList<>();
 ArrayAdapter adapter;

 //menyimpan daftar device yg terurut sesuai listview
 private ArrayList<BluetoothDevice> arrayPairedDevices = new ArrayList<>();

Buat method yang mengambil data paired devices dan kemudian update listview.

// cari paired device, dan tambah ke listview
private void tampilkanPaired() {
   //ambil paired/bonded devices, tampung di arraylist
   Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

   //ada?
   if (pairedDevices.size() > 0) {
       // Loop semua paired devices
       for (BluetoothDevice device : pairedDevices) {
           // tambahkan ke listview
           adapter.add(device.getName() + "\n" + device.getAddress());
           //nanti kalau user mengklik listview, kita tahu device apa yg diklik
           arrayPairedDevices.add(device);
       }
   }
   //refresh listview
   adapter.notifyDataSetChanged();
}

Panggil method tersebut pada saat onCreate.

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     ... sama dengan sebelumnya

     //init listview
     ListView lv = (ListView) findViewById(R.id.listView);
     //set warna abu2, karena default font adalah putih
     lv.setBackgroundColor(Color.LTGRAY);
     adapter = new ArrayAdapter (this,android.
                                 R.layout.simple_expandable_list_item_1,items);
    lv.setAdapter(adapter);
    tampilkanPaired();

     //pindah ke bawah
     if (!mBluetoothAdapter.isEnabled()) {
         //bluetooth dalam kondisi off
         //menampilkan dialog untuk menyalakan bluetooth (jika sedang mati)
         //setelah user memilih method onActivityResult akan ditrigger
         Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
         startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
     }
 }

Dibagian onActivityResult juga tambahkan bagian untuk mengupdate listview saat Bluetooth selesai dinyalakan (bagian panah).

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
           tampilkanPaired();  // <-------------------
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), 
                                    pesan, Toast.LENGTH_LONG);
       toast.show();
   }
}

Jalankan app dan hasilnya akan seperti gambar di bawah. Informasi yang ditampilkan adalah nama device dan MAC address. MAC address adalah alamat unik untuk network interface yang umumnya tersimpan di hardware. Tidak ada device yang memiliki MAC address yang sama.

tampilan paired bluetooth

Menemukan Device (Device Discovery)

PENTING: mulai Android 6 (Marshmallow, API 23), untuk melakukan device discovery diperlukan permission ACCESS_COARSE_LOCATION atau ACCESS_FINE_LOCATION (tentang permission ini lihat tutorial tentang Play Service Location API).

Secara default, umumnya device hanya visible pada device yang sudah ter-pair. Agar dapat ditemukan oleh proses scan, visibilitas device perlu diset terlebih dulu menjadi discoverable. Pada Android 5 dan 6 ini dilakukan dengan membuka setting Bluetooth dan otomatis device akan masuk mode discoverable (gambar bawah).

Menyalakan discoverable

Sedangkan untuk Android versi 4.2 diaktifkan dengan membuka setting BlueTooth lalu tap tulisan “Only Visible to paired device” (gambar bawah)

Menyalakan discoverable di Android 4

Kita akan membuat app yang mencari device baru yang belum di-pair sebelumnya. Sebelum menjalankan app, pastikan ada device lain sudah dalam kondisi discoverable sehingga dapat ditemukan oleh app kita.

Kita akan modfikasi project sebelumnya. Pertama tambahkan permission untuk lokasi ACCESS_COARSE_LOCATION. Ini disebabkan mulai Android 6 (Marshmallow, API 23), untuk dapat melakukan device discovery perlu permission ini. Jika tidak, tidak akan keluar error tetapi tidak akan mendapatkan hasil (membuat sangat sulit didebug!).

Jadi manifest akan seperti ini:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Android 6 juga menerapkan runtime permission system untuk beberapa permission yang dianggap “sensititf”. Dengan runtime permission system, user memberikan ijin bukan saat install, tetapi saat fitur akan digunakan (contoh gambar dibawah). Lokasi termasuk dalam kategori runtime permission.

run time permission untuk lokasi

Selanjutkan kita tambahkan kode untuk menangani runtime permission untuk lokasi di onCreate:

//untuk minta ijin lokasi, mulai android 6, scan bluetooth perlu ijin lokasi
//angka bebas tapi jangan lebih dari 1 byte! (<255)
private static final int MY_PERMISSIONS_REQUEST = 98;
boolean isIjinLokasi = false; //sudah mendapat ijin untuk mengakses lokasi?


@Override
 protected void onCreate(Bundle savedInstanceState) {
     ...  sama

     //mulai android 6, device discovery bluetooth perlu ijin lokasi
     //akan keluar dialog yg menanyakan apkah user memberikan ijin?
     if (ContextCompat.checkSelfPermission(this,
             //hati2, jika konstanta tidak cocok dgn manifest, tidak ada runtimeerror
             Manifest.permission.ACCESS_COARSE_LOCATION)
             != PackageManager.PERMISSION_GRANTED) {

         ActivityCompat.requestPermissions(this,
                 new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                 MY_PERMISSIONS_REQUEST);
                 // MY_PERMISSIONS_REQUEST adalah konstanta, 
                 //nanti digunakan di  onRequestPermissionsResult
     } else {
         //sudah diijinkan, tidak perlu tanya lagi
         isIjinLokasi = true;
     }

 }

Saat pertamakali menggunakan fitur, user akan ditanya apakah memberikan ijin. Hasilnya akan ditangkap di method onRequestPermissionResult seperti dibawah

//setelah muncul dialog tentang ijin lokasi, 
//user akan merespon (allow atau deny), dan method ini akan dipanggil
@Override
public void onRequestPermissionsResult(int requestCode,
                                      String permissions[], int[] grantResults) {

   if (requestCode == MY_PERMISSIONS_REQUEST) {
       if (grantResults.length > 0
           && grantResults[0] == PackageManager.PERMISSION_GRANTED)
       {
           isIjinLokasi = true; //diijinkan oleh user
       }
       return;
   }
}

Sekarang kita mulai aksi untuk mencari device. Tambahkan button, beri label “Start Discovery”

rancangan button

Isi onClicknya dengan method klikStartDiscovery sebagai berikut. Untuk memulai proses discovery dilakukan hanya dengan memanggil method startDiscovery() yang hasilnya kemudian ditangkap dengan BroadcastReceiver.

public void klikStartDiscovery(View v) {
   //membutuhkan waktu lama (sekitar 12 detik), proses dibackground
   //hasil ditangkap dengan BroadcastReceiver


   if (!isIjinLokasi) {
       //tidak mendapat ijin lokasi. Untuk android versi dibawah 6, tidak masalah tapi
       //di android versi 6 tidak error tapi tidak akan memberikan hasil --> susah didebug!!
       Toast toast = Toast.makeText(getApplicationContext(),
               "Tidak diijinkan untuk akses lokasi. " +
               "Proses device discovery tidak akan memberi hasil!!", Toast.LENGTH_LONG);
       toast.show();
   } else {
       //dapat ijin
       if (mBluetoothAdapter.isDiscovering()) {
           //sedang proses discovery? batalkan.
           mBluetoothAdapter.cancelDiscovery();
       }
       //mulai proses discovery
       //hasilnya akan ditangkap di BroadcastReceiver
       if (mBluetoothAdapter.startDiscovery()) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Sukses memulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       } else {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "GAGAL memulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       }
   }
}

Selanjutnya kita akan siapkan BroadcastReceiver. Pertama register BroadReceiver agar dipanggil saat device ditemukan, dan saat service discovery dimulai dan selesai.

@Override
protected void onResume() {
   // onResume juga dipanggil saat app dimulai
   // daftar broadcastreciver untuk menerima intent.
   IntentFilter filter = new IntentFilter();
   filter.addAction(BluetoothDevice.ACTION_FOUND);
   filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
   filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
   registerReceiver(mReceiver, filter);
   super.onResume();
}

@Override
protected void onPause() {
   // Unregister karena activity dipause.
   LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
   super.onPause();
}

Kemudian implementasikan BroadcastReceiver. Di method ini akan ditampilkan pesan saat pencarian dimulai dan selesai. Jika ada device baru, maka akan ditambahkan ke listview.

//untuk menangkap hasil method .startDiscovery
//tertrigger untuk setiap*device ditemukan (ACTION_FOUND)
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();

       // ada device baru
       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           // ambil objek BluetoothDevice dari Intent, tulis ke listivew
           BluetoothDevice device = 
               intent.getParcelableExtra (BluetoothDevice.EXTRA_DEVICE);
           
           arrayPairedDevices.add(device); 
    // tulis nama dan MAC address ke ListView
           adapter.add("Baru:" + device.getName() + "\n" + device.getAddress());
           //refresh listview, JANGAN LUPA!!
           adapter.notifyDataSetChanged();
       } else
       // mulai proses discovery, untuk debug saja, memastikan proses dimulai
       if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Mulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       } else
       // mulai proses discovery, untuk debug saja, memastikan proses selesai
       if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Proses discovery selesai", Toast.LENGTH_LONG);
           toast.show();
       }
   }
};

Jalankan maka hasilnya akan sebagai berikut (pastikan device lain dalam kondisi ter-discoverable). Device yang baru akan diawali kata “Baru”.

tambahan device baru

 

Berlanjut ke bagian 3: komunikasi data antar device

Membuat App Bluetooth di Android (1): Pendahuluan dan Aktifkan BT

logo bluetooth

 

Bluetooth (BT) adalah  standard teknologi wireless untuk komunikasi jarak pendek (10m – 100m) dengan energi rendah untuk data berukuran relatif kecil. BT menggunakan frekuensi 2.4 sampai dengan  2.485Ghz (sama dengan WiFi, tapi berbeda teknologi). Kecepatan Bluetooth mencapai 25Mbps (versi 4.0) Bluetooth diciptakan oleh perusahaan Erricson tahun 1995 dan berkembang cepat sejalan dengan tumbuhnya mobile device dan IoT (Internet of Thing).

Darimana asal kata dan logo Bluetooth? Nama Bluetooth diambil dari Raja Denmark: King Harald Bluetooth yang menyatukan berbagai fraksi di Denmark, Norwegia dan Swedia. Logo Bluetooth merupakan gabungan dari simbol H dan B yang merupakan inisial raja tersebut.

sejarah logo bluetooth

Bluetooh Low Energy (BLE) atau Bluetooth Smart muncul tahun 2010 sejalan dengan versi 4.0. BLE menggunakan daya rendah dan memiliki kecepatan transfer lebih tinggi dibandingkan BT versi lama (klasik). Karena hemat daya, BLE cocok untuk smartphone dan alat yang menggunakan batere koin. Sebagai contoh, batere koin CR2032 bisa digunakan untuk menghidupi alat bluteooth selama 5-10 tahun!. Sayangnya protokol BLE tidak kompatibel dengan BT klasik dan belum semua device support BLE. Tutorial ini masih membahas BT klasik.

Saat ini ada jutaan device yang memiliki BT ini. Mulai dari laptop, mouse, keyboard, smarphone, headphone, headset, fitness tracker, speaker dan lain sebagainya. Jumlah yang besar ini membuat harga hardware BT semakin murah.

Saingan utama BT adalah Wi-Fi Direct. Wi-Fi Direct dapat menghubungkan dua device secara peer to peer tanpa memerlukan access point. Wi-Fi direct memiliki kecepatan lebih tinggi (250Mbps) dan jangkauan lebih jauh (300m). Tapi Wi-Fi directy lebih boros daya dan belum banyak device yang mendukungnya. Android mendukung baik BT, BLE maupun WiFi direct.

Android menyediakan Bluetooth API yang dapat digunakan untuk:

  • Cek apakah device mensupport BT
  • Menampilkan dialog agar user menyalakan BT
  • Scan device yang berada dekat lalu pairing.
  • Membuka channel RFCOMM (Radio Frequency Communications) dan bertukar data.

Ada dua jenis bluetooth yang disupport Android, pertama Classic Bluetooth dan kedua adalah Bluetooth Low Energy, BLE (mulai Android 4.3). Kita akan mulai dari yang klasik terlebih dulu, karena BLE masih belum disupport oleh semua device Android yang ada di pasaran.

Berikutnya kita akan membuat app yang mendeteksi apakah device sudah support BT dan menampilkan dialog untuk menyalakan jika BT dalam kondisi off.

Buat project baru.

Buatlah project baru. Untuk menggunakan Bluetooth diperlukan permission android.permission.BLUETOOTH sedangkan untuk dapat mencari device harus digunakan android.permission.BLUETOOTH_ADMIN. Update manifest sebagai berikut:

<manifest>
...
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>

Pengecekan Device apakah telah mendukung Bluetooth

Selanjutnya perlu dicek apakah device sudah men-support bluetooth. Untuk melakukan ini gunakan class BluetoothAdapter. Semua aktivitas yang terkait BT memerlukan kelas ini. Gunakan method getDefaultAdapter, dan jika mengembalikan nilai null, maka device tidak mensupport Bluetooth. Coba code dibawah dibagian onCreate.

BluetoothAdapter mBluetoothAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
   String pesan;
   if (mBluetoothAdapter == null) {
       pesan = "Tidak ada bluetooth!";
       finish(); //keluar
   } else {
       pesan ="Ada bluetooth!";
   }
   Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
   toast.show();
}

Menyalakan Bluetooth

Langkah berikutnya adalah menyalakan Bluetooth jika dalam kondisi mati. Akan muncul dialog, dan setelah user memilih maka method onActiviyResult akan dipanggil. Berikut codenya:

//konstanta untuk req enable bluetooth, angkanya bebas
public static final int REQUEST_ENABLE_BT=999;

@Override
protected void onCreate(Bundle savedInstanceState) {
   … sama dengan sebelumnya

   if (!mBluetoothAdapter.isEnabled()) {
       //bluetooth dalam kondisi off
       //menampilkan dialog untuk menyalakan bluetooth 
       //setelah user memilih; method onActivityResult akan di-trigger
       Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
       startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
   }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//setelah user menjawab dialog menyalakan BT 
//maka method ini akan dipanggil
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
       toast.show();
   }
}

Bersambung ke bagian 2: menampilkan paired device dan mencari device (device discovery)

Alternatif Internet Fiber Optic selain Indihome di Bandung

Update Nov 2017: Hore, dapat penawaran dari MNC.  MNC sudah masuk daerah rumah saya 🙂  Sekilas harganya relatif sama dengan Indihome, tapi ada promo gratis ongkos pasang dan potongan harga 50% selama 6 bulan.  MNC juga menawarkan kecepatan upload yang sama dengan download (Indihome hanya 20%  saja). Untuk sekarang belum tertarik karena Indihome masih sangat reliable, tapi senang punya alternatif lain 🙂

mnc

Update Feb 2016: Blokir Indihome terhadap Netflix dan pemberlakuan FUP (pengurangan kecepatan setelah mencapai batas tertentu) membuktikan kecemasan saya. Semoga alternatif Indihome bisa lebih cepat menjangkau daerah rumah 😐

Bagi saya internet sangat penting, pertama kali saya langganan internet di Bandung sekitar tahun 1998 saat saya mahasiswa S1. Masih dialup modem dengan Centrin sebagai ISP. Pulsa telepon dibayar orang tua sedangkan Centrin saya yang bayar dari uang mengerjakan proyek-proyek. Dilanjutkan telkomnet instant, GPRS Indosat (25 rb/bulan), flexy, tri, simpati dsb tidak terhitung lagi. Terakhir saya menggunakan 4G dari tiga operator dan akhirnya triple play Indihome.  Setelah menggunakan Indihome saya pikir akan sulit sekali kembali ke masa internet berkuota. Oleh karena itu penting mencari informasi alternatif lain. Indihome sampai saat ini masih bagus dan stabil, tapi tetap harus berjaga-jaga karena ada beberapa kejadian yang sempat membuat khawatir (sudah saya tulis di posting tentang Indihome). Dari sisi jangkauan memang sepertinya tidak ada yang dapat mengalahkan Indihome karena mereka memanfaatkan tiang-tiang telepon Telkom yang sudah lama ada.

Setelah mencari-cari, beberapa alternatif untuk Bandung adalah (update Okt 2016):

    1. Biznet home,  harga 240rb / 25Mbps. Cakupan di Bandung cukup luas (tip: cari dengan kode pos). Untuk rumah saya,  jarak terdekat dengan jalan yang tercover adalah sekitar 500m.  Belum pernah kontak apakah bisa menarik kabel sejauh itu. Ada kantornya di PVJ (depan Carrefour).
    2. Firstmedia Fastnet.  Harganya 300 rb/10 Mbps, termasuk TV kabel.   Cakupan Firstmedia di Bandung tidak bisa dicek langsung tapi bisa melalui area di form pendaftaranya (gambar bawah). Untuk daerah saya, ada di area Sariwangi, sayangnya hanya sampai Blok 17 (saya blok 23).  Harusnya Sariwangi itu dibawah Sarijadi, bukan kebalik, kode posnya juga salah. Tapi ini prospeknya bagus, karena eksplisit masuk ke perumnas Sarijadi.
    3. CBN. Harga 300rb untuk 15Mbps, kecepatan upload sama dengan download. Pilihan paketnya  jelas dan transparan. Coverage di Bandung juga jelas dengan peta (gambar bawah, lagi-lagi wilayah rumah saya belum masuk).ScreenHunter_157 Dec. 12 17.29
    4. MNC   harga 350 untuk 10Mbps. Lokasi cakupan tidak jelas, harus daftar dulu dengan memasukkan alamat, jika tidak tercover maka akan keluar pesan.  Yang menarik dari MNC adalah kecepatan upload ditulis eksplisit dan sama dengan download. Sebagai perbandingan, Indihome downloadnya mencapai 10Mbps tapi upload hanya 2 Mbps.
    5. Update Feb 2016: Indosat GIG, kecepatannya 15  sampai 1Giga (menarik yang 1Gbps, harganya berapa ya hehe). Di Bandung hanya tersedia untuk perumahan Cherry Field Buah Batu. Mungkin memang mencari tempat yang belum tercover Indihome, terutama apartemen.
    6. Megavision/starnet. Ini ISP aneh, katanya ada yang pakai dan ada yang share foto promonya. Tapi di websitenya sangat minim info. Tidak ada harga, tidak ada informasi coverage  dan paket yang tercepat hanya 5Mbps. Dugaan saya umur ISP ini tidak akan lama lagi.

 

 

 

GameMaker Language (GML)

Ini lanjutan dari tutorial GameMaker yang pertama

GameMaker Language (GML)  adalah bahasa pemrograman yang digunakan di dalam GameMaker. Walaupun model interface drag-drop sangat memudahkan, tapi saat game semakin rumit biasanya diperlukan code tambahan dan penggunaan code semakin mempermudah pemeliharaan (menghilangkan duplikasi dan lebih readable).

Code dalam GameMaker disimpan dalam event dan script.  Seperti biasa, project pertama adalah membuat “hello world” terlebih dulu. Gunakan project pada tutorial sebelumnya atau buat project baru, tambahkan objek, dan pada objek properties tambahkan event “create” (gambar bawah).

ScreenHunter_148 Dec. 11 14.08

Lalu dibagian action, pilih tab “Control” dan drag “Execute Code” (gambar bawah). Artinya saat object dicreate, code ini akan dipanggil.

ScreenHunter_149 Dec. 11 14.10

Lalu ketikan code berikut, dan tekan check hijau di kiri atas.

ScreenHunter_150 Dec. 11 14.12

Jalankan program, maka sebelum objek di create, akan muncul window “Hello World”. Perhatikan standard penamaan fungsi yang digunakan GML adalah menggunakan underscore.

Syntax GML

Sebelum kita melanjutkan, kita akan bahas sekilas tentang syntax dari GML. Diasumsikan pembaca telah mengetahui dasar-dasar pemrograman. Syntax GML mirip dengan C dan variannya seperti Java dan PHP.

Variabel

Ada tiga jenis variabel pada GML: instance, local, dan global. Variabel instance berlaku pada objek, dapat langsung ditulis untuk seperti ini:

xx = 5;
nama = "Yudi";

Variabel lokal berlaku pada satu script atau block, harus diklarasikan dengan var, contohnya

var x = 5;
var nama = "Yudi";

Variabel lokal ini akan dihapus setelah script atau block berakhir sehingga lebih menghemat memori, berbeda dengan variabel instance yang tetap ada selama objek ada.

Variabel global, berlaku untuk semua objek, dideklrasikan dengan keyword global.  Sebagai contoh:

global.xx = 5;
global.nama = "Yudi";

Variabel global akan selalu ada dan tidak pernah didestroy selama program berjalan.

Array
Untuk variabel array, contohnya seperti ini

mobil[0] = 5;
orang[10,10] = 2;//dua dimensi

Fungsi-fungsi terkait array:

is_array
array_length_1d 
array_length_1d
array_height_2d

Statement if-else

Contoh:

xx=10;
if (xx==5) {
   show_message("sama dengan lima");
} else if (xx<5) {
   show_message("lebih kecil dari lima");
} else {
   show_message("lebih besar dari lima");
}

Statement loop
Ada beberapa jenis loop di GML, berikut contoh-contohnya

For loop:

xx = 0;
for (i=0;i<15;i++) {
    xx = xx + 1;
}
show_message(xx);

Repeat loop:

xx=0
repeat(15) {  
  xx = xx+1;
}
show_message(xx);

While loop:

xx=0;
i=0;
while (i<15) {
  xx = xx+1;
  i  = i+1;
}
show_message(xx);

GML juga mensupport break

Fungsi
Pemanggilan fungsi di GML adalah sebagai berikut

nama_fungsi(arg1, arg2, arg3);

Untuk membuat fungsi buatan sendiri, kita dapat menggunakan script.

Script

Di GML, script adalah fungsi buatan sendiri. Dengan script kita dapat menghilangkan duplikasi dan me-reuse code. Untuk menambahkan script, coba lihat di tree resources, klik kanan script dan create (gambar bawah)

ScreenHunter_151 Dec. 11 14.29

Kita akan membuat script (atau fungsi) yang menerima dua argumen numerik dan menambahkannya. Beri nama script “tambah” (gambar bawah)

ScreenHunter_152 Dec. 11 14.30

Selanjutnya, masukan kode berikut, perhatikan penggunaan var dalam deklarasi variabel.

var n1 = argument0; //parameter 1
var n2 = argument1; //parameter 2
var hasil = n1+n2;
return hasil;

Lalu, di event create objek (ingat program hello world sebelumnya), tambahkan code berikut:

hasil = tambah(10,5); //script atau fungsi dipanggil
show_message(hasil);

Tip: show_message(“hasil=”+hasil) tidak akan berfungsi, harus menggunakan show_message(“hasil=”+ string(hasil))

Tutorial 2: menggerakan objek dengan code

Setelah sekilas mengetahui dasar-dasar syntax GML, sekarang kita coba menghasilkan seperti tutorial pertama tetapi dengan code.

Seperti pada tutorial pertama, buat sprite, lalu objek, lalu room, letakan objek di room. Selanjutnya tambahan event “Step” untuk objek (gambar bawah). Step adalah langkah dalam animasi atau game, jika frame rate 30 frame per detik, maka step akan dipanggil 30 kali setiap satu detik. Pilih tab control di action, tambah code.

ScreenHunter_153 Dec. 11 14.43

Kemudian isi code berikut. Pada setiap step, program akan mengecek apakah ada tombol panah yang ditekan dan menyesuaikan posisi.

if (keyboard_check(vk_left)) {
    x = x - 8;
}
if (keyboard_check(vk_right)) {
    x = x +8
} 
if (keyboard_check(vk_up)) {
    y = y - 8;
}
if (keyboard_check(vk_down)) {
   y  = y + 8;
}

Perhatikan penggunaan x dan y. Secara otomatis, dua variabel itu sudah ada di dalam code (variabel instance) dan terhubung dengan posisi objek.

Built In Variabel
Selain variabel x, y seperti dalam contoh sebelumnya, ada beberapa variabel terdefinisi yang dapat dimanfaatkan. Hati-hati memilih nama variabel yang dapat bentrok dengan built in variable  ini. Variabel berjenis ini dapat dilihat dari warna (gambar bawah). Variabel built in (y) berwarna merah sedangkan variabel user (yy) berwarna putih.

ScreenHunter_154 Dec. 11 14.46

Variabel yang lain built in diantaranya:

  • room_width: lebar room
  • room_height: tinggi room
  • sprite_index: sprite yang digunakan objek. Dapat digunakan untuk mengganti sprite.
  • image_number: frame sprite
  • sprite_width: lebar sprite
  • sprite_height: tinggi sprite
  • image_speed: kecepatan animasi sprite (default 1)
  • image_angle: sudut sprite
  • image_xscale: skala sprite (lebar)
  • image_yscale: skala sprite (tinggi)
  • xprevious: posisi x pada step sebelumnya.
  • yprevious: posisi y pada step sebelumnya.

Android Thread (3): IntentService

Lanjutan dari tutorial pendahuluan tentang thread dan Asynctask.

Service cocok untuk task yang membutuhkan waktu lama, berjalan di background dan tidak membutuhkan akses ke user interface. Misalnya saat app mengirimkan data ke server, user dapat menekan tombol “send” dan kemudian melanjutkan menggunakan app untuk yang lain. Contoh yang lain adalah streaming musik di backround dan download.

Perbedaan utama antara service dengan Asynctask adalah Asynctask cocok untuk task yang berjalan di background yang terkait dengan user interface sedangkan service cocok untuk task background yang independen terhadap user interface. Untuk memberikan notifikasi ke user dari service, dapat digunakan toast atau notification bar. Perbedaan lain dengan AsyncTask adalah service mendapat prioritas lebih tinggi sehingga lebih kecil kemungkinannya untuk dimatikan, walaupun jika kehabisan memory service dapat dimatikan untuk kemudian direstart lagi.

Fungsi service ada dua: pertama meminta sistem menjadwalkan task di background yang akan terus berjalan sampai service berhenti. Fungsi kedua adalah menyediakan layanan pada app lain. Service bukan thread dan berada di thread utama (UI thread). Itu sebabnya kita tetap perlu membuat thread terpisah di service untuk task yang panjang karena tetap dapat menyebabkan error ANR.

Seperti halnya AsyncTask, Android menyediakan IntentService untuk memudahkan programmer menggunakan service. IntentService secara otomatis akan membuat thread baru (disebut worker thread) dan menangani antrian pemanggilan service (jika service dipanggil berkali-kali). Intinya programmer tidak perlu lagi memikirkan pengelolaan thread di dalam service.

Pada tutorial ini akan dibahas terlebih dulu tentang IntentService, baru kemudian dilanjutkan mengenai service yang lebih fleksibel tapi lebih kompleks.

Kita akan membuat app sederhana, yang akan menampilkan toast saat proses background dimulai dan toast saat proses background selesai.

Untuk menggunakan IntentService, extends kelas IntentService dan override method create() dan onHandleIntent(). Task yang berjalan di backround diletakkan di onHandleIntent().

Tetapi kita perlu membuat kelas khusus untuk menampilkan toast terlebih dulu. Toast tidak dapat langsung digunakan karena onHandleIntent berjalan di worker thread, bukan UI thread. Oleh karena itu perlu digunakan handler. Handler adalah objek yang dapat digunakan untuk berkomunikasi antar thread. Handler akan berada pada thread yang membuatnya. Karena service dicreate oleh UI thread, maka handler otomatis juga ada di UI thread. Jadi kita bisa menggunakan handler untuk berkomunikasi dari worker thread ke UI thread (pusing ya?).

public class DisplayToast implements Runnable {
   private final Context mContext;
   String mText;

   public DisplayToast(Context mContext, String text){
       this.mContext = mContext;
       mText = text;
   }

   public void run(){
       Toast.makeText(mContext, mText, Toast.LENGTH_SHORT).show();
   }
}

Buat project baru, buat kelas CobaService yang mengextend IntentService sebagai berikut :

public class CobaService extends IntentService {
    Handler mHandler; //untuk komunikasi dengan UI thread 


public CobaService() {
   super("cobaservice"); //untuk debug saja
   mHandler = new Handler();
}

@Override
protected void onHandleIntent(Intent intent) {

   //ambil data dari intent
   String pesan;
   Bundle extras = intent.getExtras();
   if(extras == null) {
       pesan= null;
   } else {
       pesan=extras.getString("PESAN");
   }
   
  mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

  //proses background ada disini 
  for (int i = 0; i<50;i++) {
       try {
           Thread.sleep(50);  //simulasikan proses panjang
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
   mHandler.post(new DisplayToast(this, "Service selesai"));
}

}

Selanjutnya, service perlu didaftarkan di dalam manifest, dengan membuat elemen service di dalam elemen application.


<application
   ...
   <service android:name=".CobaService" android:exported="false"/>
</application>

android:exported diset false agar service ini hanya dapat dipanggil lokal oleh app. Ingat bahwa salah satu fungsi service adalah menyediakan layanan bagi app lain. Untuk faktor keamanan, kecuali jika memang ingin service dapat diakses app lain, selalu set exported dengan false.

Selanjutnya kita akan membuat tombol yang saat ditekan akan memanggil service ini: (jangan lupa set onClick button dengan method dibawah).

public void klikMulai(View v) {
   //intent digunakan sebagai paramater
   serviceIntent = new Intent(getApplicationContext(), CobaService.class);
   serviceIntent.putExtra("PESAN", "hello world"); //data yang dikirim
   getApplicationContext().startService(serviceIntent); //mulai jalankan 
}

Coba tekan tombol berulang-ulang, maka akan muncul secara berurutan pesan “service mulai,” “service selesai”, “service mulai” dan seterusnya. Intentservice akan menangani request secara berurutan, bukan paralel. Lalu coba tekan tombol dan langsung hide app, maka walaupun kita berada di app lain, tulisan “service selesai” tetap akan muncul.

Menampilkan Notifikasi

Pada contoh sebelumnya kita menggunakan toast untuk menampilkan pesan bahwa service sudah selesai. Kelemahan toast adalah hanya muncul sebentar. Untuk menjamin agar pesan dibaca user dapat digunakan notification (gambar bawah).

screen1

Untuk menambahkan notifikasi, gunakan NotificationCompat.Builder untuk membuat notifikasi dan NotifcacationManager untuk menampilkannya. Perlu digunakan TaskStackBuilder agar tombol back tetap berfungsi konsisten. Tambahkan kode berikut dibagian akhir dari onHandleIntent.

@Override
protected void onHandleIntent(Intent intent) {
...sama dengan sebelumnya

// sudah selesai, siapkan notif builder
NotificationCompat.Builder notifBuilder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("My notification")
               .setContentText("Selesai! Tap untuk menampilkan app");

// explisit intent untuk memulai activity
Intent resultIntent = new Intent(this, MainActivity.class);
// Gunakan taskstack agar saat user menekan tombol back tetap konsisten
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);

PendingIntent resultPendingIntent =
       stackBuilder.getPendingIntent(
               0,
               PendingIntent.FLAG_UPDATE_CURRENT
       );
notifBuilder.setContentIntent(resultPendingIntent);
notifBuilder.setAutoCancel(true);//agar setelah ditap tutup otomatis

//notifmanager untuk menampilkan
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 999 id untuk jika perlu modif nanti (bagusnya jadi konstanta).
mNotificationManager.notify(999, notifBuilder.build());

}

Menampilkan Notifikasi dengan Progressbar

Untuk proses yang panjang, dari sisi user interface akan lebih bagus jika user mendapat informasi kemajuan proses seperti gambar dibawah:

screen3

Code untuk membuat notification dengan progressbar adalah sebagai berikut. Ubah project sebelumnya dibagian loop.

....
//proses background ada disini
mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

//init notification manager
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);


//notifikasi dengan progressbar
NotificationCompat.Builder notifBuilderProgress =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Progress")
               .setContentText("Sedang proses");

//loop proses yang panjang
for (int i = 0; i<100;i++) {
   try {
       Thread.sleep(50);  //simulasi proses yang panjang
       //max diisi 100, false artinya kita tahu selesainya kapan
       notifBuilderProgress.setProgress(100, i, false);
       // tampilkan progress bar
       // 998 adalah id, bisa diganti dengan nilai lain
       mNotificationManager.notify(998, notifBuilderProgress.build());
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
}

// update notif
// buang progress bar
notifBuilderProgress.setContentText("Proses selesai").setProgress(0,0,false);
mNotificationManager.notify(998, notifBuilderProgress.build());

mHandler.post(new DisplayToast(this, "Service selesai"));
...

Video contoh service dengan notification [youtube]: https://youtu.be/XE4HXXDNCpI

Menerima Status IntentService sudah selesai

Terkadang kita memerlukan informasi bahwa service sudah selesai. Masalahnya, tidak ada hubungan langsung antara service dengan activity yang memanggilnya. Untuk ini, dapat digunakan LocalBroadcastManager. Jadi service akan mengirimkan Intent, dan activity akan “menangkapnya”.

Menggunakan contoh tutorial IntentService sebelumnya, kita perlu ubah MainActivity agar dapat menangkap broadcast yang dikirim oleh service. Buat kelas yang meng-extends BroadcastReceiver. Method onReceive() berisi penanganan jika menerima broadcast dari service. Pada contoh ini, saat kita menerima broadcast, yang akan kita lakukan adalah menampilkan pesan yang dikirimkan service.

Kemudian pada onResume, kita mendaftarkan receiver dan meng-unregister-nya pada saat app di pause.

public class MainActivity extends AppCompatActivity {
   Intent serviceIntent;
   TextView tvHasil;

   //konstanta yang mendefinisikan aksi
   public static final String  ACTION_TERIMA = "aksi-cobaservice";

   //definisikan aksi jika mendapat broadcast
   private  class MyBroadcastReceiver extends BroadcastReceiver {
       //terima pesan, update UI
       //tapi jika app berada di background, tidak akan terupdate
       @Override
       public void onReceive(Context context, Intent intent) {
           // ambil extra data yang ada Intent
           String pesan = intent.getStringExtra("PESAN");
           //tulis pesan di textview
           tvHasil.setText(pesan);
       }
   }

   private MyBroadcastReceiver mReceiver;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     tvHasil = (TextView) findViewById(R.id.tvHasil);
     mReceiver = new MyBroadcastReceiver();
}

@Override
protected void onResume() {
   // onResume juga dipanggil saat app dimulai
   // daftar broadcastreciver untuk menerima intent.
   // tapi khusus untuk action ACTION_TERIMA yang sudah kita definisikan
   LocalBroadcastManager.getInstance(this).registerReceiver(
           mReceiver, new IntentFilter(ACTION_TERIMA));
   super.onResume();
}

@Override
protected void onPause() {
   // Unregister karena activity dipause.
   LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
   super.onPause();
}

   
   //...	
   //yang lain sama dengan sebelumnya   
   //...
   
}

Sekarang kita akan membuat service mengirimkan pesan. Kelas yang digunakan adalah LocalBroadcastManager. Menggunakan contoh sebelumnya, coba buka class CobaIntentService, tambahkan code berikut di onHandleIntent setelah proses berakhir:

@Override
protected void onHandleIntent(Intent intent) {
   //...	
   //sama dengan sebelumnya   
   //...

   //setelah proses selesai, kirim pesan ke MainActivity bahwa service sudah selesai
   
   //kirim pesan ke activity
   Intent localIntent = new Intent(MainActivity.ACTION_TERIMA);
   localIntent.putExtra("PESAN", "Sudah selesai!");
   //menggunakan LocalBroadCastManager 
   //agar pengiriman dijamin tidak bisa dilihat app lain
   LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}

Jalankan app, maka saat sudah selesai textview akan terupdate dengan pesan selesai. Tetapi kelemahannya jika app berada di background, maka textview tidak akan terupdate.

Source code untuk tutorial intentservice ini: https://github.com/yudiwbs/CobaIntentService

Grabtaxi di Bandung

Update Mei 2016: Pengalaman saya menggunakan Gojek

Update Maret 2016:  Pengalaman saya menggunakan app Bluebird.

Lanjutan dari posting pengalaman menggunakan Uber

Update Mei 16: GrabCar sudah ada di Bandung (gambar bawah). Tapi masih sedikit sepertinya, belum saya coba. User interface juga lebih baik, sekarang peta flat dan sudah menunjuk ke lokasi yang memang ada dekat rumah.

grab-car

Update Mar 16: selain Taksi Cipaganti, Ekspress sekarang sudah ada taksi Gemah Ripah yang menggunakan Grabtaxi. Katanya memang sopir gemahripah masih jarang yang menggunakan ini.

Saya sudah dengar grabtaxi dari lama, cuma setiap kali install, belum ada taksi yang tersedia di Bandung. Hari ini, iseng istri menginstall dan sudah ada taksinya! 🙂   Masih sedikit taksi yang tersedia tapi biasanya nanti akan banyak juga. Tampilan peta-nya dimiringkan, saya sebenarnya lebih suka yang datar 2D saja. Lokasi pickup otomatis disesuaikan ke tempat-tempat yang umum seperti restoran.  Di situs grabtaxi.com, Bandung belum masuk. Saat saya tanya via Twitter tidak ada jawaban, ini berbeda dengan Twitter Uber yang lebih responsif. Ya sudah, langsung coba saja.

Istri akhirnya mencoba grabtaxi. Percobaan pertama dari rumah gagal, masalahnya grabtaxi mewajibkan lokasi pickup yang berlabel. Mungkin kalau tempatnya populer tidak masalah, tapi kalau perumahan?  Seperti contoh (gambar bawah), tidak ada yang namanya Pondok Seafood Sarijadi di sekitar rumah saya. Seingat saya ini sudah lama tutup.  Jadi sebelum pesan kita harus bergerak ke tempat yang ‘populer’. Tapi tempat makan yang besar di sekitar rumah kami justru malah tidak ada di GrabTaxi :(.

 

grabtaxi

Percobaan kedua dari kampus ITB. Untuk yang ini berhasil, pada lokasi pickup tersedia “Teknik Informatika”.  Setelah memasukkan tujuan, langsung muncul info sopir yang akan menjemput (taksi Cipaganti). Nah masalahnya, ternyata lokasi taksi di grabtaxi tidak seperti Uber yang realtime terlihat bergerak. Lokasinya lama diam dan tiba-tiba meloncat, apa ini karena supir taksinya meletakkan HP di tempat yang tidak terjangkau sinyal GPS? Tapi memang dari awal, berbeda dengan Uber yang posisi mobilnya terlihat aktif bergerak, posisi taksi di grabtaxi  sepertinya lebih statik (mungkin karena taksi memang lebih banyak ngetem dibandingkan Uber?).

OK, kembali ke pemesanan, karena lama menunggu, istri menelepon dan supir bilang sudah sampai ke gedung Teknik Informatika dan Elektro, ternyata supir nyasar satu gedung ke Labtek 8 (Elektro), padahal istri di labtek 5 (Informatika). Tidak jauh sih, tinggal jalan sedikit saja. Tapi ini berarti supir juga melihat lokasi bukan posisi eksak seperti Uber, tetapi dari label. Labtek 8 memang memiliki tulisan Teknik Informatika dan Elektro (kelemahan ITB memang nama gedung tidak jelas, tidak ada plang di depan gedung).

Pembayaran seperti halnya taksi standar yang menggunakan argo. Kalau misalnya ada potongan discount apa kita yang harus tanya? apa sopir yang bilang ya?  Kalau Uber, dengan kartu kredit jadi praktis, tidak perlu menyiapkan uang.

Kesimpulan: label lokasi penjemputan sangat penting di GrabTaxi, berbeda dengan Uber yang menggunakan posisi eksak. Di satu sisi ini mungkin menguntungkan karena banyak orang yang tidak melek peta dan teknologi sehingga bisa saja saat memesan lokasinya melenceng, tapi  di sisi lain, repot kalau di tempat penjemputan tidak ada label lokasi yang pas. Perbedaan lain: Grabtaxi menggunakan taksi resmi dan Uber menggunakan mobil biasa plat hitam. Taksi punya kelebihan tidak terkena aturan 4 in 1, dan dapat masuk ITB dengan bebas (uber kadang bisa masuk ITB tapi kadang dicegat satpam). Sedangkan kelemahan Grabtaxi adalah tidak dapat menggunakan kartu kredit, harus tunai, sedangkan Uber justru sebaliknya walaupun sekarang katanya Uber sudah bisa menerima pembayaran tunai.

 

Android Thread (4): Java Thread

Jika pertama kali menggunakan thread, dianjurkan untuk membaca dulu bagian pertama yang membahas pendahuluan tentang thread lalu mencoba bagian kedua tentang implementasi thread yang paling sederhana, AsyncTask terlebih dulu.

Cara lain melakukan proses di background adalah menggunakan thread seperti halnya yang disediakan Java. Cara ini lebih fleksibel daripada AsyncTask, tetapi lebih sulit. Sebenarnya Asynctask, IntentService dan lain-lain dibuat menggunakan library java thread ini.

Untuk membuat thread baru, dapat digunakan kelas Thread. Ada dua cara menjalankan thread, pertama dengan menggunakan objek Runnable, kedua dengan meng-override method Run di kelas Thread. Contoh berikut akan lebih menjelaskan (baca saja contohnya, tidak perlu dibuat).

Cara pertama, menggunakan Runnable. Runnable adalah kelas yang berisi potongan code yang harus dijalankan. Berikut contoh class yang meng-implement Runnable:

public class MyRunnable implements Runnable {
   public void run() {
       //code yang akan dijalankan
   }
}

Lalu buat objek thread, dan jadikan objek runnable tersebut menjadi parameter

//versi panjang:
MyRunnable r  = new MyRunnable();
Thread t = new Thread(r); //runnnable jadi parameter
t.start();  //thread dimulai

Cara yang lebih singkat dan lebih umum adalah dengan anonymous class

//versi pendek dengan anonymous class
(new Thread(new MyRunnable())).start();

Cara yang kedua adalah tanpa runnable, dengan meng-extends kelas Thread. Contohnya sebagai berikut:

public class MyThread extends Thread {
   public void run() {
       //code yang akan dijalankan
   }
}

Dan untuk menjalankannya:

(new MyThread()).start();

Cara pertama dengan Runnable lebih umum digunakan karena tidak perlu meng-extends class Thread.

Di Android, untuk mengupdate UI, harus menggunakan method View.post dengan parameter kelas Runnable. Lebih jelasnya dapat dilihat di contoh berikut.

Kita akan membuat app seperti tutorial AsyncTask sebelumnya. Pada app tersebut terdapat satu button yang jika ditekan akan menjalankan 100 loop yang setiap loopnya membutuhkan waktu cukup lama. Setiap akhir loop, kemajuan akan terlihat di progressbar. Isilah onclick pada button sebagai berikut:

ProgressBar pb;
int progress=0;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   pb = (ProgressBar) findViewById(R.id.progressBar);
}

public void onClick(View v) {
   //jika diklik, jalankan thread 
   new Thread(new Runnable() {
       public void run() {
	   //ini proses yg di background	 
           for (int i = 0; i<100;i++) {
               try {
                   Thread.sleep(50);  //delay 0.05 detik, simulasikan proses panjang
                   progress = i;
                   //update user interface harus menggunakan View.post(Runnable)
		   //pb adalah progressbar	
                   pb.post(new Runnable() {
                       public void run() {
                           pb.setProgress(progress);
                       }
                   });
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }).start();
}

Latihan: dari program diatas, bagaimana kita menambahkan tulisan “mulai” saat proses bacground dimulai dan “sukses” saat selesai?

 

Handler

Cara lain untuk berkomunikasi dengan UI thread adalah dengan Handler.  Pada contoh diatas kita menggunakan ProgressBar.post(), tetapi semakin kompleks interaksi antara thread  dan UI maka code akan semakin sulit dibaca. Handler dapat digunakan untuk mengatasinya.

Handler adalah objek yang digunakan untuk berkomunikasi antar thread.  Di UI thread kita buat handler, lalu objek handler ini di-pass ke thread. Thread kemudian berkirim pesan melalui handler.

Baik kita langsung buat saja. Pertama buat kelas baru yang berisi thread yang akan memproses dibackground. Kelas ini (ThreadDgnHandler) mengextends kelas Thread. Kelas ini menerima parameter Handler dari UI. Untuk mengirim informasi, gunakan method .obtainMessage() dan .sendToTarget() dari Handler.

public class ThreadDgnHandler extends Thread {
    private final Handler mHandler; //handler dari UI

    //constructor
    public ThreadDgnHandler(Handler mHandler) { 
        this.mHandler = mHandler;
    }

    public void run() {
        //kirim pesan mulai, update ke UI
        //1: konstanta "1" artinya pesan untuk update textivew. konstanta bebas
        //-1: paramter 1, tidak digunakan
        //-1: paramter 2, tidak digunakan
        //"Mulai": paramter 3, bisa objek apapun
        mHandler.obtainMessage(1, -1, -1, "Mulai").sendToTarget();

        //loop peroses background
        for (int i = 0; i<100;i++) {
            try {
                Thread.sleep(50);  //delay 0.05 detik
                //2: konstanta "2" artinya update progres barr
                //i: paramter 1, progressnya
                //paramter yang lain tidak digunakan
                mHandler.obtainMessage(2, i, -1,null).sendToTarget();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //kirim pesan selesai
        mHandler.obtainMessage(1, -1, -1, "Selesai").sendToTarget();

    }
}

Selanjutnya buat objek handler di main activity. Handler ini akan mengupdate textview dengan “mulai””dan “selesai” sekaligus progressbar. Karena handler ini dicreate di UI thread maka handler juga akan berjalan di UI thread dan dapat digunakan untuk mengupdate UI.

    /**
     * Handler yang mendapat informasi dari ThreadDgnHandler
     * Handler ini ada di UI thread, jadi dapat digunakan untuk mengupdate UI
     */
    private final Handler mHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                //1: artinya update textview
                //2: artinya update progressbar
                if (msg.what==1) {
                    String pesan = (String) msg.obj;
                    tvHasil.setText(pesan);
                } else
                if (msg.what==2) {
                    pb.setProgress(msg.arg1); //arg1 berisi progress
                }
            }
        };

Sekarang buat tombol yang menggunakan handler (gambar bawah), jangan lupa hubungkan onClicknya dengan method berikut:

ScreenHunter_158 Dec. 12 21.22


 public void klikDgnHandler(View v) {
        //memproses dengan handler
        //kalau contoh sebelumnya menggunakan View.post
        //kalau ini dengan handler

        //handler dipassing jadi paramter
        ThreadDgnHandler tdh = new ThreadDgnHandler(mHandler);
        tdh.start();
 }

Terlihat code lebih bersih dan mudah dibaca dibandingkan dengan contoh pertama.

 

 

 

 

 

 

Source code untuk tutorial ini: https://github.com/yudiwbs/CobaThread

 

Android Thread (2): AsyncTask

Update versi terakhihr (google doc): https://docs.google.com/document/d/1K03-kHsokJ4aE5u2Ec4cxsaYFubheIAlJ8DqgmrvW_s/edit?usp=sharing

Catatan: jika belum mengerti tentang thread, baca dulu bagian 1.

AsyncTask adalah class yang disediakan Android untuk memudahkan  programmer membuat task yang dijalankan di-background. AsyncTask cocok untuk proses background yang relatif  sederhana dan pendek. Misalnya saat mendowload objek dari internet, dan dalam prosesnya UI akan menampilkan kemajuan download.

Sebelum memulai tutorial, ada empat method terpenting dalam AsyncTask.

  1. onPreExecute() dipanggil sebelum task dikerjakan. Biasanya untuk menginisasi user interface.
  2. doInBackground(Params…), berisi task inti yang perlu dijalankan di background dan berpotensi memblok UI. Method ini akan dijalankan di thread background langsung setelah onPreExecute dipanggil. Gunakan  publishProgress(Progress…) untuk mengupdate UI mengenai progress (misal mengupdate progressbar). JANGAN update UI dimethod ini! gunakan onProgressUpdate.
  3. onProgressUpdate(Progress…)  dipanggil setelah publisProgress dijalankan. Berisi code untuk mengupdate user interface.  JANGAN panggil onProgressUpdate langsung!  gunakan publishProgress (tanpa on). Method ini berjalan di UI thread sehingga aman untuk memanipulasi komponen UI.
  4. onPostExecute(Result), dapat digunakan untuk mengupdate user interface setelah task background selesai. Hasil dari komputasi dipassing sebagai parameter.

Supaya lebih jelas kita akan membuat contoh app yang jika tombolnya ditekan maka akan mensimulasikan task yang cukup lama (100 loop yang setiap loopnya berisi sleep beberapa milidetik). Saat setiap satu loop berakhir maka progressbar akan diupdate.

Pertama, tambahkan tiga komponen view seperti gambar di bawah. Satu label, satu progress bar (horizontal) dan satu button:

ScreenHunter_123 Nov. 25 23.15
Tambahkan code berikut, yang akan dijalankan saat button ditekan.

public void klikMulai(View v) {
   //button ditekan
   pb.setMax(100);
   new KerjakanBackgroundTask().execute(); //tidak ada parameter
}

Jangan lupa set property dari button:

ScreenHunter_124 Nov. 25 23.18

Tambahkan code berikut, perhatikan penggunaan extends AsyncTask. Harap baca penjelasan dalam comment.

private class KerjakanBackgroundTask extends AsyncTask&lt;Void, Integer, String&gt; 
/*
 Tiga parameter (Void, Integer, String): 
1. Parameter ke background task; 
2. Pogress saat background task dijalankan
3. Result hasil dari background task
Untuk kasus di app ini (untuk app lain bisa berbeda):
1.Param ke bacground task: Void (tidak ada)
2.Progress saat background task dijalankan: Integer (setiap loop diincrement dan dikirim ke progressbar
3.Result: String (output)
*/
{
   //sebelum proses
   protected void onPreExecute() {
       pb.setProgress(0);
       tvHasil.setText("Mulai");
   }

   //proses
   protected String doInBackground(Void... v) {
       String hasil="-";
       try {
           for (int i = 0; i&lt;100;i++) {
               Thread.sleep(50);  //delay 0.05 detik biar lama
               //update user interface dengan publichProgress
               //JANGAN ada update user interface langsung
               publishProgress(i);
               if (isCancelled()) {  
                   hasil = "batal";
                   break; //user bisa cancel ditengah task
               }
           }
           hasil = "Sukses";
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return hasil;  //result
   }

   //progress
   protected void onProgressUpdate(Integer... progress) {
   //Update user interface disini karena ini berjalan di UI thread
   //Integer... (tiga titik) artinya parameter bisa inteteger lebih dari satu
   //dan bentuknya array
   //jangan panggil onProgress langsung, gunakan publichProgress!

       pb.setProgress(progress[0]);
   }

   
   protected void onPostExecute(String result) {
   //selesai, bisa update user interface karena ini berjalan di UI thread
       tvHasil.setText(result);
   }
}

Jika dijalankan dan button ditekan, maka progressbar akan bergerak sedikit demi sediikit, dan setelah selesai maka textview akan diisi dengan hasil (string “Sukses”). Result dapat diganti tipenya menjadi integer atau float.

Kelemahan Asynctask adalah jumlah task dalam satu app terbatas. Asynctask juga terkait dengan Activity, sehingga dapat bermasalah jika Activity di-destroy (misal saat device di rotate).

Source code tutorial ini: https://github.com/yudiwbs/AsyncTask_Demo

Lanjut ke materi thread berikutnya: IntentService

todo:loader untuk masalah activity yang didestroy.