Usando o Retrofit 2.x como um cliente REST - Tutorial

1. Retrofit


1.1 O que é Retrofit?


Retrofit é um cliente REST para Java e Android. Isso facilita a obtenção e o carregamento de JSON (ou outros dados estruturados) por meio de um serviço da Web baseado em REST. No Retrofit, você configura qual conversor é usado para serializar dados. Geralmente, o GSon é usado para JSON, mas você pode adicionar seus próprios conversores para processar XML ou outros protocolos. O Retrofit usa a biblioteca OkHttp para solicitações HTTP.

Você pode criar objetos Java baseados em JSON usando a seguinte ferramenta: www.jsonschema2pojo.org Isso pode ser útil para criar estruturas de dados Java complexas a partir de JSON existente.


1.2 Usando Retrofit


Para trabalhar com o Retrofit, você precisará das três classes a seguir:

  • Classe de modelo usada como modelo JSON
  • Interfaces que identificam possíveis operações HTTP
  • A classe Retrofit.Builder é uma instância que usa a interface e a API do Builder para definir uma definição de terminal de URL para operações HTTP.

Cada método de interface representa uma das possíveis chamadas de API. Ele deve ter uma anotação HTTP (GET, POST etc.) para indicar o tipo de solicitação e o URL relativo. O valor de retorno conclui a resposta no objeto Call com o tipo de resultado esperado.

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

Você pode usar os blocos de substituição e os parâmetros de consulta para configurar o URL. O bloco de substituição é anexado ao URL relativo usando {}. Usando a anotação @ Path para um parâmetro de método, o valor desse parâmetro é vinculado a um bloco de substituição específico.

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

Os parâmetros de consulta são adicionados usando a anotação @ Query no parâmetro de método. Eles são adicionados automaticamente no final do URL.

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

A anotação @ Body para o parâmetro method diz ao Retrofit para usar o objeto como o corpo da solicitação a ser chamada.

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

2. Pré-requisitos


Os exemplos a seguir usam o IDE Eclipse com o sistema de construção Gradle.
Este exercício pressupõe que você esteja familiarizado com o Gradle e usando o Gradle com o Eclipse .

Outros ambientes de desenvolvimento, como o Visual Studio Code ou IntelliJ, fazem o mesmo, para que você possa usar sua ferramenta favorita.

3. Exercício: Primeiro Cliente de Retrofit


Neste exercício, você criará um cliente REST independente. As respostas são geradas pelo servidor Mock.

3.1 Criando e configurando um projeto


Crie um novo projeto Gradle chamado com.vogella.retrofitgerrit. Adicione um novo pacote ao src / main / java chamado com.vogella.retrofitgerrit.

Inclua as seguintes dependências no arquivo 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 Definir API e Adaptador de Retrofit


Na resposta JSON da Gerrit, estamos interessados ​​apenas na questão das mudanças. Portanto, crie a seguinte classe de dados no pacote padrão adicionado anteriormente.

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

Defina a API REST para Retrofit através da seguinte interface.

 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); } 

Crie a seguinte classe de controlador. Essa classe cria um cliente Retrofit, chama a API Gerrit e processa o resultado (imprime o resultado da chamada no console).

 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(); } } 

Crie uma classe com um método principal para iniciar o controlador.

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

4. Adapte conversores e adaptadores


4.1 Conversores Retrofit


O retrofit pode ser configurado para usar um conversor específico. Este conversor lida com a (des) serialização de dados. Vários conversores já estão disponíveis para vários formatos de serialização.

  • Para converter para JSON e vice-versa:
    • Gson: com.squareup.retrofit: conversor-gson
    • Jackson: com.squareup.retrofit: conversor-jackson
    • Moshi: com.squareup.retrofit: conversor-moshi

  • Para converter em buffers de protocolo e vice-versa:
    • Protobuf: com.squareup.retrofit: conversor-protobuf
    • Fio: com.squareup.retrofit: converter-wire

  • Para converter para XML e vice-versa:
    • XML simples: com.squareup.retrofit: converter-simplexml


Além dos conversores listados, você também pode criar o seu próprio para processar outros protocolos estendendo a classe Converter.Factory.

4.2 Adaptadores de adaptação


O retrofit também pode ser estendido com adaptadores para interface com outras bibliotecas, como RxJava 2.x, Java 8 e Guava.

