I'm trying to build a library that basically wraps our api. Basically, the structure im going for is something like this:
MySDK mySDK = new MySDK("username", "password");
mySDK.getPlaylistInfo("3423", 2323, new CustomCallback<>(){
//on response
//on failure
});
So with vanilla Retrofit, an api call usually looks something like the following:
ApiService api = retrofit.create(ApiService.class);
Call<Response> call = api.getPlaylistInfo()
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, Response<Response> response) {
//handle response
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
//handle failure
}
});
Basically, how would I wrap retrofits callback system into my own? Note, the reason for needing to do this is to preprocess the data returned from the api before delivering the final response.
I've written something similar so it might help you getting started, this follows an implementation I'v written for Volley, and re-used when I migrated to Retrofit2 so it resembles it (this SO question).
Create a global object (what you would refer to as MySDK) as a singelton class that handles your requests:
create a singleton class, which you instatiate when you're application comes up:
public class NetworkManager
{
private static final String TAG = "NetworkManager";
private static NetworkManager instance = null;
private static final String prefixURL = "http://some/url/prefix/";
//for Retrofit API
private Retrofit retrofit;
private ServicesApi serviceCaller;
private NetworkManager(Context context)
{
retrofit = new Retrofit.Builder().baseUrl(prefixURL).build();
serviceCaller = retrofit.create(ServicesApi.class);
//other stuf if you need
}
public static synchronized NetworkManager getInstance(Context context)
{
if (null == instance)
instance = new NetworkManager(context);
return instance;
}
//this is so you don't need to pass context each time
public static synchronized NetworkManager getInstance()
{
if (null == instance)
{
throw new IllegalStateException(NetworkManager.class.getSimpleName() +
" is not initialized, call getInstance(...) first");
}
return instance;
}
public void somePostRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
String url = prefixURL + "this/request/suffix";
Map<String, Object> jsonParams = new HashMap<>();
jsonParams.put("param1", param1);
Call<ResponseBody> response;
RequestBody body;
body = RequestBody.create(okhttp3.MediaType.parse(JSON_UTF), (new JSONObject(jsonParams)).toString());
response = serviceCaller.thePostMethodYouWant("someUrlSufix", body);
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
public void someGetRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
// you need it all to be strings, lets say id is an int and name is a string
Call<ResponseBody> response = serviceCaller.theGetMethodYouWant
(String.valueOf(param1.getUserId()), param1.getUserName());
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
}
This works with your interface (example with POST and GET request, GET could be without params):
public interface BelongServicesApi
{
#POST("rest/of/suffix/{lastpart}") // with dynamic suffix example
Call<ResponseBody> thePostMethodYouWant(#Path("lastpart") String suffix, #Body RequestBody params);
#GET("rest/of/suffix") // with a fixed suffix example
Call<ResponseBody> theGetMethodYouWant(#Query("userid") String userid, #Query("username") String username);
}
when your application comes up:
public class MyApplication extends Application
{
//...
#Override
public void onCreate()
{
super.onCreate();
NetworkManager.getInstance(this);
}
//...
}
a simple listener interface for your callback (seperate file would do good):
public interface SomeCustomListener<T>
{
public void getResult(T object);
}
and finally, from wherever you want, the context is already in there, just call:
public class BlaBla
{
//.....
public void someMethod()
{
//use the POST or GET
NetworkManager.getInstance().somePostRequestReturningString(someObject, new SomeCustomListener<String>()
{
#Override
public void getResult(String result)
{
if (!result.isEmpty())
{
//do what you need with the result...
}
}
});
}
}
you can use any object with the listener, just parse the response string to a corresponding object, depending on what you need to receive and you can call that from everywhere (onClicks, etc.), just remember the objects need to match between methods.
Hope this Helps!
Related
In my first android project i made an onClick event to call a function:
public void doSomething(View v) {
String result = authenticate();
[...]
}
This function calls the method:
private String authenticate() {
OkHttpClient client = new OkHttpClient();
[...]
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
[...]
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseString = response.body().string();
try {
JSONObject responseObject = new JSONObject(responseString);
String responseObjectAccessToken = responseObject.getString("accesstoken");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
});
}
now i would like to return the responseObjectAccessToken to my doSomething function. A detailed explanation would be great since I am new to Java and Android Studio.
One suggestion is to handle that async response through a callback which is passed as an argument to your authenticate method. Here I'm using Java 8 lambda for that one method interface.
public class MyActivity extends Activity {
public interface AuthCallback {
void onAuthResult(String token);
}
private String authenticate(AuthCallback callback) {
// ...
String responseObjectAccessToken = responseObject.getString("accesstoken");
callback.onAuthResult(responseObjectAccessToken)
// ...
}
public void doSomething(View v) {
authenticate((token) -> {
// do something with token
});
}
// ...
}
As you can see that callback could be stored if you wanted to:
// ...
AuthCallback cb = new AuthCallback() {
#Override
public void onAuthResult(String token) {
// do something in the view/fragment/activity
}
}
// then pass it as argument
or your class could implement this interface and pass itself into the method:
public class MyActivity extends Activity implements AuthCallback {
#Override
public void onAuthResult(String token) {
// do something
}
// ...
public void doSomething(View v) {
authenticate(MyActivity.this); // <-- pass itself
}
}
One important point here is that networking happens on a separate thread, so if you want to have some UI changes after your API responds you could use a helper method runOnUiThread to do changes on main ui thread instead:
authenticate((token) -> {
runOnUiThread(() -> {
// do something with UI here
})
});
From the server, sometimes we are getting a response that is null or empty. Because of this, our code will crash at some null pointer exception. We don't want to have null checks everywhere in code. Is there a way to specify default values when a retrofit response is null or empty? In the code below, this is how we can handle it on a case-by-case basis. But, we do not want to write this logic for every object. We want to do this somehow at the application level. Is that possible?
#SerializedName("isPdf")
#Expose
private String isPdf;
public boolean getIsPdf() {
return !Util.isNullOrEmpty(isPdf) && isPdf.equalsIgnoreCase("true") ? true : false;
}
public void setIsPDF(boolean isPdf) {
this.isPdf = isPdf ? "true" : "false";
}
You can create a default callback that handles a null response the way you want. For example, calling onFailure:
public class DefaultCallback<T> implements Callback<T> {
private static final String TAG = "YOUR_TAG";
private Callback<T> callback;
public DefaultCallback(Callback<T> callback) {
this.callback = callback;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.body() == null) {
callback.onFailure(call, new NullPointerException("Empty response"));
} else {
callback.onResponse(call, response);
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
Log.e(TAG, t.toString());
callback.onFailure(call, t);
}
}
And then use this callback in your implementation.
Call<MyObject> call = ... //Create your call
call.enqueue(new DefaultCallback<>(new Callback<MyObject>() {
#Override
public void onResponse(Call<MyObject> call, Response<MyObject> response) {
//Handle successful non-null responses
}
#Override
public void onFailure(Call<MyObject> call, Throwable t) {
//Handle errors/null responses
}
}));
I have a general sendRequest() method that I use to get a json result back from the server. I'm parsing the result in an instance of the callback right now, but instead I'd much rather just pass in a type to the sendRequest() method so I can parse and handle exceptions before the callback. How can I do this? I've included the relevant code and some comments below.
public void sendRequest(int method, String url, final RequestListener listener) {
StringRequest postRequest = new StringRequest(method, url,
new Response.Listener<String>()
{
#Override
public void onResponse(String response) {
listener.gotResponse(response, null);
// Instead of parsing this into a user object, I'd like to parse it into
// a type that I pass into the sendRequest() method.
try {
User user = User.parseJson(new JSONObject(response));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
First of all please note that out of the box you have JsonObjectRequest which will convert the response directly into a JSONObject which seems to be better than StringRequest in your case, but as you want to go a little bit further I would propose to create a custom request based on a generic unmarshaller.
public interface JsonUnmarshaller<T> {
T parse(JSONObject object) throws JSONException;
}
The custom request could be something like this:
public class JSONRequest extends Request<T> {
private final Listener<T> mListener;
private final JsonUnmarshaller<T> unmarshaller;
public JSONRequest(int method, String url, Listener<T> listener,
ErrorListener errorListener, JsonUnmarshaller<T> unmarshaller) {
super(method, url, errorListener);
mListener = listener;
this.unmarshaller = unmarshaller;
}
#Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(
response.data, HttpHeaderParser.parseCharset(response.headers)
);
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
try {
return Response.success(
unmarshaller.parse(new JSONObject(parsed)),
HttpHeaderParser.parseCacheHeaders(response)
);
} catch (JSONException je) {
return Response.error(new ParseError(je));
}
}
}
Then your method would be:
public <T> void sendRequest(int method, String url, final RequestListener
listener, JsonUnmarshaller<T> unmarshaller) {
JSONRequest postRequest = new JSONRequest(...
Wrote a little something for you here, I think you will be able to apply this to your example:
import java.util.*;
public class Cat{
public static void main(String... args){
Cat cat = new Cat();
cat.send(Dog.class);
cat.send(Mouse.class);
}
public <T extends Parseable> void send(Class<T> clazz){
try{
T t = clazz.newInstance();
t.parse();
} catch(Exception e){
}
}
}
interface Parseable{
void parse();
}
class Dog implements Parseable{
public void parse(){
System.out.println("Dog parse");
}
}
class Mouse implements Parseable{
public void parse(){
System.out.println("Mouse parse");
}
}
Your method is static so you probably can just do T.parseJson(). Not sure if there won't be any problems with transfering generic type to anonymous listener, but I hope it will work.
I'm new to retrofit and i am trying te get a json response to an object called RootObject. The error that i am stuck with is :
"Error:(21, 44) error: incompatible types: NewsController cannot be
converted to Callback>"
Does someone now my mistake here? thanks in regards!
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
try {
service.GetNewsItems().enqueue(this); //asynchronous
Response<List<RootObject>> response = service.GetNewsItems().execute(); //synchronous
}
catch (IOException e){
}
}
}
class to put the data:
public class RootObject implements Serializable {
public ArrayList<Result> results ;
public int nextId;
public ArrayList<Result> getResults() { return results; }
public int getNextId() { return nextId; }
public String toString() {
return String.format("JEEJ" + nextId);
}
}
Interface:
public interface GetNewsService {
#GET("/Articles")
Call<List<RootObject>> GetNewsItems();
}
First of all,
change your interface to this:
public interface GetNewsService {
#GET("/Articles")
void GetNewsItems(Callback<List<RootObject>> cb);
}
Also change your newsController class.
public class NewsController {
private RestAdapter restAdapter;
static final String API_URL = "[Enter your API base url here]";
public void getNews(){
OkHttpClient mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15000,TimeUnit.MILLISECONDS);
mOkHttpClient.setReadTimeout(15000,TimeUnit.MILLISECONDS);
restAdapter = new RestAdapter.Builder().setEndpoint(API_URL).setClient(new OkClient(mOkHttpClient)).setLogLevel(RestAdapter.LogLevel.FULL) .build();
GetNewsService service = restAdapter.create(GetNewsService.class);
Callback<List<RootObject> cb = new Callback<List<RootObject>>() {
#Override
public void success(List<RootObject> rootObjectList, Response response) {
//whatever you want to do with the fetched news items
}
#Override
public void failure(RetrofitError error) {
//whatever you want to do with the error
}
};
service.GetNewsItems(cb);
}
}
You'll need to add the following dependencies in your build.gradle:
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
#megh vidani's answer works, but he had you switch your code from Retrofit 2 to Retrofit 1. Here is how to do it in Retrofit 2. You would need to go back to your original gradle settings, etc. --
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
service.GetNewsItems().enqueue(new Callback<List<RootObject>>() {
#Override
public void onResponse(Response<List<RootObject>> response) {
// Handle your response
// Note HTTP errors are delivered here, you can check
// response.isSuccess() or response.code() to determine
// HTTP failures
}
#Override
public void onFailure(Throwable t) {
// Network errors
}
});
}
}
This is my post :
#POST("/path")
#FormUrlEncoded
void postIt(#Field("id") String id , Callback<Response> response);
and this is the Callback:
private Callback<Response> responseCallBack = new Callback<Response>() {
#Override
public void success(Response response, Response response2) {
// get the id
}
#Override
public void failure(RetrofitError error) {
// do some thing
}
};
Question:
in the callback i want to receive the id which posted in #POST, how should i do that?
and i can't change the server API
to do this we need an abstract class
abstract class CallBackWithArgument<T> implements Callback<T> {
String arg;
CallBackWithArgument(String arg) {
this.arg = arg;
}
CallBackWithArgument() {
}
and make an instance
new CallBackWithArgument<Response>(id) {
#Override
public void success(Response response, Response response2) {
//do something
}
#Override
public void failure(RetrofitError error) {
//do something
}
}
It's easy. You can simply make Callback to hold requested id and create new callback every time
class MyCallback extends Callback<Response> {
private final String id;
MyCallback(String id) {
this.id = id
}
#Override
public void success(Response response, Response response2) {
// get the id
}
#Override
public void failure(RetrofitError error) {
// do some thing
}
}
So when you call service
myService.postIt("777", new MyCallback("777"))