Menggunakan Retrofit 2.x sebagai REST Client - Tutorial

1. Retrofit


1.1. Apa itu Retrofit?


Retrofit adalah klien REST untuk Java dan Android. Ini membuatnya mudah untuk mendapatkan dan memuat JSON (atau data terstruktur lainnya) melalui layanan web berbasis REST. Di Retrofit, Anda mengonfigurasi konverter mana yang digunakan untuk membuat serialisasi data. Biasanya GSon digunakan untuk JSON, tetapi Anda dapat menambahkan konverter Anda sendiri untuk memproses XML atau protokol lainnya. Retrofit menggunakan pustaka OkHttp untuk permintaan HTTP.

Anda bisa membuat objek Java berbasis JSON menggunakan alat berikut: www.jsonschema2pojo.org Ini bisa berguna untuk membuat struktur data Java yang kompleks dari JSON yang ada.


1.2. Menggunakan Retrofit


Untuk bekerja dengan Retrofit, Anda membutuhkan tiga kelas berikut:

  • Kelas model yang digunakan sebagai model JSON
  • Antarmuka yang mengidentifikasi kemungkinan operasi HTTP
  • Kelas Retrofit.Builder adalah sebuah instance yang menggunakan antarmuka Builder dan API untuk menentukan definisi titik akhir URL untuk operasi HTTP.

Setiap metode antarmuka mewakili salah satu panggilan API yang mungkin. Itu harus memiliki anotasi HTTP (GET, POST, dll.) Untuk menunjukkan jenis permintaan dan URL relatif. Nilai kembali melengkapi respons dalam objek Panggilan dengan jenis hasil yang diharapkan.

@GET("users") Call<List<User>> getUsers(); 

Anda dapat menggunakan blok pengganti dan parameter kueri untuk mengonfigurasi URL. Blok pengganti ditambahkan ke URL relatif menggunakan {}. Menggunakan anotasi @ Path untuk parameter metode, nilai parameter ini terikat ke blok pengganti tertentu.

 @GET("users/{name}/commits") Call<List<Commit>> getCommitsByName(@Path("name") String name); 

Parameter kueri ditambahkan menggunakan anotasi @ Permintaan ke parameter metode. Mereka secara otomatis ditambahkan di akhir URL.

 @GET("users") Call<User> getUserById(@Query("id") Integer id); 

Penjelasan @ Tubuh ke parameter metode memberi tahu Retrofit untuk menggunakan objek sebagai badan permintaan untuk memanggil.

 @POST("users") Call<User> postUser(@Body User user) 

2. Prasyarat


Contoh berikut menggunakan Eclipse IDE dengan sistem build Gradle.
Latihan ini mengasumsikan bahwa Anda terbiasa dengan Gradle dan menggunakan Gradle dengan Eclipse .

Lingkungan pengembangan lainnya, seperti Visual Studio Code atau IntelliJ, melakukan hal yang sama, sehingga Anda dapat menggunakan alat favorit Anda.

3. Latihan: Klien Retrofit Pertama


Dalam latihan ini, Anda akan membuat klien REST mandiri. Respons dihasilkan oleh server Mock.

3.1. Membuat dan mengatur proyek


Buat proyek Gradle baru bernama com.vogella.retrofitgerrit. Tambahkan paket baru ke src / main / java bernama com.vogella.retrofitgerrit.

Tambahkan dependensi berikut ke file build.gradle.

 // retrofit implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' // Junit testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") // to run JUnit 3/4 tests: testImplementation("junit:junit:4.12") testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0") 

3.2. Tentukan API dan Retrofit Adapter


Dalam respons JSON Gerrit, kami hanya tertarik pada pertanyaan tentang perubahan. Oleh karena itu, buat kelas data berikut dalam paket default yang ditambahkan sebelumnya.

 package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } 

Tentukan REST API untuk Retrofit melalui antarmuka berikut.

 package com.vogella.java.retrofitgerrit; import java.util.List; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface GerritAPI { @GET("changes/") Call<List<Change>> loadChanges(@Query("q") String status); } 

Buat kelas pengontrol berikut. Kelas ini membuat klien Retrofit, memanggil Gerrit API, dan memproses hasilnya (mencetak hasil panggilan ke konsol).

 package com.vogella.java.retrofitgerrit; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class Controller implements Callback<List<Change>> { static final String BASE_URL = "https://git.eclipse.org/r/"; public void start() { Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); GerritAPI gerritAPI = retrofit.create(GerritAPI.class); Call<List<Change>> call = gerritAPI.loadChanges("status:open"); call.enqueue(this); } @Override public void onResponse(Call<List<Change>> call, Response<List<Change>> response) { if(response.isSuccessful()) { List<Change> changesList = response.body(); changesList.forEach(change -> System.out.println(change.subject)); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<List<Change>> call, Throwable t) { t.printStackTrace(); } } 

Buat kelas dengan metode utama untuk memulai controller.

 package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } } 

4. Konverter dan adaptor retrofit


4.1. Pengonversi Retrofit


Retrofit dapat dikonfigurasi untuk menggunakan konverter tertentu. Konverter ini menangani (de) serialisasi data. Beberapa konverter sudah tersedia untuk berbagai format serialisasi.

  • Untuk mengonversi ke JSON dan sebaliknya:
    • Gson: com.squareup.retrofit: converter-gson
    • Jackson: com.squareup.retrofit: converter-jackson
    • Moshi: com.squareup.retrofit: converter-moshi

  • Untuk mengonversi ke Protokol Buffer dan sebaliknya:
    • Protobuf: com.squareup.retrofit: converter-protobuf
    • Kawat: com.squareup.retrofit: konverter-kawat

  • Untuk mengonversi ke XML dan sebaliknya:
    • XML sederhana: com.squareup.retrofit: converter-simplexml


Selain konverter terdaftar, Anda juga dapat membuat sendiri untuk memproses protokol lain dengan memperluas kelas Converter.

4.2. Retrofit Adapters


Retrofit juga dapat diperluas dengan adaptor untuk berinteraksi dengan perpustakaan lain seperti RxJava 2.x, Java 8, dan Guava.

