× Home Tutorials Training Consulting Products Books Company Donate Contact us









NOW Hiring

Quick links

Share

This tutorial explains the usage of the Retrofit library as REST client.

1. Retrofit

1.1. What is Retrofit

Retrofit is a REST Client for Android and Java by Square. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based webservice. In Retrofit you configure which converter is used for the data serialization. Typically for JSON you use GSon, but you can add custom converters to process XML or other protocols. Retrofit uses the OkHttp library for HTTP requests.

You can generate Java objects based on JSON via the following URL: http://www.jsonschema2pojo.org/ This can be useful to create complex Java data structures from existing JSON.

1.2. Using Retrofit

To work with Retrofit you need basically three classes.

  • Model class which is used to map the JSON data to

  • Interfaces which defines the possible HTTP operations

  • Retrofit.Builder class - Instance which uses the interface and the Builder API which allows defining the URL end point for the HTTP operation.

Every method of an interface represents one possible API call. It must have a HTTP annotation (GET, POST, etc.) to specify the request type and the relative URL. The return value wraps the response in a Call object with the type of the expected result.

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

You can use replacement blocks and query parameters to adjust the URL. A replacement block is added to the relative URL with {}. With the help of the @Path annotation on the method parameter, the value of that parameter is bound to the specific replacement block.

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

Query parameters are added with the @Query annotation on a method parameter. They are automatically added at the end of the URL.

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

The @Body annotation on a method parameter tells Retrofit to use the object as the request body for the call.

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

2. Retrofit converters and adapters

2.1. Retrofit Converters

Retrofit can be configured to use a specific converter. This converter handles the data (de)serialization. Several converters are already available for various serialization formats.

  • To convert to and from JSON:

    • Gson: com.squareup.retrofit:converter-gson

    • Jackson: com.squareup.retrofit:converter-jackson

    • Moshi: com.squareup.retrofit:converter-moshi

  • To convert to and from Protocol Buffers:

    • Protobuf: com.squareup.retrofit:converter-protobuf

    • Wire: com.squareup.retrofit:converter-wire

  • To convert to and from XML:

    • Simple XML: com.squareup.retrofit:converter-simplexml

Besides the listed converters, you can also create custom converters to process other protocols by subclassing the Converter.Factory class.

2.2. Retrofit Adapters

Retrofit can also be extended by adapters to get involved with other libraries like RxJava 2.x, Java 8 and Guava.

An overview for available adapters can be found on Github square/retrofit/retrofit-adapters/.

For example the RxJava 2.x adapter can be obtained by using Gradle:

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

or using Apache Maven:

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

In order to add an adapter the retrofit2.Retrofit.Builder.addCallAdapterFactory(Factory) method has to be used.

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

With this adapter being applied the Retrofit interfaces are able to return RxJava 2.x types, e.g., Observable, Flowable or Single and so on.

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

3. Retrofit authentication

Retrofit supports you with API calls that need authentication. Authentication can be done by using a username and a password (Http Basic authentication) or an API token.

There are two ways, how you can handle the authentication. The first method would be to manipulate the header for the request with the help of annotations. Another possibility would be to use an OkHttp interceptor for that.

3.1. Authentication with annotations

Assume, that you want to request your user details, which requires you to authenticate. You can do this by adding a new parameter to your API definition, like the following:

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

With the help of the @Header("Authorization") annotation you tell Retrofit to add the Authorization field to the request header with the value you provide for credentials.

To generate Basic authentication credentials, you can use OkHttps Credentials class with its basic(String, String) method. The method takes the username and the password and returns the authentication credential for the Basic scheme.

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

If you want to use an API token and no Basic authentication, just call the getUserDetails(String) method with your token instead.

3.2. Authentication with OkHttp interceptors

The above method only adds the credentials, if you request your user details. If you have more calls that require you to authenticate, you can use an interceptor for this. An interceptor is used to modify each request before it is performed and alters the request header. The advantage is, that you don’t have to add @Header("Authorization") to each API method definition.

