Debugování Retrofit požadavků

O tom, jak zprovoznit Retrofit jsme si již psali. Dnes se podíváme na to, jak získat více informací o tom, jak Retrofit funguje a jaké posílá a přijímá požadavky.

Začneme nainstalováním závislosti: com.squareup.okhttp3:logging-interceptor

implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();

Kde setLevel určuje úroveň logování: NONE, BASIC, HEADERS, BODY

Tím máme základní logování zprovozněno:

10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> POST https://vencax.local/mwm/api/login
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Type: application/json; charset=UTF-8
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Length: 107
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Authorization: Basic xxx
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: {"actualDateTime":"2018-10-11T12:46:10.2580000+02:00","phoneId":"2c3c2a5fe5a7b529","versionNumber":"1.9.0"}
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> END POST (107-byte body)

Pokud chcete logovat svým vlastním způsobem, můžete přetížit konstruktor:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new 
Logger() {
@Override public void log(String message) {
    Log.d(TAG, "message: ");
    }
});

 

Retrofit: synchronní a asynchronní požadavky

Minule jsme si ukázali, jak použít Retrofit v Android studiu. Dnes se podíváme na synchronní a asynchronní požadavky.

Synchronní požadavek

Při synchronním požadavku, je na server odeslán požadavek, aplikace se „zastaví“ a čeká, dokud neobdrží odpověď od serveru.

Asynchronní požadavek

Při asynchronním požadavku se používají tzv. callbacky. Na server se odešle požadavek (stejně jako při synchronním požadavku) ale nečeká se na odpověď a kód pokračuje dál. Na odpověď ze serveru čeká callback, který provede požadovanou operaci s odpovědí od serveru. Tento callback se může zavolat kdykoliv (odpověď od serveru trvá různě dlouho).

V kódu je vidět asynchronní volání, které je shodné jako v prvním seznámení s Retrofit, přibylo synchronní volání:

package cz.vencax.retrofit;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

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

        final Button button = findViewById(R.id.buttonAsynchronous);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                getBooksFromApiAsynchronous();
            }
        });

        final Button buttonPost = findViewById(R.id.buttonSynchronous);
        buttonPost.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    getBooksFromApiSynchronous();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Asynchronous Retrofit request
     */
    private void getBooksFromApiAsynchronous() {

        Log.d("xxx", "getBooksFromApiAsynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> books = bookService.getAllBooks();
        books.enqueue(new Callback<Books>() {

            @Override
            public void onResponse(Call<Books> call, Response<Books> response) {
                Log.d("xxx", "Asynchronous onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "Asynchronous response.isSuccessful()" + response.body());
                    Books books = response.body();

                    Log.d("xxx", "Asynchronous response.isSuccessful(), books count: " + books.getBooks().size());
                    for (Book book : books.getBooks()) {
                        Log.d("xxx", "Asynchronous Book: " + book.getTitle() + ", " + book.getAuthor());
                    }
                } else {
                    Log.d("xxx", "Asynchronous response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Books> call, Throwable t) {
                Log.d("xxx", "Asynchronous onFailure: " + t);
            }
        });
    }

    /**
     * Synchronous Retrofit request
     */
    private void getBooksFromApiSynchronous() throws IOException {

        Log.d("xxx", "getBooksFromApiSynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> call = bookService.getAllBooks();
        Books books = call.execute().body();

        Log.d("xxx", "Synchronous response.isSuccessful(), books count: " + books.getBooks().size());
        for (Book book : books.getBooks()) {
            Log.d("xxx", "Synchronous Book: " + book.getTitle() + ", " + book.getAuthor());
        }
    }
}

 

 

Android: Retrofit

Před několika lety jsem komunikoval se serverem přes balíček Volley. Protože komunikuji hlavně přes REST rozhraní, nahradil jsem Volley balíčkem Retrofit, který je pro tyto účely doporučovaný.

Retrofit je HTTP klient pro Android (Javu), který vám usnadňuje připojení k webové službě REST API. Usnadňuje konzumaci dat JSON a XML. Podporuje příkazy: GET, POST, PUT, PATCH a DELETE. Pojďme si ukázat jak na to.

Nainstalujeme závislosti:

// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'

// JSON Parsing
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

+ internet permission

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.chikeandroid.retrofittutorial">
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application>
       ....
    </application>
 
</manifest>

Retrofit je knihovna, která používá anotované rozhraní pro volání REST API. Vytvoříme jednoduchý požadavek GET pro seznam knih, který se automaticky namapuje na objekty. API pro seznam knih vrací tento JSON:

{  
    "books":[  
        {  
            "author":"Božena Němcová",
            "title":"Babička"
        },
        {  
            "author":"Božena Němcová",
            "title":"V zámku a v podzámčí"
        },
        {  
            "author":"Karel Čapek",
            "title":"Krakatit"
        },
        {  
            "author":"Karel Čapek",
            "title":"Jak se co dělá"
        }
   ]
}

jsonschema2pojo

Z JSONu necháme vygenerovat objekty přes službu jsonschema2pojo. Vložíme JSON, vybereme Source type: JSON, Annotation style: Gson a odškrtneme Allow additional properties.

Rozhraní služby

Modelovou třídu máme vytvořenou, vytvoříme rozhraní služby, které bude spravovat API endpointy. Vytvoříme GET požadavek pro načtení všech knih:

package cz.vencax.retrofit;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;

public interface BookService {
    @GET("books")
    Call<Books> getAllBooks();
}

Všimněte si anotace @GET označující typ požadavku a nabývající hodnotu books, která označuje endpoint (tato hodnota se přidá k BASE_URL popsané níže).

RetrofitClient

požadavky na REST API budeme odesílat přes třídu RetrofitClient:

package cz.vencax.retrofit;

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

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    static final String BASE_URL = "http://demo7646366.mockable.io/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit==null) {

            Gson gson = new GsonBuilder()
                    .setLenient()
                    .create();

            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit;
    }
}

Kde BASE_URL nabývá hodnoty se základním URL API, ke kterému se přidávají hodnoty z rozhraní služby popsané výše.

Získání dat v activity

V activity poté data jednoduše získáme a můžeme s nimi dále pracovat. U pozorňuji, že data jsou získána asynchronně:

    private void getBooksFromApi() {

        Log.d("xxx", "getBooksFromApi");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> books = bookService.getAllBooks();
        books.enqueue(new Callback<Books>() {

            @Override
            public void onResponse(Call<Books> call, Response<Books> response) {
                Log.d("xxx", "onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "response.isSuccessful()" + response.body());
                    Books books = response.body();

                    Log.d("xxx", "response.isSuccessful(), books count: " + books.getBooks().size());
                    for (Book book : books.getBooks()) {
                        Log.d("xxx", "Book: " + book.getTitle() + ", " + book.getAuthor());
                    }
                } else {
                    Log.d("xxx", "response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Books> call, Throwable t) {
                Log.d("xxx", "onFailure: " + t);
            }
        });
    }

Touto jednoduchou ukázkou jsem vám představil jak na to. Jednoduché, že?

Odeslání dat na server – POST

Pokud však potřebujete posílat nějaká data, je nutné je ukládat přes metody PUT nebo POST. Pojďme se podívat jak na to s metodou POST (PUT by byla analogická).

V rozhraní služby vytvoříme metodu POST s endpointem a parametrem @body:

package cz.vencax.retrofit;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;

public interface BookService {
    @POST("send")
    Call<Book> postBook(@Body Book book);
}

Na endpoint BASE_URL/send odešleme objekt Book – tento objekt se automaticky převede na JSON.

Odeslání POST požadavku z activity:

    private void postBookToApi() {

        Log.d("xxx", "postBookToApi");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);

        Book bookPost = new Book();
        bookPost.setAuthor("vEnCa-X");
        bookPost.setTitle("Jak na Android - Retrofit");

        Call<Book> book = bookService.postBook(bookPost);
        book.enqueue(new Callback<Book>() {

            @Override
            public void onResponse(Call<Book> call, Response<Book> response) {
                Log.d("xxx", "onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "response.isSuccessful(): " + response.body());
                    Book book = response.body();
                    Log.d("xxx", "Book: " + book.getTitle() + ", " + book.getAuthor());
                } else {
                    Log.d("xxx", "response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Book> call, Throwable t) {
                Log.d("xxx", "onFailure: " + t);
            }
        });
    }

Další způsob bez call

Response<Book> book = bookService.postBook(bookPost).execute();
if (book.isSuccessful()) {
   Log.d("xxx", "book reponse: " + book);
}

Problémy

  • máš aktuální verze balíčků?