7.2: Connect to the Internet with AsyncTask and AsyncTaskLoader
Task 1. Explore the Books API
1.1 Send a Books API Request
- Buka Google APIs Explorer (bisa ditemukan di https://developers.google.com/apis-explorer/).
- Klik Books API.
- Temukan (Ctrl-F atau Cmd-F) books.volumes.list dan klik nama fungsi tersebut. Anda seharusnya bisa melihat laman web yang mencantumkan berbagai parameter fungsi API Books yang melakukan penelusuran buku.
- Dalam bidang
q
masukkan nama buku, atau sebagain nama buku. Parameterq
adalah satu-satunya bidang yang diwajibkan. - Gunakan bidang
maxResults
danprintType
untuk membatasi hasil ke 10 buku yang cocok yang dicetak. BidangmaxResults
mengambil nilai integer yang membatasi jumlah hasil per kueri. BidangprintType
mengambil satu dari tiga argumen string:all
, yang tidak membatasi hasil menurut tipe sama sekali;books
, yang hanya memberikan hasil buku dalam bentuk cetak; danmagazines
yang memberikan hasil majalah saja. - Pastikan switch “Authorize requests using OAuth 2.0” di bagian atas formulir dinonaktifkan. Klik Execute without OAuth di bagian bawah formulir.
- Gulir ke bawah untuk melihat Permintaan dan Respons.
Bidang Request
adalah contoh Uniform Resource Identifier (URI). URI adalah string yang memberikan nama atau menemukan sumber daya tertentu. URL adalah tipe URI tertentu untuk mengidentifikasi dan menemukan sumber daya web. Untuk API Books, permintaannya adalah URL yang berisi penelusuran sebagai parameter (mengikuti parameter q
).
1.2 Analyze the Books API Response
Di bagian bawah laman Anda bisa melihat Respons terhadap kueri. Respons menggunakan format JSON, yang merupakan format umum untuk respons kueri API. Dalam laman web API Explorer, kode JSON diformat dengan baik agar dapat dibaca oleh manusia. Dalam aplikasi Anda, repons JSON akan dikembalikan dari layanan API sebagai string tunggal, dan Anda perlu melakukan parsing pada string tersebut untuk mengesktrak informasi yang diperlukan.
- Dalam bagian Respons, temukan nilai untuk kunci “title”. Perhatikan bahwa hasil ini memiliki nilai dan kunci tunggal.
- Termukan nilai untuk kunci “authors”. Perhatikan bahwa kunci ini berisi larik nilai.
- Dalam praktik ini Anda hanya akan mengembalikan judul dan penulis item pertama.
Task 2. Create the “Who Wrote It?” App
2.1 Create the project and user interface
- Buat proyek aplikasi bernama Who Wrote it? dengan satu aktivitas, menggunakan Template Empty Activity.
- Tambahkan elemen UI berikut di dalam file XML, menggunakan LinearLayout vertikal sebagai tampilan root—tampilan yang berisi semua tampilan lain di dalam file XML layout. Pastikan LinearLayout menggunakan
android:orientation="vertical"
:
Tampilan
Atribut
Nilai
TextView
android:layout_width
android:layout_height
android:id
android:text
android:textAppearance
wrap_content
wrap_content
@+id/instructions
@string/instructions
@style/TextAppearance.AppCompat.Title
EditText
android:layout_width
android:layout_height
android:id
android:inputType
android:hint
match_parent
wrap_content
@+id/bookInput
text
@string/input_hint
Button
android:layout_width
android:layout_height
android:id
android:text
android:onClick
wrap_content
wrap_content
@+id/searchButton
@string/button_text
searchBooks
TextView
android:layout_width
android:layout_height
android:id
android:textAppearance
wrap_content
wrap_content
@+id/titleText
@style/TextAppearance.AppCompat.Headline
TextView
android:layout_width
android:layout_height
android:id
android:textAppearance
wrap_content
wrap_content
@+id/authorText
@style/TextAppearance.AppCompat.Headline
- Dalam file strings.xml, tambahkan sumber daya string berikut ini:
<string name="instructions">Enter a book name, or part of a book name, or just some text from a book to find the full book title and who wrote the book!</string> <string name="button_text">Search Books</string> <string name="input_hint">Enter a Book Title</string>
- Buat metode bernama
searchBooks()
dalam MainActivity.java untuk menangani tindakan tombol onClick. Seperti pada semua metode onClick, yang satu ini memerlukanView
sebagai parameter.
2.2 Set up the Main Activity
Untuk menanyakan API Books, Anda perlu mendapatkan masukan pengguna dari EditText.
- Dalam MainActivity.java, buat variabel anggota untuk EditText, TextView penulis dan TextView judul.
- Inisialisasi variabel ini dalam
onCreate()
. - Dalam metode
searchBooks()
, dapatkan teks dari widget EditText dan konversikan keString
, menetapkannya ke variabel string.String queryString = mBookInput.getText().toString();
2.3 Create an empty AsyncTask
Sekarang Anda siap untuk terhubung ke internet dan menggunakan Book Search REST API. Konektivitas jaringan terkadang lamban atau mengalami penundaan. Hal ini bisa menyebabkan aplikasi menjadi tidak menentu dan lambat, jadi sebaiknya Anda tidak membuat koneksi jaringan pada thread UI.
Gunakan AsyncTask untuk membuat koneksi jaringan:
Buat kelas Java baru bernama FetchBook
dalam aplikasi/java yang diperluas AsyncTask
. AsyncTask memerlukan tiga argumen:
- Parameter masukan.
- Indikator kemajuan.
- Jenis hasil.
2.4 Create the NetworkUtils class and build the URI
- Buat kelas Java baru bernama NetworkUtils dengan mengeklik File > New > Java Class dan hanya mengisi bidang “Name”.
- Buat variabel
LOG_TAG
unik untuk digunakan di semua kelas NetworkUtils untuk membuat catatan log:private static final String LOG_TAG = NetworkUtils.class.getSimpleName();
- Buat metode statis baru bernama
getBookInfo()
yang mengambilString
sebagai parameter (yang akan menjadi istilah penelusuran) dan mengembalikanString
(respons String JSON dari API yang Anda periksa sebelumnya).static String getBookInfo(String queryString){}
- Buat dua variabel lokal berikut dalam
getBookInfo()
yang akan dibutuhkan nanti untuk membantu menyambungkan dan membaca data yang datang.HttpURLConnection urlConnection = null; BufferedReader reader = null;
-
Buat variabel lokal lain di akhir
getBookInfo()
untuk memasukkan respons mentah dari kueri dan mengembalikannya:String bookJSONString = null; return bookJSONString;
- Buat konstanta anggota berikut dalam kelas NetworkUtils:
private static final String BOOK_BASE_URL = "https://www.googleapis.com/books/v1/volumes?"; // Base URI for the Books API private static final String QUERY_PARAM = "q"; // Parameter for the search string private static final String MAX_RESULTS = "maxResults"; // Parameter that limits search results private static final String PRINT_TYPE = "printType"; // Parameter to filter by print type
- Buat blok try/catch/finally skeleton dalam
getBookInfo()
. Di sinilah Anda akan membuat permintaan HTTP. Kode untuk membangun URI dan mengeluarkan kueri akan masuk ke dalam blok try. - Bangun URI permintaan dalam blok try:
//Build up your query URI, limiting results to 10 items and printed books Uri builtURI = Uri.parse(BOOK_BASE_URL).buildUpon() .appendQueryParameter(QUERY_PARAM, queryString) .appendQueryParameter(MAX_RESULTS, "10") .appendQueryParameter(PRINT_TYPE, "books") .build();
- Konversi URI ke URL:
URL requestURL = new URL(builtURI.toString());
2.5 Make the Request
- Dalam blok try metode
getBookInfo()
, buka koneksi URL dan buat permintaan.urlConnection = (HttpURLConnection) requestURL.openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.connect();
- Baca respons menggunakan InputStream dan StringBuffer, lalu konversikan ke
String
:InputStream inputStream = urlConnection.getInputStream(); StringBuffer buffer = new StringBuffer(); if (inputStream == null) { // Nothing to do. return null; } reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { /* Since it's JSON, adding a newline isn't necessary (it won't affect parsing) but it does make debugging a *lot* easier if you print out the completed buffer for debugging. */ buffer.append(line + "\n"); } if (buffer.length() == 0) { // Stream was empty. No point in parsing. return null; } bookJSONString = buffer.toString();
- Tutup blok try dan log pengecualiannya dalam blok catch.
catch (IOException e) { e.printStackTrace(); return null; }
-
Tutup kedua urlConnection dan variabel pembaca dalam blok finally:
finally { if (urlConnection != null) { urlConnection.disconnect(); } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
5. Log nilai variabel
bookJSONString
sebelum mengembalikannya. Sekarang Anda sudah selesai dengan metodegetBookInfo()
.Log.d(LOG_TAG, bookJSONString);
6. Dalam metode AsyncTask
doInBackground()
, panggil metodegetBookInfo()
, meneruskan istilah penelusuran yang Anda dapatkan dari argumenparams
yang diteruskan oleh sistem (ini adalah nilai pertama dalam larikparams
). Kembalikan hasil dari metode ini dalam metodedoInBackground()
:return NetworkUtils.getBookInfo(params[0]);
7. Sekarang setelah AsyncTask disiapkan, Anda perlu meluncurkannya dari MainActivity menggunakan metode
execute()
. Tambahkan kode berikut ke metodesearchBooks()
dalam MainActivity.java untuk meluncurkan AsyncTask:new FetchBook(mTitleText, mAuthorText).execute(mQueryString);
8.Jalankan aplikasi Anda. Eksekusi penelusuran. Aplikasi Anda akan crash. Lihat Log untuk memeriksa apa yang menyebabkan kesalahan. Anda seharusnya melihat baris berikut:
Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
Kesalahan ini menunjukkan bahwa Anda belum menyertakan izin untuk mengakses internet dalam file AndroidManifest.xml. Terhubung ke internet menimbulkan masalah keamanan baru, karena itulah aplikasi Anda tidak memiliki konektivitas secara default. Anda harus menambahkan izin secara manual dalam bentuk tag
<uses-permission>
; dalam AndroidManifest.xml.
2.6 Add the Internet permissions
- Buka file AndroidManifest.xml.
- Semua izin aplikasi harus diletakkan dalam file AndroidManifest.xml di luar tag
<application>
;. Anda harus memastikan untuk mengikuti urutan tempat tag didefinisikan dalam AndroidManifest.xml. - Tambahkan tag xml berikut di luar tag
<application>
:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- Bangun dan jalankan aplikasi Anda lagi. Menjalankan kueri seharusnya menghasilkan string JSON dicetak ke Log.
2.7 Parse the JSON string
Untuk melakukan parsing pada data JSON dan menangani pengecualian yang mungkin, lakukan hal berikut:
- Dalam
onPostExecute()
, tambahkan blok try/catch di bawag panggilan kesuper
. - Gunakan kelas JSON Java bawaan (
JSONObject
danJSONArray
) untuk mendapatkan larik JSON item hasil dalam blok try.JSONObject jsonObject = new JSONObject(s); JSONArray itemsArray = jsonObject.getJSONArray("items");
-
Iterasi melalui
itemsArray
, memeriksa judul dan informasi penulis setiap buku. Jika keduanya bukan null, keluar dari loop dan perbarui UI; jika tidak, terus periksa daftarnya. Dengan cara ini, hanya entri dengan judul dan penulis yang akan ditampilkan.//Iterate through the results for(int i = 0; i<itemsArray.length(); i++){ JSONObject book = itemsArray.getJSONObject(i); //Get the current item String title=null; String authors=null; JSONObject volumeInfo = book.getJSONObject("volumeInfo"); try { title = volumeInfo.getString("title"); authors = volumeInfo.getString("authors"); } catch (Exception e){ e.printStackTrace(); } //If both a title and author exist, update the TextViews and return if (title != null && authors != null){ mTitleText.setText(title); mAuthorText.setText(authors); return; } }
- Jika tidak ada hasil yang memenuhi kriteria memiliki penulis dan judul yang valid, setel TextView judul untuk membaca “No Results Found”, dan hapus TextView
authors
. - Dalam blok catch, cetak kesalahan ke log, setel TextView judul ke “No Results Found”, dan hapus TextView
authors
.
Task 3. Implement UI Best Practices
Anda sekarang memiliki aplikasi yang berfungsi dan menggunakan API Books untuk mengeksekusi penelusuran buku. Tetapi, ada beberapa hal yang tidak berjalan seperti yang diharapkan:
- Saat pengguna mengeklik Search Books, keyboard tidak muncul, dan tidak ada indikasi bagi pengguna bahwa kueri sebenarnya sedang dieksekusi.
- Jika tidak ada koneksi jaringan, atau bidang penelusuran kosong, aplikasi masih mencoba menanyakan API dan gagal tanpa memperbarui UI dengan benar.
- Jika Anda memutar layar selama kueri, AsyncTask akan terputus koneksinya dari Aktivitas, dan tidak dapat memperbarui UI dengan hasilnya.
3.1 Hide the Keyboard and Update the TextView
- Menambahkan kode berikut ke metode
searchBooks()
untuk menyembunyikan keyboard saat tombol ditekan:InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
- Menambahkan satu baris kode di bawah panggilan untuk mengeksekusi tugas FetchBook yang mengubah TextView judul untuk menbaca “Loading…” dan menghapus TextView penulis.
- Mengekstrak sumber daya String.
3.2 Manage the network state and the empty search field case
Kapan pun aplikasi menggunakan jaringan, aplikasi itu perlu menangani kemungkinan koneksi jaringan tidak tersedia. Sebelum mencoba terhubung ke jaringan dalam AsyncTask atau AsyncTaskLoader, aplikasi harus memeriksa status koneksi jaringan.
- Modifikasi metode
searchBooks()
untuk memeriksa kedua koneksi jaringan dan apakah ada teks dalam bidang penelusuran sebelum mengeksekusi tugas FetchBook. - Perbarui UI dalam kasus tidak ada koneksi internet atau tidak ada teks dalam bidang penelusuran. Tampilkan penyebab kesalahan dalam TextView.
Task 4. Migrate to AsyncTaskLoader
Saat menggunakan AsyncTask, AsyncTask tidak bisa memperbarui UI jika perubahan konfigurasi terjadi saat tugas latar belakang sedang berjalan. Untuk mengatasi situasi ini, Android SDK menyediakan serangkaian kelas bernama loader yang didesain secara spesifik untuk memuat data ke dalam UI secara asinkron.
Dalam tugas ini Anda akan menggunakan loader spesifik bernama AsyncTaskLoader. AsyncTaskLoader adalah subkelas abstrak Loader dan menggunakan AsyncTask untuk memuat data di latar belakang dengan efisien.
Loader menyediakan banyak fungsionalitas tambahan, bukan hanya untuk menjalankan tugas dan menghubungkan kembali ke Aktivitas. Misalnya, Anda bisa melampirkan loader ke sumber data dan membuatnya secara otomatis memperbarui elemen UI saat data dasarnya berubah. Loader juga bisa diprogram untuk melanjutkan memuat jika terganggu.
Mengimplementasikan Loader memerlukan komponen berikut:
- Kelas yang memperluas kelas Loader (dalam kasus ini, AsyncTaskLoader).
- Aktivitas yang mengimplementasikan kelas LoaderManager.LoaderCallbacks.
-
Instance LoaderManager.
4.1 Create an AsyncTaskLoader
- Salin proyek WhoWroteIt, untuk mempertahankan hasil dari praktik sebelumnya. Ganti nama proyek yang disalin menjadi WhoWroteItLoader.
- Buat kelas baru dalam direktori Java bernama BookLoader.
4.2 Modify MainActivity
Sekarang Anda harus mengimplementasikan Callback Loaderdalam MainActivity untuk menangani hasil dari metode loadInBackground()
AsyncTaskLoader.
- Tambahkan implementasi
LoaderManager.LoaderCallbacks
ke deklarasi kelas Aktivitas Utama, yang berparameter dengan tipeString
:public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String>{
- Implementasikan metode yang diperlukan:
onCreateLoader(), onLoadFinished(), onLoaderReset()
. Letakkan kursor teks pada baris tanda tangan kelas dan masukkan Alt + Enter (Option + Enter di Mac). Pastikan semua metode dipilih.
Loader menggunakan kelas Bundle untuk meneruskan informasi dari aktivitas memanggil ke LoaderCallbacks. Anda bisa menambahkan data primitif ke bundel dengan metode putType()
yang tepat.
Untuk memulai loader, Anda memiliki dua opsi:
initLoader()
: Metode ini membuat loader baru jika belum ada dan bergerak dalam Bundel argumen. Jika loader ada, Aktivitas memanggil akan dikaitkan kembali dengannya tanpa memperbarui Bundel.restartLoader()
: Metode ini sama dengan initLoader() kecuali jika metode ini menemukan loader yang sudah ada, metode ini akan memusnahkan dan membuat ulang loader dengan Bundel yang baru.
Kedua metode didefinisikan dalam LoaderManager, yang mengelola semua instance Loader yang digunakan dalam Aktivitas (atau Fragmen). Setiap Aktivitas memiliki satu instance LoaderManager yang bertanggung jawab terhadap siklus hidup dan Loader yang dikelolanya.
Untuk melakukannya, Anda perlu mengedit metode onClick untuk tombol itu.
-
Dalam metode
searchBooks()
, yang merupakan metode onClick untuk tombol tersebut, ganti panggilan untuk mengeksekusi tugas FetchBook dengan panggilan untukrestartLoader()
, yang meneruskan string kueri yang Anda dapatkan dari EditText dalam Bundel:Bundle queryBundle = new Bundle(); queryBundle.putString("queryString", queryString); getSupportLoaderManager().restartLoader(0, queryBundle,this);
Metode
restartLoader()
mengambil tiga argumen:- Loader
id
(berguna jika Anda mengimplementasikan lebih dari satu loader dalam aktivitas). - Argumen
Bundle
(tempat data yang diperlukan oleh loader disimpan). - Instance LoaderCallbacks yang Anda implementasikan dalam aktivitas. Jika ingin loader membawa hasil ke MainActivity, tetapkan
this
sebagai argumen ketiga.
- Loader
-
Periksa metode Override dalam kelas LoaderCallbacks. Metode ini adalah:
onCreateLoader()
: Dipanggil saat Anda membuat instance Loader.onLoadFinished()
: Dipanggil ketika tugas loader sudah selesai. Ini adalah tempat di mana Anda menambahkan kode untuk memperbarui UI dengan hasilnya.onLoaderReset()
: Menghapus sumber daya yang tersisa.
Implementasikan onCreateLoader()
- Dalam
onCreateLoader()
, kembalikan instance kelas BookLoader, meneruskanqueryString
yang didapatkan dari Bundel argumen:return new BookLoader(this, args.getString("queryString"));
Implementasikan onLoadFinished()
- Perbarui
onLoadFinished()
untuk memproses hasil, yang merupakan respons String JSON mentah dari API Books.- Salin kode dari
onPostExecute()
dalam kelas FetchBook ke toonLoadFinished()
dalam MainActivity, dengan mengecualikan panggilan kesuper.onPostExecute()
. - Ganti argumen ke konstruktor JSONObject dengan data
String
yang diteruskan.
- Salin kode dari
-
Jalankan aplikasi Anda.
Anda seharusnya memiliki fungsionalitas yang sama seperti sebelumnya, hanya saja sekarang di dalam Loader! Satu hal yang masih tidak berfungsi. Saat perangkat diputar, data
View
hilang. Ini karena saat Aktivitas dibuat (atau dibuat ulang), Aktivitas tidak tahu bahwa ada loader yang sedang berjalan. MetodeinitLoader()
dibutuhkan dalamonCreate()
dari MainActivity untuk menghubungkan ulang loader. -
Tambahkan kode berikut di
onCreate()
untuk menghubungkan ulang ke Loader jika sudah ada:if(getSupportLoaderManager().getLoader(0)!=null){ getSupportLoaderManager().initLoader(0,null,this); }
Catatan: Jika loader ada, inisialisasikan loader. Anda hanya perlu mengaitkan kembali loader ke Aktivitas jika kueri sudah dieksekusi. Dalam status awal aplikasi, data tidak dimuat sehingga tidak ada yang perlu dipertahankan. -
Jalankan aplikasi lagi dan putar perangkat. LoaderManager sekarang mempertahankan data di semua konfigurasi perangkat!
- Hapus kelas FetchBook karena sudah tidak digunakan lagi.