Belajar Memakai Implicit Intent Di Android


Sebagai seorang pendatang baru di pemograman Android, salah satu hal unik yang saya temui adalah sebuah Activity dapat memanggil Activity lain di aplikasi berbeda dengan mudah melalui apa yang disebut implicit intent. Hal ini mirip seperti late binding pada DLL. Bedanya, pada sebuah perangkat Android, bisa jadi ada lebih dari satu Activity dari aplikasi berbeda yang bisa menangani sebuah implicit intent yang sama. Pengguna bisa menentukan sendiri Activity mana yang akan dipakai.

Sebagai latihan, saya akan membuat sebuah aplikasi Android dengan Groovy untuk menampilkan isi sebuah file dalam bentuk hexadecimal. Langkah pertama yang saya lakukan adalah membuat sebuah proyek baru Android seperti yang saya lakukan pada artikel Membuat Aplikasi Android Dengan Groovy.

Setelah itu, saya membuat sebuah Activity dengan nama MainActivity yang isinya seperti berikut ini:

package snake.com.myhexviewer

import ...

@CompileStatic
public class MainActivity extends Activity {

    private static final int READ_REQUEST_CODE = 42
    private static final int OUTPUT_VIEW_ID = View.generateViewId()

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        TextView output = new TextView(this)
        output.setHorizontallyScrolling(true)
        output.setMovementMethod(new ScrollingMovementMethod())
        output.setTypeface(Typeface.MONOSPACE)
        output.setTextColor(Color.GREEN)
        output.setId(OUTPUT_VIEW_ID)
        setContentView(output)
    }

    @Override
    boolean onCreateOptionsMenu(Menu menu) {
        MenuItem menuItem = menu.add("Buka File")
        menuItem.setIcon(android.R.drawable.ic_menu_edit)
        menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
        menuItem.onMenuItemClickListener = { MenuItem m ->

            // Pilih file yang hendak dibaca
            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            intent.setType('*/*')
            startActivityForResult(intent, READ_REQUEST_CODE)
            true

        } as MenuItem.OnMenuItemClickListener
        true
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if ((requestCode == READ_REQUEST_CODE) && (resultCode == Activity.RESULT_OK)) {
            tampilkan(data)
        }
    }

    private void tampilkan(Intent data) {
        // proses disini!
    }
}

Karena ini adalah Activity yang dijalankan pertama kali secara otomatis, maka saya menambahkan isi berikut ini pada AndroidManifest.xml:

<application android:allowBackup="true" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
  <activity android:name=".MainActivity">
    <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
</application>

Sekarang, bila saya menjalankan aplikasi, saya akan memperoleh sebuah menu di action bar yang saya buat di onCreateOptionsMenu(). Tampilannya akan terlihat seperti pada gambar berikut ini:

Tampilan awal aplikasi

Tampilan awal aplikasi

Bila saya men-klik menu di action bar, maka closure yang saya berikan di onMenuItemClickListener akan dikerjakan. Kode program ini akan memakai Storage Access Framework melalui implicit intent Intent.ACTION_OPEN_DOCUMENT. Ini adalah fitur baru sejak Android 4.4 (KitKat) yang memungkinkan pengguna memilih file yang ada di perangkat termasuk dari Google Drive, seperti yang terlihat pada gambar berikut ini:

Membuka file dengan Intent.ACTION_OPEN_DOCUMENT

Membuka file dengan Intent.ACTION_OPEN_DOCUMENT

Walaupun activity untuk memilih file ini bukan bawaan dari aplikasi yang saya buat, ia tetap dapat dipanggil dengan menggunakan Intent.ACTION_OPEN_DOCUMENT. Android menyediakan banyak implicit intent lainnya seperti Intent.ACTION_IMAGE_CAPTURE, Intent.ACTION_SEND, Intent.ACTION_VIEW dan sebagainya. Jangan lupa bahwa bisa saja pada perangkat tertentu, tidak ada activity yang dapat menangani implicit intent yang diberikan.

