I'm trying to use retrofit for get records from my API and it works fine when i do something like this.
public interface materialAPI {
#GET("/mlearningServices/Course")
public void getMaterials(Callback<List<materialClass>> response); } public void getMaterials()
{
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(Root_Url).build();
Log.i(TAG , "hERE IS THE LINK"+adapter.toString());
materialAPI api = adapter.create(materialAPI.class);
api.getMaterials(new Callback <List<materialClass>>() {
#Override
public void success(List<materialClass> list, Response response) {
materials = list;
showList();
customAdapter customAdapter = new customAdapter();
listView.setAdapter(customAdapter);
}
#Override
public void failure(RetrofitError error) {
}
});
}
The above code works fine and i can get all my materials but what i want to achieve next is get material with any id . When a user selects a paticular material, i want to pass the id into the get url so i can get the records
meaning i have to do something like this
#GET("/mlearningServices/Course/{myId}")
..
How to i add myId to the callback method. This is my first time of using retrofit
Use the #Path annotation
#POST("/mlearningServices/Course/{myId}")
public void getMaterials(#Path("myId") String id, Callback<Response> response);
References:
https://square.github.io/retrofit/2.x/retrofit/index.html?retrofit2/http/Path.html
http://square.github.io/retrofit/
That you are asking about is called a path variable. To set one, you must rewrite your method signature as this:
public void getMaterials(#Path("myId") String id, Callback<List<materialClass>> response);
This way, the variable defined as /path/to/your/endpoint/{nameOfPathVariable} will be injected into that String parameter passed to the method. You could also define it as an Integer, and retrofit will try to cast it accordingly.
Solution:
You can use this to pass your id, Use the #Path annotation
#GET("/mlearningServices/Course/{myId}")
Call<materialClass> getMaterials(#Path("myId") String id);
#Path is some data that you wish to provide it to GET method before Question Mark ("?") and #Query("..") is some data you wish to provide after "?"
Hope you have understood.
Related
I'm using LiveData with MVVM. After updating my database with Room, I am trying to sendback both the Object I inserted into my Room database, and also the adapter position. In my ViewModel class, the method is:
private MutableLiveData<String> insertItemLiveData = new MutableLiveData<>;
public void insertMenuItem(MenuItem menuItem, int adapterPositionToUpdate){
repo.insertOrder(menuItem.getId())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull Integer integer) {
//The order is successfully inserted into database
//So I return back the name of the inserted order
String s = "Inserted Item: " + menuItem.getNameOfOrder();
insertItemLiveData.setValue(s);
}
#Override
public void onError(#NonNull Throwable e) {
errorLiveData.setValue("Failed to cancel order.");
}
});
}
In the on success method, it returns the String I want to display, but I also want to update the position of the Recyclerview item that has changed. What is the best way to handle this situation?
I can use a wrapper class and have setters for a String and the adapter position, but I feel like there's probably a better way to do this.
A resource wrapper is a good idea for it.MVVM Resource Wrapper With Live Data you can check my code to get an insight on how to use it
I am fetching news details from News Api site. I am using ViewModel architecture so that the device does not fetch the results when orientation changes.
As per various turorials i am able to fetch the result to a recyclerview using retrofit and viewmodel buy giving static parameters as query to the rest api.
private void loadTopHeadlines() {
ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
Call<TopHeadlinesResponse> response = apiInterface.getTopHeadlines("in", 20, 1,
"api_key");
response.enqueue(new Callback<TopHeadlinesResponse>() {
#Override
public void onResponse(Call<TopHeadlinesResponse> call, Response<TopHeadlinesResponse> response) {
topHeadlinesResponse.setValue(response.body().getArticles());
}
#Override
public void onFailure(Call<TopHeadlinesResponse> call, Throwable t) {
}
});
}
As you can see in the ViewModel class that is created the method getTopHeadlines() uses static parameters. How do i change it to dynamic parameters.
Static parameter
Call<TopHeadlinesResponse> response = apiInterface.getTopHeadlines("in", 20, 1,
"api_key");
Dynamic parameter
Call<TopHeadlinesResponse> response = apiInterface.getTopHeadlines(dynamic, dynamic, dynamic,
"api_key");
So basically your method should accept those parameters, e.g.:
private void loadHeadlines(String stringValue, int number, int otherNumer)
//...do some stuff
Call<TopHeadlinesResponse> response = apiInterface.getTopHeadlines(stringValue, number, otherNumber, "api_key");
//rest stays the same
}
I have my rest api like:
http://MY_SERVER/api/story?feed_ids=1,2,3&page=1
here i should provide dynamic list of feed_ids separated by comma,
for that i wrote my rest service like:
#GET("/story")
void getStory( #Query("feed_ids") List<Integer> feed_items, #Query("page") int page,Callback<StoryCollection> callback);
and:
private List<Integer> items = Arrays.asList(1, 2, 3); // items is a list of feed ids subscribed by user (retrieved from app db). initialization is done here just for testing
public void getLatestStoryCollection(int page ,Callback<StoryCollection> callback) {
newsService.getStory(items, page ,callback);
}
my code runs fine but retrofit sends request url like:
http://MY_SERVER/api/story?feed_ids=1&feed_ids=2&feed_ids=3&page=1
is there a way to send such dynamic list of parameters just like feed_ids=1,2,3 without having repeated parameter name?
You could create a custom class that overrides toString() to format them as a comma-separated list. Something like:
class FeedIdCollection extends List<Integer> {
public FeedIdCollection(int... ids) {
super(Arrays.asList(ids));
}
#Override
public String toString() {
return TextUtils.join(",", this);
}
}
And then make your declaration:
#GET("/story")
void getStory( #Query("feed_ids") FeedIdCollection feed_items, #Query("page") int page, Callback<StoryCollection> callback);
There is not a way to do it in retrofit, but you can do it pretty easily yourself. Since you are using android, you can use TextUtils.join() to convert any list to a String. Then pass that string to as your query parameter instead of a list. First, update your interface to take a String instead of a List.
#GET("/story")
void getStory( #Query("feed_ids") String feed_items, #Query("page") int page, Callback<StoryCollection> callback);
then when you call your getStory method, pass the items through join first --
String items = TextUtils.join(",", Arrays.asList(1, 2, 3));
newsService.getStory(items, page, callback);
The REST Api I'm working with has custom codes and messages which are sent from server depending on the state, I would like to implement a custom Callback<T> that calls the success method only if the status code was 0.
Example SUCCESS Response received from server:
{
"code":"0",
"message":"success",
"data": {
"actual_data":"goes_here",
"need_to_construct_objects","from_data"
}
}
Example of FAILURE Response:
{
"code":"301",
"message":"wrong_password",
"data": {
"actual_data":"will_be_null",
"no_need_to_construct_objects","from_data"
}
}
code and message are returned by all requests, the data contains the actual response values, so I would like to do the following:
Check the code and message and only call success() if code is 0.
Call failure() if request failed or code != 0
Construct custom objects based on the data response and pass them via success()
What is the best way to do this? I searched everywhere and could not find a good solution. The only one I got was to let all custom objects have the code and message fields too and check their values inside success(), but this could cause problems in future in case someone forgets to check the code before proceeding.
You can do that quickly by just making an abstract class that implements Callback, and declare your own abstract success and failure methods. The abstract class will handle Retrofit's standard callback methods, interpret the response and call the abstract methods accordingly.
I think another possible approach to this is to override Retrofit's Client interface to build your own Response object.
If you extend OkClient, it can go like this:
public class CustomClient extends OkClient {
#Override public Response execute(Request request) throws IOException {
Response originalRespone = super.execute(request);
int statusCode = 0;
//TODO: read JSON response here (using GSON or similar, and extract status code and message... etc.)
//Convert the status code to HTTP standard status codes, according to the documentation you have.
if(statusCode == 0) statusCode = 200;
//Reconstruct a Response object
return new Response(originalResponse.getUrl(), statusCode, originalResponse.getReason() /*should probably replace with parsed message*/, originalResponse.getHeaders(), originalResponse.getBody());
}
This may be more work than handling your case in Callback, but I think it can help if at some point the API transitions to RESTful API conventions.
This solution comes with its own problem though, because that means the JSON conversion will run twice. One in your client, and another one by Retrofit. Not sure the correct way to do that at the moment. Probably something around TypedInput and a dummy Converter that passes already converted objects.
Create a custom ResponseBodyConverter like this:
public class CustomResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
CustomResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException,CustomException {
String json = "";
try {
String body = value.string();
json = new JSONObject(body).getJSONObject("data").toString();
int code = new JSONObject(body).getInt("code");
String message = new JSONObject(body).getString("message");
if(code != 0){
throw new CustomException(message);
}
} catch (JSONException e) {
e.printStackTrace();
}
return adapter.fromJson(json);
}
}
It's a better idea to implement a custom callback. You can an example about it below.
public abstract class DefaultRequestCallback<T> implements Callback<T> {
public abstract void failure(Meta meta);
public abstract void success(T responseBean);
#Override
public void success(T baseResponseBean, Response response) {
// You can check your responsebean's error code and
// convert it to a default error
BaseResponseBean bean = (BaseResponseBean) baseResponseBean;
if (bean == null) {
failure(new Meta(ApplicationConstants.ERROR_RETROFIT, "Unknown Error!"));
} else if (bean.getMeta() != null && bean.getMeta().getCode() != ApplicationConstants.RESULT_SUCCESS) {
failure(bean.getMeta());
} else {
success(baseResponseBean);
}
}
#Override
public void failure(RetrofitError error) {
// Convert default error to your custom error.
Meta meta = new Meta(ApplicationConstants.ERROR_RETROFIT, "Error Unknwon");
failure(meta);
}
}
Give your custom callback to your retrofit service interface method.
void yourMethod(DefaultRequestCallback<YourResponseBean> callback);
Good Luck.
This will at least get you started. You can basically create your own custom callback and then handle the success. Look at what was sent and do what you need to.
public class CustomCallback implements Callback {
#Override
public void success(Object o, Response response) {
//Check for success
//if( Success )
//callback.success(o, response);
//else
//Check for error
//callback.failure(error);
}
}
In your case, you can have a class that maps your json response:
class CustomResponse {
String code;
String message;
Data data;
static class Data {
String actualData;
String needToContructObjects;
String noNeedToContructObjects;
}
}
Then, since you're back to the java objects world, you can have a factory-like object inside your success method callback that creates the desired object based on the returned custom response. If you want to get this response in the failure callback, I'd reconsider using Retrofit, since your API is not following a good Rest design.
Although this is plenty possible, and understanding you might not be involved on the API development, be aware this is not a good API design approach. If you are POSTing a login request to the server, you can understand this request as a request to create a resource (an authenticated user session, for instance). If you don't send the correct parameters (the correct username and password in this specific case), the server should reject the resource creation request and send back a 4-hundred-something (4xx) http status code indicating your request was not correct somehow. Retrofit would understand this 4xx status code and call your failure callback, where you could handle the response appropriately.
I'm using retrofit for fetching data from resource. But think my architecture is wrong.
So, i have an fragment with listview, for example.
In onCreateView after UI setup i calls API method(async). That returns list of models i need to setup my listview adapter.
Thats i do in callback
private Callback<List<User>> mUsersCallback = new Callback<List<User>>() {
#Override
public void success(List<User> users, Response response) {
mLoadingLayout.hideLoading();
mPeopleAdapter = new PeopleAdapter(getActivity(), users);
lvPeople.setAdapter(mPeopleAdapter);
}
#Override
public void failure(RetrofitError error) {
mLoadingLayout.hideLoading();
Log.d("get users", error.getUrl() + " " + error.toString());
}
};
In this part i sometimes get NPE when call getActivity();
How to do it on right way?
Your activity has been destroyed during the call, when you try to get it in your callback, it may be null.
Simply check if activity is not null and if null ignore the callback.
Try to create your own Callback class and there pass your Activity or Context by constructor.
Similar to this answer: https://stackoverflow.com/a/25665521/2399340