To add an interceptor, you have to use the okhttp3.OkHttpClient.Builder.addInterceptor(Interceptor) method on the 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();

The created OkHttp client has to be added to your Retrofit client with the retrofit2.Retrofit.Builder.client(OkHttpClient) method.

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

You may again noticed the usage of the Credentials class for Basic authentication. Again, if you want to use a API token, just use the token instead.

4. Exercise: Using Retrofit to query Gerrit in Java

The following section describes how to create a minimal Java application which uses Retrofit to query the subjects of open changes from the Gerrit API. The results are printed on the console.

4.1. Project creation and setup

This exercise assumes that you are familiar with Gradle and Buildship for Eclipse.

Create a new Gradle project with the name com.vogella.java.retrofitgerrit. Add a new package to src/main/java with the name com.vogella.java.retrofitgerrit.

Add the following dependencies to your build.gradle file.

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

4.2. Define the API and the Retrofit adapter

In the JSON reply from Gerrit we are only interested in the subject of the changes. Therefore create the following data classes in the previously added default package.

package com.vogella.java.retrofitgerrit;

public class Change {
        String subject;

        public String getSubject() {
                return subject;
        }

        public void setSubject(String subject) {
                this.subject = subject;
        }
}

Define the REST API for Retrofit via the following 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);
}

Create the following controller class. This class creates the Retrofit client, calls the Gerrit API and handles the result (prints the result of the call on the 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();
        }
}

Create a class with a main-method to start the controller.

package com.vogella.java.retrofitgerrit;

public class Main {

        public static void main(String[] args) {
                Controller controller = new Controller();
                controller.start();
        }
}

5. Exercise: Using Retrofit to convert XML response from an RSS feed

This section describes the usage of Retrofit to convert a XML response with the help of the Simple XML Converter.

A minimal Java application is created that requests the Vogella RSS feed (http://vogella.com/article.rss) and prints out the channel title and the titles and the links of the articles.

5.1. Project creation and setup

This exercise assumes that you are familiar with Gradle and Buildship for Eclipse.

Create a new Gradle project with the name com.vogella.java.retrofitxml. Add a new package to src/main/java with the name com.vogella.java.retrofitxml.

Add the following dependencies to your build.gradle file.

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

5.2. Define the XML mapping

An RSS feed looks like the following:

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

Besides the XML header this file consists of various XML elements. The rss-element contains a channel-element which again contains other elements (i.e. title, description, pubDate) and several item-elements (the articles).

Create the following two data classes named RSSFeed and 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;
        }


}

The 'Article' class represents one single article and only stores the title and the link of it. These are the only fields we are interested in.

With the @Root annotation a class is marked to be (de)serialized. Optional, you can provide a name in the @Root annotation that represents the name of the XML element. If no name is provided, the class name is used as the XML element name. Because the class name (RSSFeed) is different to the XML element name (rss), you have to provide a name.

With strict set to false, strict parsing is disabled. This tells the parser to not fail and throw an exception, if a XML element or attribute is found for which no mapping is provided. As the rss-element has the version attribute which is not bound to a field, the application would crash, if it is not set to false.

With the help of the @Element annotation, a XML element is represented. Optional, you can provide a name for the XML element that is represented by that field. If no name is provided, the fields name is used.

The field articleList is annotated with @ElementList . That indicates, that this field is used to store a Collection (in our case: List<Article>) of XML elements with the same name. With inline set to true it is determined that the elements of the Collection are inlined. That means, that they have no enclosing element and are listed one after another within the XML response.

With the @Path annotation you can provide a path to an XML element inside the XML tree. This is helpful, if you don’t want to model the complete XML tree with Java objects. For the title of the channel and the several item-elements, we can directly point to the specific elements within the channel-element.

5.3. Define the API and the Retrofit adapter