Setelah activity untuk memilih file ditampilkan dan user memilih sebuah file, maka kode program di onActivityResult() akan dikerjakan. Pada event listener tersebut, saya akan memanggil tampilkan() untuk mengisi TextView yang ada. Untuk itu, saya mengubah isi method tampilkan() menjadi seperti berikut ini:

private void tampilkan(Intent data) {
  Uri uri = data.getData()
  byte[] bytes = getContentResolver().openInputStream(uri).bytes
  StringBuilder hexdump = new StringBuilder()
  StringBuilder ascii = new StringBuilder()
  int i
  for (i=0; i<bytes.length; i++) {
    hexdump.append(String.format("%02x", bytes[i]))
    hexdump.append(' ')
    ascii.append(Character.isLetterOrDigit(bytes[i])?(char)bytes[i]: '.')
    if ((i > 0) && (((i+1) % 8) == 0)) {
      hexdump.append(ascii.toString())
      hexdump.append('\n')
     ascii = new StringBuilder()
    }
  }
  if (ascii) {
    (1..(8-((i-1)%8))).each { hexdump.append('   ') }
    hexdump.append(ascii.toString())
  }
  TextView output = (TextView) findViewById(OUTPUT_VIEW_ID)
  output.setText(hexdump.toString())
}

Pada kode program di atas, saya memakai getBytes() dari Groovy untuk langsung memperoleh byte[] dari sebuah InputStream. Versi yang memakai Java akan lebih panjang lagi. Bila saya menjalankan program ini dan memilih sebuah file, saya akan memperoleh hasil seperti pada gambar berikut ini:

Tampilan hasil program

Tampilan hasil program

Kode program ini belum memakai thread terpisah sehingga program akan terlihat seperti hang dan tidak responsif bila dipakai untuk membuka file dengan ukuran besar. Perhatikan juga berkat Intent.ACTION_OPEN_DOCUMENT, saya bisa menampilkan isi dari file yang tersimpan di Google Drive.

Selain berperan untuk memakai implicit intent, sebuah aplikasi juga bisa memiliki activity yang akan menangani implicit intent dari activity lain di aplikasi yang sama maupun aplikasi berbeda yang dibuat orang lain. Sebagai latihan, saya akan menambahkan sebuah activity yang akan menangani Intent.ACTION_SEND dari aplikasi lain. Untuk itu, saya membuat activity baru seperti berikut ini:

package snake.com.myhexviewer

import ...

@CompileStatic
public class SendHandlerActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        Intent mainActivityIntent = new Intent(this, MainActivity)
        mainActivityIntent.data = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM)
        startActivity(mainActivityIntent)
    }

}

Kode program di atas akan memanggil MainActivity dan mengirimkan Uri kepadanya melalui setData(). Oleh sebab itu, saya perlu menambahkan sedikit baris di MainActivity.onCreate() di bagian akhir:

if (intent.data) {
  tampilkan(intent)
}

Setelah itu, saya mendaftarkan activity baru ini di AndroidManifest.xml seperti berikut:

<activity android:name=".SendHandlerActivity">
  <intent-filter>
     <action android:name="android.intent.action.SEND" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:mimeType="*/*" />
  </intent-filter>
</activity>

Untuk mengujinya, saya akan membuka sebuah gambar yang dikirimkan kepada saya melalui aplikasi BBM dan memilih menu share. Kini, aplikasi latihan yang saya buat akan muncul seperti yang terlihat pada gambar berikut ini:

Memakai Intent filter

Memakai Intent filter

Walaupun demikian, proses pengiriman data tidak berlangsung dengan sukses. Saya memperoleh pesan kesalahan seperti berikut ini:

Caused by: java.io.FileNotFoundException: /storage/emulated/0/Pictures/BBM/IMG_20150210_120722.jpg: open failed: EACCES (Permission denied)     