Tinjauan umum adaptor yang tersedia dapat ditemukan di Github square / retrofit / retrofit-adapter / .

Misalnya, adaptor RxJava 2.x dapat dihubungkan menggunakan Gradle:

 compile 'com.squareup.retrofit2:adapter-rxjava2:latest.version' 

atau menggunakan Apache Maven:

 <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-rxjava2</artifactId> <version>latest.version</version> </dependency> 

Untuk menambahkan adaptor, Anda harus menggunakan metode retrofit2.Retrofit.Builder.addCallAdapterFactory (Factory).

 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); 

Menggunakan adaptor ini, antarmuka Retrofit dapat mengembalikan tipe RxJava 2.x, misalnya, Dapat Diamati, Dapat Dialirkan, atau Tunggal, dll

 @GET("users") Observable<List<User>> getUsers(); 

5. Otentikasi retrofit


Retrofit mendukung panggilan API yang membutuhkan otentikasi. Otentikasi dapat dilakukan dengan menggunakan nama pengguna dan kata sandi (otentikasi Http Basic) atau token API.

Ada dua cara untuk mengelola otentikasi. Metode pertama adalah mengelola tajuk permintaan menggunakan anotasi. Cara lain adalah dengan menggunakan pencegat OkHttp untuk ini.

5.1. Otentikasi dengan anotasi


Misalkan Anda ingin meminta informasi tentang pengguna yang memerlukan otentikasi. Anda dapat melakukan ini dengan menambahkan parameter baru ke definisi API, misalnya:

 @GET("user") Call<UserDetails> getUserDetails(@Header("Authorization") String credentials) 


Menggunakan anotasi @ Header ("Otorisasi"), Anda memberi tahu Retrofit untuk menambahkan header Otorisasi ke permintaan dengan nilai yang Anda berikan.

Untuk menghasilkan kredensial untuk otentikasi Dasar, Anda dapat menggunakan kelas Kredensial OkHttps dengan metode dasarnya (String, String). Metode ini menerima nama pengguna dan kata sandi dan mengembalikan kredensial otentikasi untuk skema Http Basic.

 Credentials.basic("ausername","apassword"); 

Jika Anda ingin menggunakan token API dan tidak menggunakan skema Basic, panggil saja metode getUserDetails (String) dengan token Anda.

5.2. Otentikasi dengan pencegat OkHttp.


Metode di atas menambahkan kredensial hanya jika Anda meminta data pengguna. Jika Anda memiliki lebih banyak panggilan yang memerlukan otentikasi, Anda dapat menggunakan interseptor untuk melakukan ini. Pencegat digunakan untuk memodifikasi setiap permintaan sebelum dieksekusi dan menetapkan header permintaan. Keuntungannya adalah Anda tidak perlu menambahkan @ Header ("Otorisasi") ke setiap definisi metode API.

Untuk menambahkan interseptor, Anda harus menggunakan metode okhttp3.OkHttpClient.Builder.addInterceptor (Interceptor) di OkHttp Builder.

 OkHttpClient okHttpClient = new OkHttpClient().newBuilder().addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", Credentials.basic("aUsername", "aPassword")); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build(); 

Klien yang dibuat oleh OkHttp harus ditambahkan ke klien Retrofit Anda menggunakan metode retrofit2.Retrofit.Builder.client (OkHttpClient).

 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .build(); 

Seperti yang telah Anda perhatikan, kelas Kredensial digunakan di sini untuk otorisasi Dasar.
Sekali lagi, jika Anda ingin menggunakan token API, cukup gunakan token itu.

6. Latihan: Menggunakan Retrofit untuk Meminta Gerrit di Jawa


Bagian berikut menjelaskan cara membuat aplikasi Java minimal yang menggunakan Retrofit untuk mengambil objek perubahan terbuka dari API Gerrit. Hasilnya dicetak di konsol.

6.1. Membuat dan mengatur proyek


Latihan ini mengasumsikan bahwa Anda terbiasa dengan Gradle dan Buildship for Eclipse .

Buat proyek Gradle baru bernama com.vogella.java.retrofitgerrit. Tambahkan paket baru ke src / main / java bernama com.vogella.java.retrofitgerrit.

Tambahkan dependensi berikut ke file build.gradle.

 // retrofit implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' // Junit testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") // to run JUnit 3/4 tests: testImplementation("junit:junit:4.12") testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0") 

6.2. Tentukan API dan Retrofit Adapter


Dalam respons JSON Gerrit, kami hanya tertarik pada pertanyaan tentang perubahan. Oleh karena itu, buat kelas data berikut dalam paket default yang ditambahkan sebelumnya.

 package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } 

Tentukan REST API untuk Retrofit menggunakan antarmuka berikut.

 package com.vogella.java.retrofitgerrit; import java.util.List; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface GerritAPI { @GET("changes/") Call<List<Change>> loadChanges(@Query("q") String status); } 

Buat kelas pengontrol berikut. Kelas ini membuat klien Retrofit, memanggil Gerrit API, dan memproses hasilnya (mencetak hasil panggilan ke konsol).

 package com.vogella.java.retrofitgerrit; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class Controller implements Callback<List<Change>> { static final String BASE_URL = "https://git.eclipse.org/r/"; public void start() { Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); GerritAPI gerritAPI = retrofit.create(GerritAPI.class); Call<List<Change>> call = gerritAPI.loadChanges("status:open"); call.enqueue(this); } @Override public void onResponse(Call<List<Change>> call, Response<List<Change>> response) { if(response.isSuccessful()) { List<Change> changesList = response.body(); changesList.forEach(change -> System.out.println(change.subject)); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<List<Change>> call, Throwable t) { t.printStackTrace(); } } 

Buat kelas dengan metode utama untuk memulai controller.

 package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } } 

7. Latihan: Menggunakan Retrofit untuk Mengonversi Respons XML dari Umpan RSS


Bagian ini menjelaskan cara menggunakan Retrofit untuk mengubah respons XML menggunakan SimpleXMLConverter.