Uma visão geral dos adaptadores disponíveis pode ser encontrada em Github square / retrofit / retrofit-adapters / .

Por exemplo, o adaptador RxJava 2.x pode ser conectado usando Gradle:

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

ou usando o Apache Maven:

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

Para adicionar um adaptador, você deve usar o método retrofit2.Retrofit.Builder.addCallAdapterFactory (Factory).

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

Usando esse adaptador, as interfaces Retrofit podem retornar tipos RxJava 2.x, por exemplo, Observable, Flowable ou Single, etc.

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

5. Retrofit de autenticação


O Retrofit suporta chamadas de API que requerem autenticação. A autenticação pode ser feita usando um nome de usuário e senha (autenticação Http Basic) ou um token de API.

Existem duas maneiras de gerenciar a autenticação. O primeiro método é gerenciar o cabeçalho da solicitação usando anotações. Outra maneira é usar um interceptor OkHttp para isso.

5.1 Autenticação com anotações


Suponha que você deseje solicitar informações sobre um usuário para quem a autenticação é necessária. Você pode fazer isso adicionando um novo parâmetro à definição da API, por exemplo:

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


Usando a anotação @ Header ("Authorization"), você solicita ao Retrofit para adicionar o cabeçalho de Autorização à solicitação com o valor que você está passando.

Para gerar credenciais para autenticação básica, você pode usar a classe OkHttps Credentials com seu método base (String, String). O método aceita um nome de usuário e uma senha e retorna credenciais de autenticação para o esquema Http Basic.

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

Se você deseja usar o token da API e não o esquema Básico, basta chamar o método getUserDetails (String) com o seu token.

5.2 Autenticação com interceptores OkHttp.


O método acima adiciona credenciais apenas se você solicitar dados do usuário. Se houver mais chamadas que exijam autenticação, você poderá usar um interceptador para fazer isso. Um interceptador é usado para modificar cada solicitação antes de ser executado e definir o cabeçalho da solicitação. A vantagem é que você não precisa adicionar @ Header ("Autorização") a cada definição de método da API.

Para adicionar um interceptador, você deve usar o método okhttp3.OkHttpClient.Builder.addInterceptor (Interceptor) no 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(); 

O cliente criado por OkHttp deve ser adicionado ao seu cliente Retrofit usando o método retrofit2.Retrofit.Builder.client (OkHttpClient).

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

Como você notou, a classe Credentials é usada aqui para autorização básica.
Novamente, se você quiser usar o token da API, basta usar o token.

6. Exercício: Usando o Retrofit para solicitar o Gerrit em Java


A seção a seguir descreve como criar um aplicativo Java mínimo que use o Retrofit para recuperar objetos de mudança abertos da API Gerrit. Os resultados são impressos no console.

6.1 Criando e configurando um projeto


Este exercício pressupõe que você esteja familiarizado com Gradle e Buildship for Eclipse .

Crie um novo projeto Gradle chamado com.vogella.java.retrofitgerrit. Adicione um novo pacote ao src / main / java chamado com.vogella.java.retrofitgerrit.

Inclua as seguintes dependências no arquivo 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 Definir API e Adaptador de Retrofit


Na resposta JSON da Gerrit, estamos interessados ​​apenas na questão das mudanças. Portanto, crie a seguinte classe de dados no pacote padrão adicionado anteriormente.

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

Defina a API REST para Retrofit usando a seguinte interface.

 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); } 

Crie a seguinte classe de controlador. Essa classe cria um cliente Retrofit, chama a API Gerrit e processa o resultado (imprime o resultado da chamada no console).

 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(); } } 

Crie uma classe com um método principal para iniciar o controlador.

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

7. Exercício: Usando o Retrofit para converter uma resposta XML de um feed RSS


Esta seção descreve como usar o Retrofit para transformar uma resposta XML usando o SimpleXMLConverter.

