Liste mit Elementen mit Android / Robospice und Spring Boot mit HATEOAS

Mit Hilfe von Spring Boot und Robospice ist es möglich schnell und einfach eine REST API und einen Android Consumer für Listen zu erstellen. Dabei ermöglicht Spring Boot eine einfache CRUD API zu erstellen. Mi Hilfe der Annotation @RepositoryRestResource und der Klasse PagingAndSortingRepository werden automatisch alle CRUD Funktionen inklusiv der Methode “findByName” für die Entity Task erstellt.

@RepositoryRestResource(collectionResourceRel = “tasks”, path = “tasks”)
public interface TaskEntityRepository extends PagingAndSortingRepository<Task, Long> {

List<TaskEntity> findByName(@Param("name") String name);

}

Dafür werden die folgenden Spring Boot Dependencies benötigt:


org.springframework.boot
spring-boot-starter-parent
1.1.7.RELEASE
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>


com.h2database
h2

Um einen Task per REST zu erstellen verwendet wird die URL http://localhost:8080/tasks und als Content

{
“name”: “f1231231”,
“description”: “foobar”
}

mit POST verwendet. Dabei ist wichtig das “path” der Url entspricht unter der die CRUD Funktionen zur Verfügung gestellt werden. Das Ergebnis unter http://localhost:8080/tasks:

{
“_links” : {
“self” : {
“href” : “http://localhost:8080/tasks{?page,size,sort}",
“templated” : true
},
“search” : {
“href” : “http://localhost:8080/tasks/search"
}
},
“_embedded” : {
“tasks” : [ {
“name” : “foo”,
“description” : null,
“repeat” : 0,
“points” : 0,
“timePeriodForThisTask” : 0,
“startTime” : “2015-01-10T17:16:28.346+0000”,
“_links” : {
“self” : {
“href” : “http://localhost:8080/tasks/1"
},
“room” : {
“href” : “http://localhost:8080/tasks/1/room"
}
}
} ]
},
“page” : {
“size” : 20,
“totalElements” : 1,
“totalPages” : 1,
“number” : 0
}
}

Und die dazu gehörende Task Klasse:

@Entity{
public class TaskEntity {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long   id;
private String name;

private String description;

public TaskEntity() {
}

public String getDescription() {
return this.description;
}

public long getId() {
    return this.id;
}

public String getName() {
    return this.name;
}

public void setName(final String name) {
this.name = name;
}

public void setDescription(final String description) {
this.description = description;
}
}

Möchte man nun die Liste von Task mit Robospice auslesen, muss man beachten das Spring Boot an dieser Stelle HATEOAS verwendet. Das bedeutet man muss in der Android App folgende Klasse erstellen oder die Task Klasse von oben wiederverwenden:

import org.apache.commons.lang3.builder.ToStringBuilder;

/**
* Task for homework.
*/
public class Task {

private Long   id;

private String name;

private String description;


public String getDescription() {
    return this.description;
}

public Long getId() {
    return this.id;
}

public String getName() {
    return this.name;
}

public void setDescription(final String description) {
    this.description = description;
}

public void setId(final Long id) {
    this.id = id;
}

public void setName(final String name) {
    this.name = name;
}

@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

}

Um das ganze mit Robospice in Android und mit HATEOAS auslesen zu können braucht man in der Android App folgende Dependency : groupId: org.springframework.hateoas artefactId: spring-hateoas

compile ('org.springframework.hateoas:spring-hateoas:0.16.0.RELEASE') {
    exclude group: 'commons-logging', module: 'commons-logging'
    exclude module: 'spring-core'
    exclude module: 'spring-aop'
    exclude module: 'spring-web'
    exclude module: 'spring-beans'
    exclude module: 'spring-webmvc'
    exclude module: 'spring-context'

}

Damit kann man dann mittels

final ResponseEntity<Resources<Task>> taskEntity =
        getRestTemplate().exchange(url, HttpMethod.GET, getRequestEntity(), new ParameterizedTypeReference<Resources<Task>>() {});

sich die Elemente inklusive aller Links per REST holen. Mit taskEntity.getBody().getContent() bekommt man dann eine Liste der Tasks und mit taskEntity.getBody().getLinks() eine Liste der Links. Die gesamte GetTasksRequest Klasse sieht dann wie folgt aus:

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.octo.android.robospice.request.springandroid.SpringAndroidSpiceRequest;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;

import java.util.ArrayList;
import java.util.HashMap;

public class GetTasksRequest extends
SpringAndroidSpiceRequest<GetTasksRequest.TaskList> {

public GetTasksRequest() {
    super(TaskList.class);
}

public String createCacheKey() {
    return "task.tasks";
}

@Override
public TaskList loadDataFromNetwork() throws Exception {

    final String url = "http://localhost:8080/tasks?size=20&page=0";

    final ResponseEntity<Resources<Task>> taskEntity =
            getRestTemplate().exchange(

url,
HttpMethod.GET,
getRequestEntity(),
new ParameterizedTypeReference<Resources>() {});

    if (taskEntity.getBody() == null) {
        return new TaskList();
    }
    return new TaskList(Lists.newArrayList(taskEntity.getBody().getContent()));
}

public static class TaskList {

    private ArrayList<Task> tasks;

    public TaskList() {
    }

    public TaskList(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    public ArrayList<Task> getTasks() {
        return tasks;
    }

    public void setTasks(ArrayList<Task> tasks) {
        this.tasks = tasks;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }
}

}

und die folgende RestUtil Klasse

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import java.util.Collections;

public class RestUtils {

public static HttpEntity<Void> getRequestEntity() {

    HttpHeaders requestHeaders = new HttpHeaders();
    requestHeaders.setAccept(Collections.singletonList(new MediaType("application", "hal+json")));
    return new HttpEntity<Void>(requestHeaders);
}

}

Das ganze kann dann im Listener verwendet werden:

new RequestListener<GetTasksRequest.TaskList>() {

@Override
public void onRequestFailure(final SpiceException arg0) {
  Toast.makeText(TaskListFragment.this.getActivity(),
     "Fehler", Toast.LENGTH_LONG).show();
}

@Override
public void onRequestSuccess(final GetTasksRequest.TaskList tasks) {
   final ArrayAdapter listAdapter = new ArrayAdapter<Task>(getActivity(), 

android.R.layout.simple_list_item_1, android.R.id.text1);
if(tasks.getTasks() != null) listAdapter.addAll(tasks.getTasks());
setListAdapter(listAdapter);
}
});