Define the REST API for Retrofit via the following interface.

package com.vogella.java.retrofitxml;

import retrofit2.Call;
import retrofit2.http.GET;

public interface VogellaAPI {

        @GET("article.rss")
        Call<RSSFeed> loadRSSFeed();
}

Create the following controller class. This class creates the Retrofit client, calls the Vogella API and handles the result.

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

The last step is to create a class with a main-method to start the controller.

package com.vogella.java.retrofitxml;

public class Application {

        public static void main(String[] args) {
                Controller ctrl = new Controller();
                ctrl.start();
        }

}

6. Exercise: Using Retrofit to query Stackoverflow in Android

6.1. Target

StackOverflow is a popular side for asking programming orientated questions. It also provides a REST API which is well documented. Queries can be build using this API. You find it documented via the following URL: https://api.stackexchange.com/docs/search In this exercise you query for open questions based on tags with the Retrofit REST library.

We use the following query URL in our example. The tags will be set via our code.

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

6.2. Project creation and setup

Create an Android application called com.vogella.android.retrofitstackoverflow. It should use the com.vogella.android.retrofitstackoverflow top level package name.

Add the following dependency to your build.gradle file.

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

Add the permission to access the Internet to your manifest file.

6.3. Define the API and the Retrofit adapter

In the JSON reply from StackOverflow we are only interested in the title and the link. Therefore create the following data classes.

package com.vogella.android.retrofitstackoverflow;

// This is used to map the JSON keys to the object by GSON
public class Question {

    String title;
    String link;

    @Override
    public String toString() {
        return(title);
    }
}

Define the REST API for Retrofit via the following interface.

The list structure is needed as stackoverflow wraps its answer in an item object.

package com.vogella.android.retrofitstackoverflow;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface StackOverflowAPI {
    @GET("/2.2/questions?order=desc&sort=creation&site=stackoverflow")
    Call<List<Question>> loadQuestions(@Query("tagged") String tags);
}

Create a XML menu resource similar to the following called main_menu.xml and put it into the menu folder, which you should find in the res folder (create the folder if you could not find it).

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

Change your activity code to allow querying for questions marked with the tag "android".

package com.vogella.android.retrofitstackoverflow;

