Using Retrofit in Android

Using Retrofit in Android

Using Retrofit is quite simple and straightforward.

First of all you need to add retrofit to your project, as example with Gradle build sytem.

compile 'com.squareup.retrofit:retrofit:1.7.1' |

another way you can download .jar and place it to your libs folder.

Then you need to define interfaces that will be used by Retrofit to make API calls to your REST endpoints. For example for users:

public interface YourUsersApi {

//You can use rx.java for sophisticated composition of requests
@GET("/users/{user}")
public Observable<SomeUserModel> fetchUser(@Path("user") String user);

//or you can just get your model if you use json api
@GET("/users/{user}")
public SomeUserModel fetchUser(@Path("user") String user);

//or if there are some special cases you can process your response manually
@GET("/users/{user}")
public Response fetchUser(@Path("user") String user);

}

Ok. Now you have defined your API interface an you can try to use it.

To start you need to create an instance of RestAdapter and set base url of your API back-end. It's also quite simple:

RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://yourserveraddress.com")
.build();

YourUsersApi yourUsersApi = restAdapter.create(YourUsersApi.class);

Here Retrofit will read your information from interface and under the hood it will create RestHandler according to meta-info your provided which actually will perform HTTP requests.

Then under the hood, once response is received, in case of json api your data will be transformed to your model using Gson library so you should be aware of that fact that limitations that are present in Gson are actually there in Retrofit.

To extend/override process of serialisers/deserialisation your response data to your models you might want to provide your custom serialisers/deserialisers to retrofit.

Here you need to implement Converter interface and implement 2 methods fromBody() and toBody().

Here is example:

public class SomeCustomRetrofitConverter implements Converter {

private GsonBuilder gb;

public SomeCustomRetrofitConverter() {
gb = new GsonBuilder();

//register your cursom custom type serialisers/deserialisers if needed
gb.registerTypeAdapter(SomeCutsomType.class, new SomeCutsomTypeDeserializer());
}

public static final String ENCODING = "UTF-8";

@Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
String charset = "UTF-8";
if (body.mimeType() != null) {
charset = MimeUtil.parseCharset(body.mimeType());
}
InputStreamReader isr = null;
try {
isr = new InputStreamReader(body.in(), charset);
Gson gson = gb.create();
return gson.fromJson(isr, type);
} catch (IOException e) {
throw new ConversionException(e);
} catch (JsonParseException e) {
throw new ConversionException(e);
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException ignored) {
}
}
}
}

@Override
public TypedOutput toBody(Object object) {
try {
Gson gson = gb.create();
return new JsonTypedOutput(gson.toJson(object).getBytes(ENCODING), ENCODING);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}

private static class JsonTypedOutput implements TypedOutput {
private final byte[] jsonBytes;
private final String mimeType;

JsonTypedOutput(byte[] jsonBytes, String encode) {
this.jsonBytes = jsonBytes;
this.mimeType = "application/json; charset=" + encode;
}

@Override
public String fileName() {
return null;
}

@Override
public String mimeType() {
return mimeType;
}

@Override
public long length() {
return jsonBytes.length;
}

@Override
public void writeTo(OutputStream out) throws IOException {
out.write(jsonBytes);
}
}
}

And now you need to enable your custom adapters, if it was needed by using setConverter() on building RestAdapter

Ok. Now you are aware how you can get your data from server to your Android application. But you need somehow mange your data and invoke REST call in right place.
There I would suggest to use android Service or AsyncTask or loader or rx.java that would query your data on background thread in order to not block your UI.

So now you can find the most appropriate place to call

SomeUserModel yourUser = yourUsersApi.fetchUser("someUsers")

to fetch your remote data.

Using Retrofit 2.0 in Android

  1. 'Repo' in this example means a repository on Github.
    Retrofit automatically deserializes JSON responses to Java POJOs. The example fetches information about Github repositories from the Github API, the fetched repository information is represented by a Repo class / a List of Repo objects. You'll have to provide your own class representation of the data that you're getting from your server / API.
  2. Does it mean a subdirectory under the server URL?

Basically, yeah. It's the path / Uri to the resource you're trying to access on the server specified with baseUrl.

In this case we have the baseUrl https://api.github.com. We append the path /users/{user}/repos. The method

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

takes a user id as an argument and replaces {user} with that argument.

So if you call that method with the argument JakeWharton the full Uri is

https://api.github.com/users/JakeWharton/repos

You can call it from your browser to see the response.
You'll have to change those Url/Uri Strings to match the API you want to access.

.

How to POST and GET that data from a web API. (Retrofit/Kotlin/Android)

Edit: As @extremeoats wrote in comments, the api doesn't support passing urls an image indentifier and you have to save its id also for the operations like making favorite etc.