É criado um aplicativo Java mínimo que solicita o feed RSS da Vogella ( http://vogella.com/article.rss ) e imprime o nome do canal, o título e os links do artigo.

7.1 Criando e configurando um projeto


Este exercício pressupõe que você esteja familiarizado com Gradle e Buildship for Eclipse .

Crie um novo projeto Gradle chamado com.vogella.java.retrofitxml. Inclua um novo pacote no src / main / java chamado com.vogella.java.retrofitxml.

Inclua as seguintes dependências no arquivo build.gradle.

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

7.2 Definindo uma exibição XML


Um feed RSS é o seguinte:

 <?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> 

Além do cabeçalho XML, este arquivo consiste em vários elementos XML. Um elemento RSS contém um elemento de canal que contém outros elementos (por exemplo, título, descrição, pubDate) e vários elementos de itens (artigos).

Crie as duas classes de dados a seguir: RSSFeed e Article.

 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; } } 

A classe Article representa um artigo e mantém apenas o título e o link para o artigo. Estes são os únicos campos que nos interessam.

Anotação @ Root marca uma classe como sujeita a (des) serialização. Opcionalmente, você pode especificar um nome na anotação @ Root que corresponda ao nome do elemento XML. Se nenhum nome for especificado, o nome da classe será usado como o nome do elemento XML. Como o nome da classe (RSSFeed) é diferente do nome do elemento XML (rss), precisamos especificar o nome.

Quando strict é definido como false, a análise estrita é desativada. Isso indica ao analisador para não interromper ou lançar uma exceção se um elemento ou atributo XML for encontrado para o qual nenhum mapeamento é fornecido. Como o elemento rss possui um atributo de versão para o qual não há campo correspondente, o aplicativo gerará um erro se o parâmetro strict não estiver definido como false.

Usando a anotação @ Element, um elemento XML é representado. Se necessário, você pode especificar o nome XML do elemento representado por este campo. Se nenhum nome for especificado, o nome do campo será usado.

O campo articleList é anotado com @ ElementList. Isso mostra que esse campo é usado para armazenar uma coleção (no nosso caso: Lista) de elementos XML com o mesmo nome. Quando inline é definido como true, isso significa que os itens da coleção são listados um após o outro imediatamente dentro do item especificado e não possuem um pai intermediário.

Usando a anotação @ Path, você pode especificar o caminho para um elemento XML na árvore XML. Isso é útil se você não deseja modelar uma árvore XML completa com objetos Java. Para o nome do canal e vários elementos do item, podemos apontar diretamente para elementos específicos no elemento do canal.

7.3 Definindo uma API e Adaptador de Retrofit


Defina a API REST para Retrofit através da seguinte interface.

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

Crie a seguinte classe de controlador. Essa classe cria um cliente Retrofit, chama a API Vogella e processa o resultado.

 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(); } } 

O último passo é criar uma classe com um método principal para iniciar o controlador.

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

8. Exercício: Criando um aplicativo para solicitar StackOverflow


StackOverflow é um site popular para problemas relacionados à programação. Ele também fornece uma API REST, bem documentada na página da API Stackoverflow .

Neste exercício, você usará a biblioteca REST Retrofit. Você o usará para consultar as perguntas da tag StackOverflow e suas respostas.

No nosso exemplo, usamos o seguinte URL de solicitação. Abra esse URL em um navegador e veja a resposta.

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

8.1 Criando e configurando um projeto


Crie um aplicativo Android chamado com.vogella.android.stackoverflow. Use com.vogella.android.stackoverflow como o nome de um pacote de nível superior.

Inclua as seguintes dependências no arquivo build.gradle.

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

8.2 Criando um modelo de dados


Estamos interessados ​​em perguntas e respostas do Stackoverflow. Para esse fim, crie as duas classes de dados a seguir.

 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 Criando atividade e layout


Defina activity_main.xml para sua atividade.

 <?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> 

Inclua a classe de visualização do reciclador de adaptador chamada RecyclerViewAdapter em seu projeto.

Uma implementação possível é a seguinte.

 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(); } } 

Modifique a classe MainActivity da seguinte maneira:

 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 Usando um provedor de dados falso


Crie um provedor de dados falso e preencha o botão giratório com perguntas falsas e a reciclagem com respostas falsas (depois de alterar a seleção no botão giratório).

 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; } } 

Agora configure o spinner e a recyclerview para usar esses dados falsos.

 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 Adicionando dependências e permissões de gradle


Inclua as seguintes dependências no arquivo build.gradle.

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

Adicione permissão para acessar a Internet no manifesto.

 <?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 Definindo uma API e Adaptador de Retrofit