Hal ini terjadi karena aplikasi yang saya buat tidak memiliki akses untuk membaca file di penyimpanan eksternal. Setiap aplikasi Android harus mendaftarkan permission yang dibutuhkannya pada AndroidManifest.xml dan nantinya pengguna perangkat yang akan menentukan apakah akan men-install aplikasi tersebut. Setelah di-install, permission sebuah aplikasi tidak bisa diubah lagi. Untuk itu, saya menambahkan baris berikut ini pada AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Sekarang, aplikasi saya bisa menerima file yang dikirim dari aplikasi lain dengan baik.

Sebagai latihan lebih lanjut, saya akan menambahkan sebuah fasilitas untuk mengirim hasil hexdump ke aplikasi lainnya yang bisa menerima data dalam bentuk plain/text. Untuk itu, saya menambahkan kode program berikut ini pada MainActivity.onCreateOptionsMenu() sebelum mengembalikan nilai true:

menuItem = menu.add("Bagikan")
menuItem.setIcon(android.R.drawable.ic_menu_share)
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
menuItem.onMenuItemClickListener = { MenuItem m ->

  // Kirim hasil hexdump melalui Intent.ACTION_SEND
  String hexdump = ((TextView) findViewById(OUTPUT_VIEW_ID)).getText()
  Intent sendIntent = new Intent(Intent.ACTION_SEND)
  sendIntent.putExtra(Intent.EXTRA_TEXT, hexdump)
  sendIntent.setType('text/plain')
  startActivity(sendIntent)
  true

} as MenuItem.OnMenuItemClickListener

Kode program di atas akan membuat sebuah icon baru di action bar seperti yang terlihat pada gambar berikut ini:

Membuat menu share di action bar

Membuat menu share di action bar

Bila saya menyentuh icon tersebut, saya bisa memilih aplikasi yang menangani Intent.ACTION_SEND untuk tipe data text/plain seperti yang terlihat pada gambar berikut ini:

Daftar activity yang dapat menangani Intent.ACTION_SEND

Daftar activity yang dapat menangani Intent.ACTION_SEND

Selain membuat menu sendiri, saya juga bisa menggunakan ShareActionProvider yang tersedia sejak Android 4.0. Sebagai contoh, saya bisa menambahkan sebuah property pada class MainActivity seperti:

private ShareActionProvider shareActionProvider

Setelah itu, saya mengubah kode program yang membuat menu bagikan menjadi seperti berikut ini:

menuItem = menu.add('Bagikan')
menuItem.setIcon(android.R.drawable.ic_menu_share)
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
shareActionProvider = new ShareActionProvider(this)
menuItem.setActionProvider(shareActionProvider)

Terakhir, saya perlu senantiasa memperbaharui intent yang diasosiasikan dengan ShareActionProvider. Karena hasil hexdump akan berubah setiap kali tampilkan() dipanggil, maka saya menambahkan baris program berikut ini di akhir method tersebut:

Intent sendIntent = new Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_TEXT, hexdump.toString())
sendIntent.setType('text/plain')
shareActionProvider.setShareIntent(sendIntent)

Sekarang, setelah menjalankan program dan menampilkan hexdump, bila saya menyentuh icon bagikan, saya akan memperoleh hasil seperti berikut ini:

Menu yang memakai ShareActionProvider

Menu yang memakai ShareActionProvider

Bila saya memilih menu Lihat Semua, akan ada lebih banyak submenu yang muncul yang berisi daftar activity yang bisa menangani implicit intent yang saya buat. Terlihat bahwa penggunaan implicit intent memudahkan aplikasi saya dalam berkomunikasi dengan aplikasi lain.

Tentang Solid Snake
I'm nothing...

3 Responses to Belajar Memakai Implicit Intent Di Android

  1. Ping-balik: Multithreading Di Android | The Solid Snake

  2. Ping-balik: Melakukan Unit Testing Di Android | The Solid Snake

  3. Admin Situs says:

    Misal ada url WA Kalau biar langsung jalan ke aplikasi gimana gan?

Apa komentar Anda?