I'm studying WebService and consumption on Android with Retrofit2.
I've done tests with public APIs like ViaCEP and FIPE and I can use the retrofit quietly, very easy but when I set up my own webservice using JAX-RS with Jersey I have problems. I'll post the details so you can help me if you can.
The error message is:
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2
Result when calling resource in WebService - JSON file:
URL: http: // MEUSERVER: 8080 / movie director / movies
{
"filmes": {
"filme": [
{
"id": 1,
"titulo": "E o vento levou",
"ano": "1961-01-12T00:00:00-03:00",
"idioma": "Português",
"atorPrincipal": "ValdikSoriano",
"locado": false,
"valorDiaria": 2.65
},
{
"id": 2,
"titulo": "Titanic",
"ano": "1998-01-12T00:00:00-02:00",
"idioma": "Português",
"atorPrincipal": "Dicaprio",
"locado": false,
"valorDiaria": 2.65
}
]
}
}
My web.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>LocadoraFilmes</display-name>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>service.ApplicationJAXRS</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
My file ApplicationJAXRS.java
:
@ApplicationPath("resources")
public class ApplicationJAXRS extends Application {
@Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(new JettisonFeature());
return singletons;
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(FilmeService.class);
return classes;
}
}
The project I did is using JPA, so there are JPA annotation besides JAX-RS:
File Filme.java
:
@Entity(name = "filme")
@Table(name = "filme")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Filme implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@XmlElement(name = "id")
private Integer id;
@Column(nullable = false)
private String titulo;
@Column(nullable = false)
@Temporal(TemporalType.DATE)
private Date ano;
@Column(nullable = false)
private String idioma;
@Column(nullable = false)
private String atorPrincipal;
@Column(nullable = false)
private Boolean locado = false;
@Column(nullable = true)
private Float valorDiaria;
public Filme() {
}
public Filme(Integer id, String titulo, Date ano, String atorPrincipal, Boolean locado, Float valorDiaria) {
this.id = id;
this.titulo = titulo;
this.ano = ano;
this.atorPrincipal = atorPrincipal;
this.locado = locado;
this.valorDiaria = valorDiaria;
}
//GET and SET
}
File FilmeService.java
:
@Path("/filmes")
@Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
@Consumes({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public class FilmeService {
private FilmeDAO daoFilme = new FilmeDAO();
@GET
public List<Filme> listarFilmes(){
return daoFilme.findAll();
}
@GET
@Path(value = "{id}")
public Filme getFilmeByID(@PathParam(value="id")int id){
return daoFilme.findById(id);
}
}
On the Android device, as I mentioned, I use the retrofit. Here is the FilmeAPI.java
file there:
public interface FilmeAPI {
@GET("filmes")
Call<List<Filme>> getFilmes();
//Gson gson = new GsonBuilder().registerTypeAdapter(Filme.class, new FilmeDeserializer()).create();
Gson gson = new GsonBuilder().setLenient().create();
public static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.25.2:8080/locadorafilmes-0.0.1-SNAPSHOT/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
The Filme.java
class in android:
public class Filme implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String titulo;
private Date ano;
private String idioma;
private String atorPrincipal;
private Boolean locado = false;
private Float valorDiaria;
//GET and SET
}
And the file MainActivity.java
:
public class MainActivity extends AppCompatActivity {
private ListView lvFilmes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvFilmes = (ListView) findViewById(R.id.listview_filmes);
connectWebServiceFilmes();
}
private void connectWebServiceFilmes(){
FilmeAPI filmeAPI = FilmeAPI.retrofit.create(FilmeAPI.class);
Call<List<Filme>> callFilme = filmeAPI.getFilmes();
callFilme.enqueue(new Callback<List<Filme>>() {
@Override
public void onResponse(Call<List<Filme>> call, Response<List<Filme>> response) {
Log.i("Teste", "Dentro do onResponse");
List<Filme> filmes = new ArrayList<Filme>();
if (response.body()!=null){
Log.i("Teste", "Response não esta vazio! " + response.isSuccessful());
filmes.addAll(response.body());
if (filmes!=null){
onUpdateListViewFilmes(getBaseContext(), filmes);
}
else{
Log.e("Teste", "Array de Filmes vazio!");
}
}
}
@Override
public void onFailure(Call<List<Filme>> call, Throwable t) {
Log.e("Teste", "Erro ao baixar dados. Mensagem: " + t.getMessage() +
" \n Local Mensagem: " + t.getLocalizedMessage() +
" \n TrackTrace: " + t.getStackTrace());
}
});
}
private void onUpdateListViewFilmes(Context context, List<Filme> filmes){
FilmeAdapter adapter = new FilmeAdapter(context, filmes);
lvFilmes.setAdapter(adapter);
}
}