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
})
});
Related
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!
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
}
});
}
}
I have to respond to result of volley request. but because it is asynchronous. I need to wait for the results before I proceed. If I don't I will get nullobjects.
How do I set flags and wait until flags are off.
categoryslashid = new JSONObject[Category_IDs.size()];//size of the list containing all categories
taskfinished = new boolean[Category_IDs.size()];
//boolean is initialized to false
//Request to category/{id} to get
for(int i= 0;i<Category_IDs.size();i++)
{ makevolleyrequesforCategorySlashID(Const.URL_FULL_STOREURL+Const.URL_PRODUCT_GET_CATEGORY_ID,i);
}
public void makevolleyrequesforCategorySlashID(URL,id)
{
//volley implementation
public void onResponseoverride
{
categoryslashid[i]=response;
taskfinished[i]=true;
}
}
Now I must proceed after I get all the booleans in task finished become true.
public boolean areAllTrue()
{
for(boolean b : taskfinished) if(!b) return false;
return true;
}
Implement an Interface and use it to call back when your data is ready. Something like this:
public interface OnDownloadTaskCompleted {
public void onTaskCompleted(List<ofSomething> list, boolean error, String message);
}
Then you should pass an instance of this to your request and override onTaskCompleted()
private void downloadData(){
final DownloadUsingVolley downloader = new DownloadUsingVolley(getActivity());
downloader.retrieveData(new OnDownloadTaskCompleted() {
#Override
public void onTaskCompleted(List<ofSomething> list, boolean error, String message) {
//do something with your data
}
});
}
I'm assuming that you have a class where you implemented volley stuff (DownloadusingVolley) and a method do call on it and make the request itself (retrieveData).
retrieveData can be implemented like this:
private void retrieveData(String url, final OnDownloadTaskCompleted taskCompleted){
final JsonObjectRequest request = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>(){
#Override
public void onResponse(JSONObject response) {
try {
//parse
taskCompleted.onTaskCompleted(result,false,null);
}catch (JSONException e){
taskCompleted.onTaskCompleted(0,true,e.getMessage());
}
}
},new Response.ErrorListener(){
#Override
public void onErrorResponse(VolleyError volleyError) {
taskCompleted.onTaskCompleted(0,true,volleyError.getMessage());
}
});
//adding request into the queue
ApplicationClass.getInstance().addToRequestQueue(request,"aTag");
}
Here you can find a nice tutorial about volley:
Asynchronous HTTP Requests in Android Using Volley
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"))
I have APIHelper class, whose static methods asynchronously make request to server, receive json string, parse and return Object or ArrayList:
...
public static ArrayList<Item> getItemsInCategory(int id_category) throws ExecutionException, InterruptedException, JSONException {
DoRequest doRequest = new DoRequest();
String jsonString = doRequest.execute(API_PATH + PRODUCT_SEARCH + CATEGORY_ID + id_category).get();
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray("products");
return Item.fromJson(jsonArray);
}
public static Item getItem(int id_item) throws ExecutionException, InterruptedException, JSONException {
DoRequest doRequest = new DoRequest();
String jsonString = doRequest.execute(API_PATH + PRODUCT_GET_INFO + id_item).get();
JSONObject jsonObject = new JSONObject(jsonString);
return Item.fromJson(jsonObject);
}
...
Now I want to make methods without calling get() method from AsyncTask class DoRequest.
My DoRequest class:
public class DoRequest extends AsyncTask<String, Void, String> {
ResultListener mResultListener;
public abstract interface ResultListener{
Object onResultAvailable(String result) throws JSONException;
}
DoRequest(ResultListener resultListener){
mResultListener = resultListener;
}
#Override
protected String doInBackground(String... URL) {
ServiceHandler serviceHandler = new ServiceHandler();
String jsonStr = serviceHandler.makeServiceCall(URL[0]);
return jsonStr;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
try {
mResultListener.onResultAvailable(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
Help me change my methods in APIHelper class that they return values after callback from DoRequest.
You can use an event bus like otto to have the async task publish an event to the event bus once in onPostExecute and then have the whoever the Helper class is returning its results to, listen to the event on the bus and then handle the callback there.
http://square.github.io/otto/
An example of using this would be:
First you are going to have to use a custom post method to be able to post to threads from the background.
public class MainThreadBus extends Bus {
private final Handler handler = new Handler(Looper.getMainLooper());
#Override public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
super.post(event);
} else {
handler.post(new Runnable() {
#Override
public void run() {
post(event);
}
});
}
}
}
Now that we have that set up, inside of the class that calls the helper class we create a register on the bus and call the helper method:
class someClass(){
///some stuff
public void performRestCall(int id_category){
bus.register(this);
ApiHelper.getItemsInCategory(id_category);
}
#Subscribe
public void restCallCompleted(GetCategoryEvent e){
ArrayList<Item> list = e.getList();
//do whatever else you need to
bus.unRegister(this);
}
}
Now in the asyncTask onPostExecute we perform all the work we were doing after the asyncTask finished and tell everyone on the bus that this event has completed. We pass the list in the object instead of return it from a method.:
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
bus.register(this);
try {
JSONObject jsonObject = new JSONObject(jsonString);
bus.post(new GetCategoryEvent( Item.fromJson(jsonObject));
} catch (JSONException e) {
e.printStackTrace();
}
}
Your solution will end up being something along these lines.