Aplikasi Java minimal dibuat yang meminta Vogella RSS feed ( http://vogella.com/article.rss ) dan mencetak nama saluran, judul, dan tautan artikel.

7.1. Membuat dan mengatur proyek


Latihan ini mengasumsikan bahwa Anda terbiasa dengan Gradle dan Buildship for Eclipse .

Buat proyek Gradle baru bernama com.vogella.java.retrofitxml. Tambahkan paket baru ke src / main / java bernama com.vogella.java.retrofitxml.

Tambahkan dependensi berikut ke file build.gradle.

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0' 

7.2. Menentukan Tampilan XML


Umpan RSS adalah sebagai berikut:

 <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>Eclipse and Android Information</title> <link>http://www.vogella.com</link> <description>Eclipse and Android Information</description> <language>en</language> <copyright>Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany (CC BY-NC-SA 3.0)</copyright> <pubDate>Tue, 03 May 2016 11:46:11 +0200</pubDate> <item> <title>Android user interface testing with Espresso - Tutorial</title> <description> This tutorial describes how to test Android applications with the Android Espresso testing framework.</description> <link>http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html</guid> </item> <item> <title>Using the Gradle build system in the Eclipse IDE - Tutorial</title> <description>This article describes how to use the Gradle tooling in Eclipse</description> <link>http://www.vogella.com/tutorials/EclipseGradle/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/EclipseGradle/article.html</guid> </item> <item> <title>Unit tests with Mockito - Tutorial</title> <description>This tutorial explains testing with the Mockito framework for writting software tests.</description> <link>http://www.vogella.com/tutorials/Mockito/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/Mockito/article.html</guid> </item> </channel> </rss> 

Selain header XML, file ini terdiri dari berbagai elemen XML. Elemen RSS berisi elemen saluran yang berisi elemen lain (misalnya, judul, deskripsi, pubDate) dan beberapa elemen item (artikel).

Buat dua kelas data berikut: RSSFeed dan Artikel.

 package com.vogella.java.retrofitxml; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @Root(name = "item", strict = false) public class Article { @Element(name = "title") private String title; @Element(name = "link") private String link; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } } 

 package com.vogella.java.retrofitxml; import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Path; import org.simpleframework.xml.Root; @Root(name="rss", strict=false) public class RSSFeed { @Element(name="title") @Path("channel") private String channelTitle; @ElementList(name="item", inline=true) @Path("channel") private List<Article> articleList; public String getChannelTitle() { return channelTitle; } public void setChannelTitle(String channelTitle) { this.channelTitle = channelTitle; } public List<Article> getArticleList() { return articleList; } public void setArticleList(List<Article> articleList) { this.articleList = articleList; } } 

Kelas Artikel mewakili satu artikel dan hanya mempertahankan judul dan tautan ke artikel tersebut. Ini adalah satu-satunya bidang yang menarik minat kami.

Annotation @ Root menandai kelas sebagai subjek (de) serialisasi. Secara opsional, Anda dapat menentukan nama dalam penjelasan @ Root yang cocok dengan nama elemen XML. Jika tidak ada nama yang ditentukan, nama kelas digunakan sebagai nama elemen XML. Karena nama kelas (RSSFeed) berbeda dari nama elemen XML (rss), kita perlu menentukan namanya.

Saat ketat disetel ke false, penguraian ketat dinonaktifkan. Ini memberi tahu parser untuk tidak mengganggu atau melempar pengecualian jika elemen atau atribut XML ditemukan tanpa pemetaan. Karena elemen rss memiliki atribut versi yang tidak ada bidangnya, aplikasi akan menghasilkan kesalahan jika parameter ketat tidak disetel ke false.

Menggunakan anotasi @ Element, elemen XML direpresentasikan. Jika perlu, Anda bisa menentukan nama XML elemen yang diwakili oleh bidang ini. Jika tidak ada nama yang ditentukan, nama bidang digunakan.

Bidang articleList diberi keterangan dengan @ ElementList. Ini menunjukkan bahwa bidang ini digunakan untuk menyimpan koleksi (dalam kasus kami: Daftar) elemen XML dengan nama yang sama. Ketika inline disetel ke true, ini berarti bahwa item dalam koleksi terdaftar satu demi satu segera di dalam item yang ditentukan dan tidak memiliki induk perantara.

Menggunakan anotasi @ Path, Anda dapat menentukan path ke elemen XML di dalam pohon XML. Ini berguna jika Anda tidak ingin memodelkan pohon XML lengkap dengan objek Java. Untuk nama saluran dan beberapa elemen item, kami dapat langsung menunjuk ke elemen spesifik di elemen saluran.

7.3. Menentukan API dan Retrofit Adapter


Tentukan REST API untuk Retrofit melalui antarmuka berikut.

 package com.vogella.java.retrofitxml; import retrofit2.Call; import retrofit2.http.GET; public interface VogellaAPI { @GET("article.rss") Call<RSSFeed> loadRSSFeed(); } 