A API Stackoverflow agrupa respostas ou perguntas em um objeto JSON chamado itens. Para lidar com isso, crie a seguinte classe de dados chamada ListWrapper. Isso é necessário para processar o wrapper dos elementos Stackoverflow. Essa classe usa um parâmetro de tipo e simplesmente compacta uma lista de objetos desse tipo.

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

Defina a API REST para Retrofit através da seguinte interface.

 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 Configuração de atividade


Altere o código MainActivity da seguinte maneira.

 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 Opcional: obtendo uma imagem de perfil do usuário


Altere o layout das linhas na visualização da recicladora para exibir também a imagem do perfil do usuário. Estenda seu modelo de dados para obter uma imagem de perfil do usuário que respondeu à pergunta. Adicione um ImageView às linhas de layout e use a biblioteca Glide para carregar a imagem.

8.9 Opcional: use layouts diferentes para linhas pares e ímpares


Altere a implementação do adaptador para usar layouts diferentes para linhas pares e ímpares.

Isso requer a criação de diferentes layouts com base no tipo de dados. Use getItemViewType () no adaptador.

8.10 Opcional: Tratamento de erros de rede


Se houver uma falha na rede, mostre o botão Solicitar novamente em vez da interface principal do usuário.

9. Exercício: usando o retrofit para acessar a API do GitHub no Android


Este exercício descreve como listar todos os repositórios do GitHub para um usuário em um aplicativo Android usando o Retrofit. Você pode selecionar o repositório na lista suspensa e especificar as discussões relacionadas ao usuário para o repositório selecionado.

Em seguida, você pode selecionar uma discussão no campo suspenso adicional e postar um comentário. DialogFragment será usado para inserir credenciais para autenticação.

Verifique se você possui uma conta no Github, pois isso é necessário para este exercício. Como o Retrofit será usado com o RxJava2 durante este exercício, preste atenção também ao Tutorial do RxJava2 .

9.1 Configuração do projeto


Crie um aplicativo Android chamado Retrofit Github. Use com.vogella.android.retrofitgithub como o nome do pacote de nível superior e use um modelo vazio. Verifique se o sinalizador "Compatibilidade com versões anteriores" está marcado.

Para usar o Retrofit e o RxJava2 CallAdapter , adicione as seguintes dependências ao arquivo 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' 

Adicione permissão para acessar a Internet no manifesto.

 <?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 Definição da API


Crie as duas classes de dados a seguir: GithubIssue e 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); } } 

A partir das informações do repositório, apenas o nome e o URL do repositório serão exibidos na lista suspensa. Também adicionamos o proprietário à classe de dados, pois o nome do proprietário é necessário para solicitar discussões posteriormente.

Como mostramos apenas o ID e o título da discussão no campo suspenso, criamos um campo para cada um deles. Além disso, a resposta do Github contém um URL para postar um comentário, que é armazenado no campo comments_url. Para postar um novo comentário na API do Github posteriormente, adicione um campo chamado comment. API do Githubindica que o conteúdo do comentário deve ser vinculado a um campo nomeado body na solicitação JSON. Como o Retrofit (de) serializa todos os campos com base em seus nomes e como não queremos usar o corpo como o nome do campo em nossa classe GithubIssue, usamos a anotação @SerializedName. Com esta anotação, podemos alterar o nome com o qual o campo é (des) serializado para JSON.

Infelizmente, a classe GithubRepo não é suficiente para solicitar todas as informações necessárias sobre o repositório. Como você vê aqui, o proprietário do repositório é um objeto JSON separado na resposta do repositório e, portanto, geralmente precisa da classe Java apropriada para (des) serializar. Felizmente, o Retrofit permite adicionar seu próprio JSONDeserializer digitado para controlar a desserialização de um tipo específico. Sempre que um objeto de um determinado tipo precisa ser desserializado, esse desserializador personalizado é usado.

Para fazer isso, adicione a seguinte classe GithubRepoDeserialzer.

 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; } } 

Defina a API REST para Retrofit através da seguinte interface:

 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); } 

Você pode ter uma pergunta sobre a anotação @ Url. Usando esta anotação, podemos especificar o URL para esta solicitação. Isso nos permite alterar o URL de cada solicitação dinamicamente. Precisamos disso para o campo comments_url da classe GithubIssue.