import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import java.util.ArrayList;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends ListActivity implements Callback<List<Question>> {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        requestWindowFeature(Window.FEATURE_PROGRESS);
        ArrayAdapter<Question> arrayAdapter =
                new ArrayAdapter<Question>(this,
                        android.R.layout.simple_list_item_1,
                        android.R.id.text1,
                        new ArrayList<Question>());
        setListAdapter(arrayAdapter);
        setProgressBarIndeterminateVisibility(true);
        setProgressBarVisibility(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        setProgressBarIndeterminateVisibility(true);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.stackexchange.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        // prepare call in Retrofit 2.0
        StackOverflowAPI stackOverflowAPI = retrofit.create(StackOverflowAPI.class);

        Call<List<Question>> call = stackOverflowAPI.loadQuestions("android");
        //asynchronous call
        call.enqueue(this);

        // synchronous call would be with execute, in this case you
        // would have to perform this outside the main thread
        // call.execute()

        // to cancel a running request
        // call.cancel();
        // calls can only be used once but you can easily clone them
        //Call<> c = call.clone();
        //c.enqueue(this);

        return true;
    }

    @Override
    public void onResponse(Call<List<Question>> call, Response<List<Question>> response) {
            setProgressBarIndeterminateVisibility(false);
        ArrayAdapter<Question> adapter = (ArrayAdapter<Question>) getListAdapter();
        adapter.clear();
        adapter.addAll(response.body().items);
    }

    @Override
    public void onFailure(Call<List<Question>> call, Throwable t) {
        Toast.makeText(MainActivity.this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
    }
}

7. Exercise: Using Retrofit to access Github API in Android

7.1. Project setup

To test Retrofit create an application called Retrofit Github. Use the com.vogella.android.retrofitgithub top level package name.

To use Retrofit in your application, add the following dependency to your build.gradle file

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

Add the permission to access the Internet to your manifest file.

<?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:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Add two buttons to your activity_main.xml layout file.

<?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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.vogella.android.retrofitgithub.MainActivity">

    <Button
        android:id="@+id/loadRepositories"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Repositories"
        android:gravity="center"
        android:layout_alignParentBottom="true"
        android:onClick="onClick"/>

    <Button
        android:id="@+id/loadUserData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load user data"
        android:gravity="center"
        android:layout_above="@id/loadRepositories"
        android:onClick="onClick"/>
</RelativeLayout>

Create the following interface and class for the Retrofit API.

package com.vogella.android.retrofitgithub;

// This is used to map the JSON keys to the object by GSON
public class GithubRepo {

    String name;
    String url;

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

// This is used to map the JSON keys to the object by GSON
public class GithubUser {

    String login;
    String name;
    String email;

    @Override
    public String toString() {
        return(login);
    }
}
package com.vogella.android.retrofitgithub;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;


public interface GithubAPI {
    String ENDPOINT = "https://api.github.com";

    @GET("/users/{user}")
    Call<GithubUser> getUser(@Path("user") String user);

    @GET("users/{user}/repos")
    Call<List<GithubRepo>> getRepos(@Path("user") String user);

}
package com.vogella.android.retrofitgithub;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends Activity implements Callback<GithubUser> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
    public void onClick(View view) {
        Gson gson = new GsonBuilder()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                .create();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(GithubAPI.ENDPOINT)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
        GithubAPI githubUserAPI = retrofit.create(GithubAPI.class);

        switch (view.getId()) {
            case R.id.loadUserData:
                // prepare call in Retrofit 2.0

                Call<GithubUser> callUser = githubUserAPI.getUser("vogella");
                //asynchronous call
                callUser.enqueue(this);
                break;
            case R.id.loadRepositories:
                Call<List<GithubRepo>> callRepos = githubUserAPI.getRepos("vogella");
                //asynchronous call
                callRepos.enqueue(repos);
                break;
        }
    }


    Callback repos = new Callback<List<GithubRepo>>(){

        @Override
        public void onResponse(Call<List<GithubRepo>> call, Response<List<GithubRepo>> response) {
            if (response.isSuccessful()) {
                List<GithubRepo> repos = response.body();
                StringBuilder builder = new StringBuilder();
                for (GithubRepo repo: repos) {
                    builder.append(repo.name + " " + repo.toString());
                }
                Toast.makeText(MainActivity.this, builder.toString(), Toast.LENGTH_SHORT).show();

            } else
            {
                Toast.makeText(MainActivity.this, "Error code " + response.code(), Toast.LENGTH_SHORT).show();
            }

        }

        @Override
        public void onFailure(Call<List<GithubRepo>> call, Throwable t) {
            Toast.makeText(MainActivity.this, "Did not work " +  t.getMessage(), Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    public void onResponse(Call<GithubUser> call, Response<GithubUser> response) {
        int code = response.code();
        if (code == 200) {
            GithubUser user = response.body();
            Toast.makeText(this, "Got the user: " + user.email, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, "Did not work: " + String.valueOf(code), Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void onFailure(Call<GithubUser> call, Throwable t) {
        Toast.makeText(this, "Nope", Toast.LENGTH_LONG).show();

    }
}

Access the following URL in your browser: https://api.github.com/users/vogella and build a data model and interface with the data you are interested in. Read this data via Retrofit and show it in a list.

8. Exercise: Using Retrofit to post comment on Github issue in Android

This exercise describes how to list all issues that are assigned to an user in an Android application using Retrofit. You can select an issue from a drop down field and post a comment on it. Make sure, that you have a Github account, as this tutorial doesn’t work if you don’t have one.

8.1. Project setup

Create an Android application with the name Retrofit Github Comment. Use com.vogella.android.retrofitgithubcomment as the top level package name.

To use Retrofit, add the following lines to your build.gradle file

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

Add the permission to access the Internet to your manifest file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vogella.android.retrofitgithubcomment">

    <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.2. Define the API

Create the following data class called GithubIssue.

package com.vogella.android.retrofitgithubcomment;

import com.google.gson.annotations.SerializedName;

public class GithubIssue {

    String id;
    String title;
    String comments_url;

    @SerializedName("body")
    String comment;

    public String getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getCommentsUrl() {
        return comments_url;
    }

    public void setCommentsUrl(String commentsUrl) {
        this.comments_url = commentsUrl;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }


    @Override
    public String toString() {
        return id +  " - " + title;
    }
}

We only show the id and the title of the issue in the drop down field, so we create a field for each of them. Furthermore, the response from Github contains the URL we have to post the comment to, which we store in the field comments_url. To later post a new comment to the Github API, we add a field called comment. The Github API specifies that the contents of a comment has to be bound to a field named body in the JSON request. As Retrofit (de)serializes all fields based on their name and as we don’t want to use body as the field name in our GithubIssue class, we use the @SerializedName annotation. With the help of this annotation, we can change the name a field is (de)serialized to in the JSON.

Define the REST API for Retrofit via the following interface:

package com.vogella.android.retrofitgithubcomment;

import java.util.List;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Url;


public interface GithubAPI {
    String ENDPOINT = "https://api.github.com";

    @GET("/issues")
    Call<List<GithubIssue>> getIssues();

    @POST
    Call<ResponseBody> postComment(@Url String url, @Body GithubIssue issue);
}

You might wonder about the @Url annotation. With the help of this annotation, we can provide the URL for this request. This allows us to change the URL for each request dynamically. We need this for the comments_url field of the GithubIssue class.

8.3. Create Activity

Add two Buttons (to load the issues and to send the comment), a Spinner (the drop down field to list the issues) and an EditText (to provide your comment) to your activity_main.xml layout file.

<?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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.vogella.android.retrofitgithubcomment.MainActivity">

    <Spinner
        android:id="@+id/issues_spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/comment_edittext"
        android:layout_below="@id/issues_spinner"
        android:enabled="false"
        android:maxLines="1"
        android:inputType="text"
        android:imeOptions="actionDone"
        android:hint="Your comment" />

    <Button
        android:id="@+id/loadIssues_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:onClick="onClick"
        android:text="Load issues" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send comment"
        android:layout_above="@id/loadIssues_button"
        android:onClick="onClick"
        android:enabled="false"
        android:id="@+id/send_comment_button"/>
</RelativeLayout>

Change your activity code to allow querying for issues assigned to an user and to post a comment on an issue. Make sure, that you replace aUsername and aPassword with your Github login credentials.

package com.vogella.android.retrofitgithubcomment;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
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 okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends AppCompatActivity {

    GithubAPI githubAPI;
    String credentials = Credentials.basic("aUsername", "aPassword");

    Spinner issuesSpinner;
    EditText commentEditText;
    Button sendButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendButton = (Button) findViewById(R.id.send_comment_button);

        issuesSpinner = (Spinner) findViewById(R.id.issues_spinner);
        issuesSpinner.setEnabled(false);

        commentEditText = (EditText) findViewById(R.id.comment_edittext);

        createGithubAPI();
    }

    private void createGithubAPI() {
        Gson gson = new GsonBuilder()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                .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);

                        Request newRequest = builder.build();
                        return chain.proceed(newRequest);
                    }
                }).build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(GithubAPI.ENDPOINT)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        githubAPI = retrofit.create(GithubAPI.class);
    }

    public void onClick(View view) {


        switch (view.getId()) {
            case R.id.loadIssues_button:
                Call<List<GithubIssue>> callIssues = githubAPI.getIssues();
                callIssues.enqueue(issues);
                break;
            case R.id.send_comment_button:
                String newComment = commentEditText.getText().toString();
                if (!newComment.isEmpty()) {
                    GithubIssue selectedItem = (GithubIssue) issuesSpinner.getSelectedItem();
                    selectedItem.setComment(newComment);
                    Call<ResponseBody> postComment = githubAPI.postComment(selectedItem.getCommentsUrl(), selectedItem);
                    postComment.enqueue(comment);
                } else {
                    Toast.makeText(MainActivity.this, "Please enter a comment", Toast.LENGTH_LONG).show();
                }
                break;
        }
    }

    Callback<List<GithubIssue>> issues = new Callback<List<GithubIssue>>() {
        @Override
        public void onResponse(Call<List<GithubIssue>> call, Response<List<GithubIssue>> response) {
            if (response.isSuccessful()) {
                List<GithubIssue> issuesList = response.body();
                GithubIssue[] idArray = issuesList.toArray(new GithubIssue[issuesList.size()]);
                ArrayAdapter<GithubIssue> spinnerAdapter = new ArrayAdapter<GithubIssue>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, idArray);
                issuesSpinner.setAdapter(spinnerAdapter);

                issuesSpinner.setEnabled(true);
                commentEditText.setEnabled(true);
                sendButton.setEnabled(true);
            } else {
                Log.e("onResponse failure", "Code: " + response.code() + " , Message: " + response.message());
            }
        }

        @Override
        public void onFailure(Call<List<GithubIssue>> call, Throwable t) {
            t.printStackTrace();
        }
    };

    Callback<ResponseBody> comment = new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                commentEditText.setText("");
                Toast.makeText(MainActivity.this, "Comment created", Toast.LENGTH_LONG).show();
            } else {
                Log.e("onResponse failure", "Code: " + response.code() + " , Message: " + response.message());
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            t.printStackTrace();
        }
    };
}