Buat kelas pengontrol berikut. Kelas ini membuat klien Retrofit, memanggil Vogella API, dan memproses hasilnya.

 package com.vogella.java.retrofitxml; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.simplexml.SimpleXmlConverterFactory; public class Controller implements Callback<RSSFeed> { static final String BASE_URL = "http://vogella.com/"; public void start() { Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(SimpleXmlConverterFactory.create()).build(); VogellaAPI vogellaAPI = retrofit.create(VogellaAPI.class); Call<RSSFeed> call = vogellaAPI.loadRSSFeed(); call.enqueue(this); } @Override public void onResponse(Call<RSSFeed> call, Response<RSSFeed> response) { if (response.isSuccessful()) { RSSFeed rss = response.body(); System.out.println("Channel title: " + rss.getChannelTitle()); rss.getArticleList().forEach( article -> System.out.println("Title: " + article.getTitle() + " Link: " + article.getLink())); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<RSSFeed> call, Throwable t) { t.printStackTrace(); } } 

Langkah terakhir adalah membuat kelas dengan metode utama untuk memulai controller.

 package com.vogella.java.retrofitxml; public class Application { public static void main(String[] args) { Controller ctrl = new Controller(); ctrl.start(); } } 

8. Latihan: Membuat Aplikasi untuk Meminta StackOverflow


StackOverflow adalah situs populer untuk masalah terkait pemrograman. Ini juga menyediakan REST API, didokumentasikan dengan baik pada halaman Stackoverflow API .

Dalam latihan ini Anda akan menggunakan pustaka REST Retrofit. Anda akan menggunakannya untuk menanyakan pertanyaan tag StackOverflow dan jawabannya.

Dalam contoh kami, kami menggunakan URL permintaan berikut. Buka URL ini di browser dan lihat jawabannya.

 https://api.stackexchange.com/2.2/search?order=desc&sort=votes&tagged=android&site=stackoverflow 

8.1. Membuat dan mengatur proyek


Buat aplikasi Android bernama com.vogella.android.stackoverflow. Gunakan com.vogella.android.stackoverflow sebagai nama paket tingkat atas.

Tambahkan dependensi berikut ke file build.gradle.

 compile "com.android.support:recyclerview-v7:25.3.1" compile 'com.google.code.gson:gson:2.8.1' 

8.2. Membuat model data


Kami tertarik pada pertanyaan dan jawaban dari Stackoverflow. Untuk tujuan ini, buat dua kelas data berikut.

 package com.vogella.android.stackoverflow; import com.google.gson.annotations.SerializedName; public class Question { public String title; public String body; @SerializedName("question_id") public String questionId; @Override public String toString() { return(title); } } 

 package com.vogella.android.stackoverflow; import com.google.gson.annotations.SerializedName; public class Answer { @SerializedName("answer_id") public int answerId; @SerializedName("is_accepted") public boolean accepted; public int score; @Override public String toString() { return answerId + " - Score: " + score + " - Accepted: " + (accepted ? "Yes" : "No"); } } 

8.3. Membuat aktivitas dan tata letak


Tetapkan activity_main.xml untuk aktivitas Anda.

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp" android:orientation="vertical" tools:context="com.vogella.android.stackoverflow.MainActivity"> <Spinner android:id="@+id/questions_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v7.widget.RecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/authenticate_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="onClick" android:text="Authenticate" /> </LinearLayout> 

Tambahkan kelas tampilan pendaur ulang adaptor bernama RecyclerViewAdapter ke proyek Anda.

Salah satu implementasi yang mungkin adalah sebagai berikut.

 package com.vogella.android.stackoverflow; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Answer> data; public class ViewHolder extends RecyclerView.ViewHolder { public TextView text; public ViewHolder(View v) { super(v); text = (TextView) v.findViewById(android.R.id.text1); } } public RecyclerViewAdapter(List<Answer> data) { this.data = data; } @Override public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; v = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_selectable_list_item, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) { Answer answer = ((Answer) data.get(position)); holder.text.setText(answer.toString()); holder.itemView.setTag(answer.answerId); } @Override public int getItemCount() { return data.size(); } } 