Old answer

Could you please add some code of how a request is made?
It's a local error of Moshi trying to parse the response and not seing the required field (maybe got an error from server - the data structure of an error would be different from a normal response)

I've built a sample app to test this and get a proper response when marking the image as a fav. You are right to use the structure with an image_id and sub_id. Here are some code parts if it helps

  1. Set up the Retrofit (interceptor for debug only, so you can see what exactly you sent and got back)
        private val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val retrofit = Retrofit.Builder()
.baseUrl(ServerApi.BASE_URL)
.client(
OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
)
.addConverterFactory(MoshiConverterFactory.create())
.build()

private val api = retrofit.create(ServerApi::class.java)

1.1. A dependency for the logging interceptor and OkHttp

    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'

  1. Api interface

     interface ServerApi {
    companion object {
    const val API_KEY: String = **PASTE YOUR API KEY HERE**
    const val AUTH_HEADER = "x-api-key"
    const val BASE_URL = "https://api.thecatapi.com/v1/"
    }

    @GET("images/search")
    fun getImages(@Header(AUTH_HEADER) authHeader: String = API_KEY, @Query("limit") limit: Int = 5): Call<List<ImagesItem>>

    @POST("favourites")
    fun postFavourite(@Header(AUTH_HEADER) authHeader: String = API_KEY, @Body payload: PostFavouritePayload): Call<String>
    }

    private var listMyData = Types.newParameterizedType(List::class.java, ImagesItem::class.java)
    private val adapter: JsonAdapter<List<ImagesItem>> = Moshi.Builder().build().adapter(listMyData)
  2. Use the api

     api.getImages().enqueue(object : Callback<List<ImagesItem>> {
    override fun onResponse(call: Call<List<ImagesItem>>, response: Response<List<ImagesItem>>) {
    val images = response.body() ?: return
    api.postFavourite(payload = PostFavouritePayload(images[0].id, **PASTE ANY STRING HERE AS USER ID**))
    .enqueue(object : Callback<String> {
    override fun onResponse(call: Call<String>, response: Response<String>) =
    Log.d("TestCatApi", "SUCCESS posting a fav: ${response.body()}")
    override fun onFailure(call: Call<String>, t: Throwable) =
    t.printStackTrace()
    })
    }

    override fun onFailure(call: Call<List<ImagesItem>>, t: Throwable) =
    t.printStackTrace()
    })

Side notes:

  1. For such example APIs I find very useful a plugin "Json to Kotlin class". Alt+K, then you can paste the String response from a Server or in example, and you have a decent starting point for the data classes (not affiliated with them)

  2. Just in case: you can pass a baseUrl to the Retrofit builder like that

     Retrofit.Builder()
    .baseUrl(ServerApi.BASE_URL)...

Then you put in @Get, @Post etc. only the part after the "/" of a base url: "images/search"

Synchronous API call using retrofit execute

The problem with your sample code is that inside your function check(p1, p2) you start a new Thread. This means your Thread which started the function will continue and return true. You must add a way to wait for the execution of the call and then continue your process with the result.

There are multiple ways to achieve this:

  1. with Callback
private fun check(v1: String, v2: String, onResult: (Boolean) -> Unit) {
val vObj = ValidateObj( v1, v2 )
Thread {
val result = runCatching {
val retrofit = ServiceBuilder.buildService(API::class.java)
val res = retrofit.validate(vObj).execute()
res.code() == 200
}.getOrDefault(false)

onResult(result) // result will then be on the other thread and you have to switch back if necessary
}.start()
}

//with Coroutine
private fun check(v1: String, v2: String, onResult: (Boolean) -> Unit) {
val vObj = ValidateObj( v1, v2 )
CoroutineScope(Dispatchers.IO).launch {
val result = runCatching {
val retrofit = ServiceBuilder.buildService(API::class.java)
val res = retrofit.validate(vObj).execute()
res.code() == 200
}.getOrDefault(false)

onResult(result) // result will then be on the other thread and you have to switch back if necessary

// e.g. CoroutineScope(Dispatchers.Main).launch{onResult(result)}

}
}

fun logic(){
check(p1, p2){ boolean ->
// continue your logic
}
}

  1. with Coroutines
private suspend fun check(v1: String, v2: String): Boolean = 
withContext(Dispatchers.IO){
val vObj = ValidateObj( v1, v2 )

runCatching {
val retrofit = ServiceBuilder.buildService(API::class.java)
val res = retrofit.validate(vObj).execute()
res.code() == 200
}.getOrDefault(false)
}

fun logic{

//use function inside a coroutine, e.g.
CoroutineScope(Dispatchers.Main).launch{
val checkResult = check(p1, p2)

...
}

}



Related Topics



Leave a reply



Submit