9. Exercise: Using Retrofit with OAuth to request user details from Twitter in Android

This exercise describes how to login into Twitter using Retrofit on Android. We write an application that can request and display user details for a provided user name. In this exercise, we use Twitters application-only authentication with OAuth 2 for authorization. To make this tutorial working, you need to have a Twitter account. Furthermore, you need to head over to Twitter Apps and create a new app to get your consumer key and the corresponding secret. We need this later to request our OAuth token.

9.1. Project setup

Create an Android application with the name Retrofit Twitter. Use _com.vogella.android.retrofittwitter as the top level package name.

To use Retrofit, add the following lines to your build.gradle file

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

Add the permission to access the Internet to your manifest file.

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

9.2. Define the API

Create the following two data clases called OAuthToken and 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;
    }
}

The OAuthToken class is used to store the bearer token we request from Twitter with our consumer key and secret. We use the @SerializedName annotation to set the name Retrofit (de)serializes the fields with.

The UserDetails class simply stores a few fields from the response from Twitter when requesting the user details. We don’t show all user details the response contains, but the name, location, url and description.

Define the REST API for Retrofit via the following 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);
}

9.3. Create Activity

Modify your activity_main.xml layout file and the corresponding MainActivity class like the following:

<?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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    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();
        }
    };
}

Make sure, that you replace aConsumerKey and aSecret with the consumer key and secret provided by Twitter.

Also have a look at the interceptor we add to our Retrofit client. As we are using OAuth, our credentials are different for each call. The postCredentials method needs to post credentials in the Basic scheme to Twitter, which are composed from our consumer key and the secret. As a result, this call returns the bearer token Retrofit deserializes into our OAuthToken class, which we then store in the token field. Any further request can (and have to) now use this token as its credentials for authorization. So does the request to get the user details.

10. About this website

11. Retrofit resources

11.1. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT

The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”.

The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.