Ubah kelas MainActivity sebagai berikut:

 package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity implements View.OnClickListener { private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "Spinner item selected", Toast.LENGTH_LONG).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) { // TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } } 

8.4. Menggunakan penyedia data palsu


Buat penyedia data palsu dan isi spinner dengan pertanyaan palsu dan recyclerview dengan jawaban palsu (setelah mengubah pilihan dalam spinner).

 package com.vogella.android.stackoverflow; import java.util.ArrayList; import java.util.List; public class FakeDataProvider { public static List<Question> getQuestions(){ List<Question> questions = new ArrayList<>(); for (int i = 0; i<10; i++) { Question question = new Question(); question.questionId = String.valueOf(i); question.title = String.valueOf(i); question.body = String.valueOf(i) + "Body"; questions.add(question); } return questions; } public static List<Answer> getAnswers(){ List<Answer> answers = new ArrayList<>(); for (int i = 0; i<10; i++) { Answer answer = new Answer(); answer.answerId = i; answer.accepted = false; answer.score = i; answers.add(answer); } return answers; } } 

Sekarang konfigurasikan spinner dan recyclerview untuk menggunakan data palsu ini.

 package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import java.util.List; public class MainActivity extends Activity implements View.OnClickListener { private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "Spinner item selected", Toast.LENGTH_LONG).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); List<Question> questions = FakeDataProvider.getQuestions(); ArrayAdapter<Question> arrayAdapter = new ArrayAdapter<Question>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, questions); questionsSpinner.setAdapter(arrayAdapter); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); List<Answer> answers = FakeDataProvider.getAnswers(); RecyclerViewAdapter adapter = new RecyclerViewAdapter(answers); recyclerView.setAdapter(adapter); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) { // TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } } 

8.5. Menambahkan Ketergantungan Derajat dan Izin


Tambahkan dependensi berikut ke file build.gradle.

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' 

Tambahkan izin untuk mengakses Internet di manifes.

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.stackoverflow"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 

8.6. Menentukan API dan Retrofit Adapter


Stackoverflow API membungkus jawaban atau pertanyaan dalam objek bernama item JSON. Untuk menangani ini, buat kelas data berikut yang disebut ListWrapper. Ini diperlukan untuk memproses pembungkus elemen Stackoverflow. Kelas ini mengambil parameter tipe dan hanya mengemas daftar objek jenis ini.

 package com.vogella.android.stackoverflow; import java.util.List; public class ListWrapper<T> { List<T> items; } 

Tetapkan REST API untuk Retrofit melalui antarmuka berikut.

 package com.vogella.android.stackoverflow; import java.util.List; import okhttp3.ResponseBody; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.Call; public interface StackOverflowAPI { String BASE_URL = "https://api.stackexchange.com/"; @GET("/2.2/questions?order=desc&sort=votes&site=stackoverflow&tagged=android&filter=withbody") Call<ListWrapper<Question>> getQuestions(); @GET("/2.2/questions/{id}/answers?order=desc&sort=votes&site=stackoverflow") Call<ListWrapper<Answer>> getAnswersForQuestion(@Path("id") String questionId); } 

8.7. Pengaturan aktivitas


Ubah kode MainActivity sebagai berikut.

 package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.List; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends Activity implements View.OnClickListener { private StackOverflowAPI stackoverflowAPI; private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Question question = (Question) parent.getAdapter().getItem(position); stackoverflowAPI.getAnswersForQuestion(question.questionId).enqueue(answersCallback); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); createStackoverflowAPI(); stackoverflowAPI.getQuestions().enqueue(questionsCallback); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } private void createStackoverflowAPI() { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(StackOverflowAPI.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); stackoverflowAPI = retrofit.create(StackOverflowAPI.class); } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) { //TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } Callback<ListWrapper<Question>> questionsCallback = new Callback<ListWrapper<Question>>() { @Override public void onResponse(Call<ListWrapper<Question>> call, Response<ListWrapper<Question>> response) { if (response.isSuccessful()) { ListWrapper<Question> questions = response.body(); ArrayAdapter<Question> arrayAdapter = new ArrayAdapter<Question>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, questions.items); questionsSpinner.setAdapter(arrayAdapter); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); } } @Override public void onFailure(Call<ListWrapper<Question>> call, Throwable t) { t.printStackTrace(); } }; Callback<ListWrapper<Answer>> answersCallback = new Callback<ListWrapper<Answer>>() { @Override public void onResponse(Call<ListWrapper<Answer>> call, Response<ListWrapper<Answer>> response) { if (response.isSuccessful()) { List<Answer> data = new ArrayList<>(); data.addAll(response.body().items); recyclerView.setAdapter(new RecyclerViewAdapter(data)); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); } } @Override public void onFailure(Call<ListWrapper<Answer>> call, Throwable t) { t.printStackTrace(); } }; Callback<ResponseBody> upvoteCallback = new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful()) { Toast.makeText(MainActivity.this, "Upvote successful", Toast.LENGTH_LONG).show(); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); Toast.makeText(MainActivity.this, "You already upvoted this answer", Toast.LENGTH_LONG).show(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); } }; } 

8.8. Opsional: mendapatkan gambar profil pengguna


Ubah tata letak garis dalam tampilan pendaur ulang untuk juga menampilkan gambar profil pengguna. Perluas model data Anda untuk mendapatkan gambar profil pengguna yang menjawab pertanyaan. Tambahkan ImageView ke garis tata letak dan gunakan pustaka Glide untuk memuat gambar.

8.9. Opsional: gunakan tata letak yang berbeda untuk garis genap dan ganjil


Ubah implementasi adaptor untuk menggunakan tata letak yang berbeda untuk garis genap dan ganjil.

Ini membutuhkan pembuatan tata letak yang berbeda berdasarkan tipe data. Gunakan getItemViewType () di adaptor.

8.10. Opsional: Penanganan Kesalahan Jaringan


Jika Anda mengalami kegagalan jaringan, perlihatkan tombol permintaan ulang alih-alih antarmuka pengguna utama.

9. Latihan: Menggunakan Retrofit untuk Mengakses API GitHub di Android


Latihan ini menjelaskan cara membuat daftar semua repositori GitHub untuk pengguna di aplikasi Android menggunakan Retrofit. Anda dapat memilih repositori dari daftar drop-down dan menentukan diskusi yang terkait dengan pengguna untuk repositori yang dipilih.

Kemudian Anda dapat memilih diskusi dari bidang tarik-turun tambahan dan memposting komentar di atasnya. DialogFragment akan digunakan untuk memasukkan kredensial untuk otentikasi.

Pastikan Anda memiliki akun Github, karena ini diperlukan untuk latihan ini. Karena Retrofit akan digunakan dengan RxJava2 selama latihan ini, perhatikan juga Tutorial RxJava2 .

9.1. Penyiapan proyek


Buat aplikasi Android yang disebut Retrofit Github. Gunakan com.vogella.android.retrofitgithub sebagai nama paket tingkat atas dan gunakan templat kosong. Pastikan bendera "Kompatibilitas Mundur" dicentang.

Untuk menggunakan Retrofit dan RxJava2 CallAdapter , tambahkan dependensi berikut ke file build.gradle

 implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 

Tambahkan izin untuk mengakses Internet di manifes.

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.retrofitgithub"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.vogella.android.retrofitgithub.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 

9.2. Definisi API


Buat dua kelas data berikut: GithubIssue dan GithubRepo.

 package com.vogella.android.retrofitgithub; import com.google.gson.annotations.SerializedName; public class GithubIssue { String id; String title; String comments_url; @SerializedName("body") String comment; @Override public String toString() { return id + " - " + title; } } 

 package com.vogella.android.retrofitgithub; public class GithubRepo { String name; String owner; String url; @Override public String toString() { return(name + " " + url); } } 

Dari informasi repositori, hanya nama repositori dan URL yang akan ditampilkan dalam daftar drop-down. Kami juga menambahkan pemilik ke kelas data, karena nama pemilik diperlukan untuk meminta diskusi nanti.

Kami hanya menampilkan id dan judul diskusi di bidang tarik-turun, jadi kami membuat bidang untuk masing-masing. Selain itu, respons dari Github berisi URL untuk memposting komentar, yang disimpan di bidang comments_url. Untuk memposting komentar baru di API Github nanti, tambahkan bidang yang disebut comment. Github apimenunjukkan bahwa isi komentar harus diikat ke bidang bernama badan dalam permintaan JSON. Karena Retrofit (de) membuat serial semua bidang berdasarkan namanya, dan karena kami tidak ingin menggunakan tubuh sebagai nama bidang dalam kelas GithubIssue kami, kami menggunakan anotasi @SerializedName. Dengan anotasi ini, kita dapat mengubah nama yang isian isiannya (de) menjadi JSON.

Sayangnya, kelas GithubRepo tidak cukup untuk meminta semua informasi yang diperlukan tentang repositori. Seperti yang Anda lihat di sini, pemilik repositori adalah objek JSON yang terpisah dalam respons repositori, dan oleh karena itu biasanya memerlukan kelas Java yang sesuai untuk (de) membuat serial. Untungnya, Retrofit memungkinkan Anda untuk menambahkan JSONDeserializer yang diketik sendiri untuk mengontrol deserialisasi jenis tertentu. Setiap kali objek dari jenis tertentu perlu deserialized, deserializer khusus ini digunakan.

Untuk melakukan ini, tambahkan kelas GithubRepoDeserialzer berikut.

 package com.vogella.android.retrofitgithub; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import java.lang.reflect.Type; public class GithubRepoDeserializer implements JsonDeserializer<GithubRepo> { @Override public GithubRepo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { GithubRepo githubRepo = new GithubRepo(); JsonObject repoJsonObject = json.getAsJsonObject(); githubRepo.name = repoJsonObject.get("name").getAsString(); githubRepo.url = repoJsonObject.get("url").getAsString(); JsonElement ownerJsonElement = repoJsonObject.get("owner"); JsonObject ownerJsonObject = ownerJsonElement.getAsJsonObject(); githubRepo.owner = ownerJsonObject.get("login").getAsString(); return githubRepo; } } 

Tetapkan REST API untuk Retrofit melalui antarmuka berikut:

 package com.vogella.android.retrofitgithub; import java.util.List; import io.reactivex.Single; import okhttp3.ResponseBody; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.http.Url; public interface GithubAPI { String ENDPOINT = "https://api.github.com"; @GET("user/repos?per_page=100") Single<List<GithubRepo>> getRepos(); @GET("/repos/{owner}/{repo}/issues") Single<List<GithubIssue>> getIssues(@Path("owner") String owner, @Path("repo") String repository); @POST Single<ResponseBody> postComment(@Url String url, @Body GithubIssue issue); } 

Anda mungkin memiliki pertanyaan tentang penjelasan @ Url. Dengan menggunakan anotasi ini, kita dapat menentukan URL untuk permintaan ini. Ini memungkinkan kami untuk mengubah URL untuk setiap permintaan secara dinamis. Kami membutuhkan ini untuk bidang comments_url dari kelas GithubIssue.

@ Anotasi jalur mengikat nilai parameter ke variabel yang sesuai (kurung kurawal) di URL permintaan. Ini diperlukan untuk menunjukkan pemilik dan nama repositori yang harus dimintakan diskusi.

9.3. Buat Kotak Dialog Kredensial


Untuk memberi pengguna kemampuan untuk menyimpan kredensial mereka dalam aplikasi, DialogFragment digunakan. Jadi buat kelas berikut yang disebut CredentialsDialog, dan juga tambahkan file tata letak yang disebut dialog_credentials.xml ke folder tata letak sumber daya.

Hasilnya akan terlihat seperti tangkapan layar berikut.



 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginTop="16dp" android:text="Fill you credentials here" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.3" android:text="Username:" /> <EditText android:id="@+id/username_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.7" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.3" android:text="Password" /> <EditText android:id="@+id/password_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.7" android:inputType="textPassword" /> </LinearLayout> </LinearLayout> 

 package com.vogella.android.retrofitgithub; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.EditText; import okhttp3.Credentials; public class CredentialsDialog extends DialogFragment { EditText usernameEditText; EditText passwordEditText; ICredentialsDialogListener listener; public interface ICredentialsDialogListener { void onDialogPositiveClick(String username, String password); } @Override public void onAttach(Context context) { super.onAttach(context); if (getActivity() instanceof ICredentialsDialogListener) { listener = (ICredentialsDialogListener) getActivity(); } } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_credentials, null); usernameEditText = (EditText) view.findViewById(R.id.username_edittext); passwordEditText = (EditText) view.findViewById(R.id.password_edittext); usernameEditText.setText(getArguments().getString("username")); passwordEditText.setText(getArguments().getString("password")); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setView(view) .setTitle("Credentials") .setNegativeButton("Cancel", null) .setPositiveButton("Continue", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (listener != null) { listener.onDialogPositiveClick(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } } }); return builder.create(); } } 