As anotações @ Path vinculam o valor do parâmetro à variável correspondente (chaves) no URL da solicitação. Isso é necessário para indicar o proprietário e o nome do repositório para o qual as discussões devem ser solicitadas.

9.3 Caixa de diálogo Criar credencial


Para dar ao usuário a capacidade de armazenar suas credenciais no aplicativo, DialogFragment é usado. Portanto, crie a seguinte classe chamada CredentialsDialog e também adicione um arquivo de layout chamado dialog_credentials.xml à pasta de layout de recursos.

O resultado deve ser semelhante à captura de tela a seguir.



 <?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 Criar atividade


Modifique activity_main.xml da seguinte maneira.

 <?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> 


Dois botões (para baixar repositórios e enviar comentários), dois Spinner (campo suspenso para exibir repositórios e discussões) e EditText (para escrever comentários). Para iniciar o CredentialsDialog, use o menu na barra de ferramentas do Android. Para criá-lo, adicione um arquivo xml de menu chamado menu_main.xml à pasta de recursos do menu (crie uma pasta se ela não existir).

 <?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> 

Como usamos o widget Barra de ferramentas, é necessário desativar a barra de ação por padrão. Para fazer isso, modifique o arquivo de estilo xml como mostrado abaixo.

 <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> 

Altere seu código de atividade para o seguinte.

 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); } } 

Aqui, adicionamos o GithubRepoDeserializer criado anteriormente como um TypeAdapter no GsonBuilder. Para lidar com a autenticação de cada chamada, um Interceptor foi adicionado ao OkHttpClient. Para que os métodos da API retornem os tipos RxJava2, adicionamos o RxJava2 CallAdapter ao nosso cliente.

10. Exercício: Usando o Retrofit com OAuth para solicitar informações do usuário do Twitter no Android


Este exercício descreve como acessar o Twitter usando o Retrofit no Android. Escreveremos um aplicativo que pode solicitar e exibir dados do usuário para o nome de usuário fornecido. Neste exercício, usamos somente o aplicativo de autenticação do Twitter com o OAuth 2 para autorização. Para fazer este exercício, você precisa ter uma conta no Twitter. Além disso, você precisa acessar os aplicativos do Twitter e criar um novo aplicativo para obter sua chave e segredo do consumidor. Precisamos disso posteriormente para solicitar nosso token OAuth.

10.1 Configuração do projeto


Crie um aplicativo Android chamado Retrofit Twitter. Use com.vogella.android.retrofittwitter como o nome de um pacote de nível superior.

Para usar o Retrofit, inclua as seguintes linhas no arquivo build.gradle

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

Adicione permissão para acessar a Internet no manifesto.

 <?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 Definição da API


Crie as duas classes de dados a seguir, chamadas OAuthToken e 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; } } 

A classe OAuthToken é usada para armazenar o token do portador que solicitamos do Twitter, com nossa chave e segredo. Usamos a anotação @ SerializedName para definir o nome Retrofit para (des) serializar campos.

A classe UserDetails simplesmente salva vários campos da resposta do Twitter ao solicitar informações do usuário. Não mostramos todos os dados do usuário contidos na resposta, apenas o nome, local, URL e descrição.

Defina a API REST para Retrofit através da seguinte interface:

 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 Criar atividade


Modifique o arquivo activity_main.xml e a classe MainActivity correspondente da seguinte maneira:

 <?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(); } }; } 

Substitua aConsumerKey e aSecret pela chave e segredo do consumidor recebidos do Twitter.

Veja também o interceptador que adicionamos ao nosso cliente Retrofit. Como usamos o OAuth, nossas credenciais são diferentes para cada chamada. O método postCredentials deve postar as credenciais (chave e segredo do consumidor) no esquema básico do Twitter. Como resultado, essa chamada retorna um token de portador, que Retrofit desserializa em nossa classe OAuthToken, que é então armazenada no campo token. Qualquer outra solicitação pode (e deve) agora usar esse token como credenciais para autorização. As informações do usuário também são solicitadas.

11. Recursos de modernização


Tutorial Consumindo APIs com Retrofit

Na série de blogs de departamento sobre Retrofit

Consumindo APIs com Retrofit

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


All Articles