9.4. Buat Aktivitas


Ubah activity_main.xml sebagai berikut.

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.vogella.android.retrofitgithub.MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <Spinner android:id="@+id/repositories_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Spinner android:id="@+id/issues_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/repositories_spinner" /> <EditText android:id="@+id/comment_edittext" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/issues_spinner" android:enabled="false" android:hint="Your comment" android:imeOptions="actionDone" android:inputType="text" android:maxLines="1" /> <Button android:id="@+id/loadRepos_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:enabled="false" android:gravity="center" android:onClick="onClick" android:text="Load user repositories" /> <Button android:id="@+id/send_comment_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/loadRepos_button" android:enabled="false" android:onClick="onClick" android:text="Send comment" /> </RelativeLayout> </LinearLayout> 


Dua tombol (untuk mengunduh repositori dan mengirim komentar), dua Spinner (bidang drop-down untuk menampilkan repositori dan diskusi) dan EditText (untuk menulis komentar). Untuk meluncurkan CredentialsDialog, gunakan menu pada bilah alat Android. Untuk membuatnya, tambahkan file menu xml bernama menu_main.xml ke folder sumber daya menu (buat folder jika tidak ada).

 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_credentials" android:title="Credentials"/> </menu> 

Karena kami menggunakan widget Bilah Alat, Anda harus menonaktifkan bilah tindakan secara default. Untuk melakukan ini, modifikasi file gaya xml seperti yang ditunjukkan di bawah ini.

 <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources> 

Ubah kode aktivitas Anda sebagai berikut.

 package com.vogella.android.retrofitgithub; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.IOException; import java.util.List; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.schedulers.Schedulers; import okhttp3.Credentials; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity implements CredentialsDialog.ICredentialsDialogListener { GithubAPI githubAPI; String username; String password; Spinner repositoriesSpinner; Spinner issuesSpinner; EditText commentEditText; Button sendButton; Button loadReposButtons; private CompositeDisposable compositeDisposable = new CompositeDisposable(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(toolbar); sendButton = (Button) findViewById(R.id.send_comment_button); repositoriesSpinner = (Spinner) findViewById(R.id.repositories_spinner); repositoriesSpinner.setEnabled(false); repositoriesSpinner.setAdapter(new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"No repositories available"})); repositoriesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (parent.getSelectedItem() instanceof GithubRepo) { GithubRepo githubRepo = (GithubRepo) parent.getSelectedItem(); compositeDisposable.add(githubAPI.getIssues(githubRepo.owner, githubRepo.name) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getIssuesObserver())); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); issuesSpinner = (Spinner) findViewById(R.id.issues_spinner); issuesSpinner.setEnabled(false); issuesSpinner.setAdapter(new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"Please select repository"})); commentEditText = (EditText) findViewById(R.id.comment_edittext); loadReposButtons = (Button) findViewById(R.id.loadRepos_button); createGithubAPI(); } @Override protected void onStop() { super.onStop(); if (compositeDisposable != null && !compositeDisposable.isDisposed()) { compositeDisposable.dispose(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_credentials: showCredentialsDialog(); return true; } return super.onOptionsItemSelected(item); } private void showCredentialsDialog() { CredentialsDialog dialog = new CredentialsDialog(); Bundle arguments = new Bundle(); arguments.putString("username", username); arguments.putString("password", password); dialog.setArguments(arguments); dialog.show(getSupportFragmentManager(), "credentialsDialog"); } private void createGithubAPI() { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .registerTypeAdapter(GithubRepo.class, new GithubRepoDeserializer()) .create(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", Credentials.basic(username, password)); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(GithubAPI.ENDPOINT) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); githubAPI = retrofit.create(GithubAPI.class); } public void onClick(View view) { switch (view.getId()) { case R.id.loadRepos_button: compositeDisposable.add(githubAPI.getRepos() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getRepositoriesObserver())); break; case R.id.send_comment_button: String newComment = commentEditText.getText().toString(); if (!newComment.isEmpty()) { GithubIssue selectedItem = (GithubIssue) issuesSpinner.getSelectedItem(); selectedItem.comment = newComment; compositeDisposable.add(githubAPI.postComment(selectedItem.comments_url, selectedItem) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getCommentObserver())); } else { Toast.makeText(MainActivity.this, "Please enter a comment", Toast.LENGTH_LONG).show(); } break; } } private DisposableSingleObserver<List<GithubRepo>> getRepositoriesObserver() { return new DisposableSingleObserver<List<GithubRepo>>() { @Override public void onSuccess(List<GithubRepo> value) { if (!value.isEmpty()) { ArrayAdapter<GithubRepo> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, value); repositoriesSpinner.setAdapter(spinnerAdapter); repositoriesSpinner.setEnabled(true); } else { ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"User has no repositories"}); repositoriesSpinner.setAdapter(spinnerAdapter); repositoriesSpinner.setEnabled(false); } } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not load repositories", Toast.LENGTH_SHORT).show(); } }; } private DisposableSingleObserver<List<GithubIssue>> getIssuesObserver() { return new DisposableSingleObserver<List<GithubIssue>>() { @Override public void onSuccess(List<GithubIssue> value) { if (!value.isEmpty()) { ArrayAdapter<GithubIssue> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, value); issuesSpinner.setEnabled(true); commentEditText.setEnabled(true); sendButton.setEnabled(true); issuesSpinner.setAdapter(spinnerAdapter); } else { ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"Repository has no issues"}); issuesSpinner.setEnabled(false); commentEditText.setEnabled(false); sendButton.setEnabled(false); issuesSpinner.setAdapter(spinnerAdapter); } } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not load issues", Toast.LENGTH_SHORT).show(); } }; } private DisposableSingleObserver<ResponseBody> getCommentObserver() { return new DisposableSingleObserver<ResponseBody>() { @Override public void onSuccess(ResponseBody value) { commentEditText.setText(""); Toast.makeText(MainActivity.this, "Comment created", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not create comment", Toast.LENGTH_SHORT).show(); } }; } @Override public void onDialogPositiveClick(String username, String password) { this.username = username; this.password = password; loadReposButtons.setEnabled(true); } } 

Di sini kami menambahkan GithubRepoDeserializer yang sebelumnya dibuat sebagai TypeAdapter di GsonBuilder. Untuk menangani otentikasi untuk setiap panggilan, Interceptor ditambahkan ke OkHttpClient. Agar metode API mengembalikan tipe RxJava2, kami menambahkan RxJava2 CallAdapter ke klien kami.

10. Latihan: Menggunakan Retrofit dengan OAuth untuk Meminta Informasi Pengguna dari Twitter di Android


Latihan ini menjelaskan cara mengakses Twitter menggunakan Retrofit di Android. Kami akan menulis aplikasi yang dapat meminta dan menampilkan data pengguna untuk nama pengguna yang disediakan. Dalam latihan ini, kami hanya menggunakan aplikasi autentikasi Twitter dengan OAuth 2 untuk otorisasi. Untuk melakukan latihan ini, Anda harus memiliki akun Twitter. Selain itu, Anda harus membuka aplikasi Twitter dan membuat aplikasi baru untuk mendapatkan kunci konsumen dan rahasia konsumen. Kami akan membutuhkan ini nanti untuk meminta token OAuth kami.

10.1. Penyiapan proyek


Buat aplikasi Android yang disebut Retrofit Twitter. Gunakan com.vogella.android.retrofittwitter sebagai nama paket tingkat atas.

Untuk menggunakan Retrofit, tambahkan baris berikut ke file build.gradle

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' 

Tambahkan izin untuk mengakses Internet di manifes.

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.retrofittwitter"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 

10.2. Definisi API


Buat dua kelas data berikut, yang disebut OAuthToken dan UserDetails.

 package com.vogella.android.retrofittwitter; import com.google.gson.annotations.SerializedName; public class OAuthToken { @SerializedName("access_token") private String accessToken; @SerializedName("token_type") private String tokenType; public String getAccessToken() { return accessToken; } public String getTokenType() { return tokenType; } public String getAuthorization() { return getTokenType() + " " + getAccessToken(); } } 

 package com.vogella.android.retrofittwitter; public class UserDetails { private String name; private String location; private String description; private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } 

Kelas OAuthToken digunakan untuk menyimpan token pembawa yang kami minta dari Twitter, dengan kunci dan rahasia kami. Kami menggunakan anotasi @ SerializedName untuk mengatur nama Retrofit ke (de) bidang cerita bersambung.

Kelas UserDetails hanya menyimpan beberapa bidang dari respons Twitter saat meminta informasi pengguna. Kami tidak menampilkan semua data pengguna yang terkandung dalam respons, hanya nama, lokasi, URL, dan deskripsi.

Tetapkan REST API untuk Retrofit melalui antarmuka berikut:

 package com.vogella.android.retrofittwitter; import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; public interface TwitterApi { String BASE_URL = "https://api.twitter.com/"; @FormUrlEncoded @POST("oauth2/token") Call<OAuthToken> postCredentials(@Field("grant_type") String grantType); @GET("/1.1/users/show.json") Call<UserDetails> getUserDetails(@Query("screen_name") String name); } 

10.3. Buat Aktivitas


Ubah file activity_main.xml dan kelas MainActivity yang sesuai sebagai berikut:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.vogella.android.retrofittwitter.MainActivity"> <LinearLayout android:id="@+id/username_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:orientation="horizontal"> <TextView android:id="@+id/username_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:enabled="false" android:gravity="center_vertical" android:text="Username:" /> <EditText android:id="@+id/username_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:enabled="false" android:gravity="center" android:imeOptions="actionDone" android:inputType="text" android:maxLines="1" /> </LinearLayout> <Button android:id="@+id/request_token_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:onClick="onClick" android:text="Request token" /> <Button android:id="@+id/request_user_details_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/request_token_button" android:enabled="false" android:onClick="onClick" android:text="Request user details" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/request_user_details_button" android:layout_below="@id/username_container" android:gravity="center" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Name:" /> <TextView android:id="@+id/name_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Location:" /> <TextView android:id="@+id/location_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Url:" /> <TextView android:id="@+id/url_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Description:" /> <TextView android:id="@+id/description_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> </LinearLayout> </RelativeLayout> 

 package com.vogella.android.retrofittwitter; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import okhttp3.Credentials; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity { private String credentials = Credentials.basic("aConsumerKey", "aSecret"); Button requestTokenButton; Button requestUserDetailsButton; EditText usernameEditText; TextView usernameTextView; TextView nameTextView; TextView locationTextView; TextView urlTextView; TextView descriptionTextView; TwitterApi twitterApi; OAuthToken token; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestTokenButton = (Button) findViewById(R.id.request_token_button); requestUserDetailsButton = (Button) findViewById(R.id.request_user_details_button); usernameEditText = (EditText) findViewById(R.id.username_edittext); usernameTextView = (TextView) findViewById(R.id.username_textview); nameTextView = (TextView) findViewById(R.id.name_textview); locationTextView = (TextView) findViewById(R.id.location_textview); urlTextView = (TextView) findViewById(R.id.url_textview); descriptionTextView = (TextView) findViewById(R.id.description_textview); createTwitterApi(); } private void createTwitterApi() { OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", token != null ? token.getAuthorization() : credentials); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(TwitterApi.BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); twitterApi = retrofit.create(TwitterApi.class); } public void onClick(View view) { switch (view.getId()) { case R.id.request_token_button: twitterApi.postCredentials("client_credentials").enqueue(tokenCallback); break; case R.id.request_user_details_button: String editTextInput = usernameEditText.getText().toString(); if (!editTextInput.isEmpty()) { twitterApi.getUserDetails(editTextInput).enqueue(userDetailsCallback); } else { Toast.makeText(this, "Please provide a username", Toast.LENGTH_LONG).show(); } break; } } Callback<OAuthToken> tokenCallback = new Callback<OAuthToken>() { @Override public void onResponse(Call<OAuthToken> call, Response<OAuthToken> response) { if (response.isSuccessful()) { requestTokenButton.setEnabled(false); requestUserDetailsButton.setEnabled(true); usernameTextView.setEnabled(true); usernameEditText.setEnabled(true); token = response.body(); } else { Toast.makeText(MainActivity.this, "Failure while requesting token", Toast.LENGTH_LONG).show(); Log.d("RequestTokenCallback", "Code: " + response.code() + "Message: " + response.message()); } } @Override public void onFailure(Call<OAuthToken> call, Throwable t) { t.printStackTrace(); } }; Callback<UserDetails> userDetailsCallback = new Callback<UserDetails>() { @Override public void onResponse(Call<UserDetails> call, Response<UserDetails> response) { if (response.isSuccessful()) { UserDetails userDetails = response.body(); nameTextView.setText(userDetails.getName() == null ? "no value" : userDetails.getName()); locationTextView.setText(userDetails.getLocation() == null ? "no value" : userDetails.getLocation()); urlTextView.setText(userDetails.getUrl() == null ? "no value" : userDetails.getUrl()); descriptionTextView.setText(userDetails.getDescription().isEmpty() ? "no value" : userDetails.getDescription()); } else { Toast.makeText(MainActivity.this, "Failure while requesting user details", Toast.LENGTH_LONG).show(); Log.d("UserDetailsCallback", "Code: " + response.code() + "Message: " + response.message()); } } @Override public void onFailure(Call<UserDetails> call, Throwable t) { t.printStackTrace(); } }; } 

Ganti aConsumerKey dan aSecret dengan kunci dan rahasia konsumen yang diterima dari Twitter.

Lihat juga interseptor yang kami tambahkan ke klien Retrofit kami. Karena kami menggunakan OAuth, kredensial kami berbeda untuk setiap panggilan. Metode postCredentials harus memposting kredensial (kunci dan rahasia konsumen) dalam Skema Twitter Dasar. Akibatnya, panggilan ini mengembalikan token pembawa, yang Retrofit deserializes ke dalam kelas OAuthToken kami, yang kemudian disimpan di bidang token. Setiap permintaan lain dapat (dan harus) sekarang menggunakan token ini sebagai kredensial untuk otorisasi. Informasi pengguna juga diminta.

11. Sumber Daya Retrofit


Mengkonsumsi API dengan Retrofit tutorial

Dalam seri blog dept tentang Retrofit

Mengkonsumsi API dengan Retrofit

Source: https://habr.com/ru/post/id428736/


All Articles