I am working with paging library and I am struggling with the LiveData PagedList.
My paginated list is working properly but there is an scenario where it is not. I have RecyclerView + SwipeRefreshLayout when data is already shown in the list and I disable network and I try to swipe refresh all the data in the list is removed. How can I avoid that?
public class InterestsViewModel extends ViewModel {
private LiveData<PagedList<InterestItem>> mListInterests;
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
private static final int PAGE_SIZE = 15;
private GroupsInterestsDataSourceFactory mGroupsInterestsDataSourceFactory;
public InterestsViewModel() {
Executor executor = Executors.newFixedThreadPool(5);
mGroupsInterestsDataSourceFactory = new GroupsInterestsDataSourceFactory(mCompositeDisposable);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE)
.setEnablePlaceholders(true)
.build();
mListInterests = new LivePagedListBuilder<>(mGroupsInterestsDataSourceFactory,config)
.setFetchExecutor(executor)
.build();
}
public void retry() {
mGroupsInterestsDataSourceFactory.getGroupsInterestsDataSource().getValue().retry();
}
public void refresh() {
mGroupsInterestsDataSourceFactory.getGroupsInterestsDataSource().getValue().invalidate();
}
public LiveData<NetworkState> getNetworkState() {
return Transformations.switchMap(mGroupsInterestsDataSourceFactory.getGroupsInterestsDataSource(), new Function<GroupsInterestsDataSource, LiveData<NetworkState>>() {
#Override
public LiveData<NetworkState> apply(GroupsInterestsDataSource input) {
return input.getNetworkState();
}
});
}
public LiveData<NetworkState> getRefreshState() {
return Transformations.switchMap(mGroupsInterestsDataSourceFactory.getGroupsInterestsDataSource(), new Function<GroupsInterestsDataSource, LiveData<NetworkState>>() {
#Override
public LiveData<NetworkState> apply(GroupsInterestsDataSource input) {
return input.getInitialLoad();
}
});
}
public LiveData<PagedList<InterestItem>> getListInterests() {
return mListInterests;
}
#Override
protected void onCleared() {
super.onCleared();
mCompositeDisposable.dispose();
}
}
From the the fragment:
private void initAdapter() {
mAdapter = new InterestsAdapter(this);
mListInterest.setAdapter(mAdapter);
mInterestsViewModel.getListInterests().observe(this, new Observer<PagedList<InterestItem>>() {
#Override
public void onChanged(#Nullable PagedList<InterestItem> interestItems) {
mAdapter.submitList(interestItems);
}
});
mInterestsViewModel.getNetworkState().observe(this, new Observer<NetworkState>() {
#Override
public void onChanged(#Nullable NetworkState networkState) {
mAdapter.setNetworkState(networkState);
}
});
mInterestsViewModel.getRefreshState().observe(this, new Observer<NetworkState>() {
#Override
public void onChanged(#Nullable NetworkState networkState) {
if (networkState != null) {
if (mAdapter.getCurrentList() != null) {
if (networkState.getStatus()
== NetworkState.LOADED.getStatus() || networkState.getStatus()
== Status.FAILED)
mIsRefreshing = false;
if (mAdapter.getCurrentList().size() > 0) {
mSwipeRLInterests.setRefreshing(networkState.getStatus()
== NetworkState.LOADING.getStatus());
}
}
setInitialLoadingState(networkState);
}
}
});
}
#Override
public void retry() {
mInterestsViewModel.retry();
}
private void initSwipeToRefresh() {
mSwipeRLInterests.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mIsRefreshing = true;
mInterestsViewModel.refresh();
}
});
mSwipeRLInterests.setColorSchemeColors(getResources().getColor(R.color.colorPrimaryDark)
, getResources().getColor(R.color.colorPrimary)
, getResources().getColor(R.color.colorPrimaryDark));
}
I am actually consuming Triposo's API to show the list of countries. https://www.triposo.com/api/
However, in responses, there is no numeric Id and I am struggling while incrementing page size. What should I perform on such responses to overcome? For instance, Github API is fully suitable for Paging Library since it returns itemId as numeric.
https://api.github.com/users
Example Response
{
"results": [
{
"name": "Benin",
"country_id": "Benin",
"snippet": "A safe and relatively easy country for travellers to visit; birthplace of the Voodoo religion and former home of the Kingdom of Dahomey.",
"parent_id": null,
"score": 3.98127216481287,
"id": "Benin"
}
],
"estimated_total": 30845,
"more": true
}
ItemKeyedDataSource.java
public class ItemKeyedCountryDataSource extends ItemKeyedDataSource<Integer, CountryResult> {
private EndpointHelper endpointHelper;
ItemKeyedCountryDataSource() {
endpointHelper = EndpointHelper.getInstance();
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull final LoadInitialCallback<CountryResult> callback) {
final List<CountryResult> countryResultList = new ArrayList<>();
endpointHelper.getCountryList(0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() {
#Override
public Observable<? extends CountryWrapper> call(Throwable throwable) {
return Observable.error(throwable);
}
})
.subscribe(new Subscriber<CountryWrapper>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(CountryWrapper countryWrapper) {
if (countryWrapper.getResults().size() > 0) {
countryResultList.addAll(countryWrapper.getResults());
callback.onResult(countryResultList);
}
}
});
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull final LoadCallback<CountryResult> callback) {
final List<CountryResult> countryResultList = new ArrayList<>();
endpointHelper.getCountryList(params.requestedLoadSize)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() {
#Override
public Observable<? extends CountryWrapper> call(Throwable throwable) {
return Observable.error(throwable);
}
})
.subscribe(new Subscriber<CountryWrapper>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(CountryWrapper countryWrapper) {
if (countryWrapper.getResults().size() > 0) {
countryResultList.addAll(countryWrapper.getResults());
callback.onResult(countryResultList);
}
}
});
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<CountryResult> callback) {
//Do nothing
}
#NonNull
#Override
public Integer getKey(#NonNull CountryResult item) {
return ?; // what do I return here?
}
}
ViewModel.java
public class SearchableActivityViewModel extends ViewModel {
private LiveData<PagedList<CountryResult>> countryResult;
SearchableActivityViewModel() {
Executor executor = Executors.newFixedThreadPool(5);
CountryResultDataSourceFactory countryResultDataSourceFactory = new CountryResultDataSourceFactory();
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(20) //first load
.setPageSize(21)
.build();
//noinspection unchecked
countryResult = new LivePagedListBuilder<String, CountryResult>(countryResultDataSourceFactory, config)
.setFetchExecutor(executor)
.build();
}
public LiveData<PagedList<CountryResult>> getCountryResult(){
return countryResult;
}
#Override
protected void onCleared() {
super.onCleared();
}
}
Thanks in advance. Hope I am clear.
Best,
I'm trying to display some json data to textviews. My "getTitle" getter method works fine and the data is shown in the textview widget. However when I should pass the "getDescription" getter method, it doesn't comes up for suggestion.
I would really love some help with this, or also if I'm not going about it the right way someone could point me in the right direction.
call.enqueue(new Callback<Campaign>() {
#Override
public void onResponse(Call<Campaign> call, Response<Campaign> response) {
if (response.isSuccessful()) {
String id = response.body().getId();
campaignName.setText(getTitle());
campaignDesc.setText();
} else {
Toast.makeText(StartSurveyActivity.this, "Error Retrieving Id", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<Campaign> call, Throwable t) {
// Log error here if request failed
Log.e(TAG, t.toString());
}
});
}
Here is my java campaign model class. Also I thought it was important to mention that my "getDescription" getter and setter is shown in a pale greyish color while the "getTitle" getter and setter is the gold looking color. Thanks for your assistance in advance.
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public Boolean getDeleted() {
return deleted;
}
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSurveyUrl() {
return surveyUrl;
}
public void setSurveyUrl(String surveyUrl) {
this.surveyUrl = surveyUrl;
}
public Integer getTotalResponseCount() {
return totalResponseCount;
}
public void setTotalResponseCount(Integer totalResponseCount) {
this.totalResponseCount = totalResponseCount;
}
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
public List<Question> getQuestions() {
return questions;
}
public void setQuestions(List<Question> questions) {
this.questions = questions;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getOrganisationLogoUrl() {
return organisationLogoUrl;
}
public void setOrganisationLogoUrl(String organisationLogoUrl) {
this.organisationLogoUrl = organisationLogoUrl;
}
public String getOrganisationName() {
return organisationName;
}
public void setOrganisationName(String organisationName) {
this.organisationName = organisationName;
}
Try below, you are calling method on response
campaignName.setText(response.body().getDescription())
The body of the response will hold the data or response object you have defined. So to call any getter or setter method of the response object you have to use body of the response for example response.body().getDescription(), response.body().getCreated(). To show description in the TextView try this
campaignDesc.setText(response.body().getDescription())
I am a complete beginner on rx-java and rx-android. I've heard the learning curve is quite steep in the beginning.
Im trying to replace all Eventbus based code to a more typesafe alternative by using rx-android.
I've set up this snippet to create observables from edit text text change events:
MainActivity
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe(new Action1<EditText>() {
#Override
public void call(EditText editText) {
searchStopResultFragment.query(editText.getText().toString());
}
});
RxUtils:
public static Observable<EditText> createEditTextChangeObservable(final EditText editText){
return Observable.create(new Observable.OnSubscribe<EditText>() {
#Override
public void call(final Subscriber<? super EditText> subscriber) {
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (subscriber.isUnsubscribed()) return;
subscriber.onNext(editText);
}
});
}
});
}
SearchStopResultFragment:
public void query(String query){
lastQuery = query;
resultObservable = StopProvider.getStopResultObservable(getActivity().getContentResolver(),query);
subscription = resultObservable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Stop>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Stop> stops) {
if(!lastQuery.equals("")) {
if(stops.size()>0) {
ArrayList<AdapterItem> items = adapter.getItems();
items.clear();
for (Stop stop : stops) {
SearchResultStopItem item = new SearchResultStopItem(stop, SearchResultStopItem.STOP);
items.add(item);
}
adapter.setItems(items);
adapter.notifyDataSetChanged();
}else{
//DO A NOTHER ASYNC QUERY TO FETCH RESULTS
}
}else{
showStartItems();
}
}
});
}
It feels like i'm doing this wrong. I create new observables from the query method in my fragment on every text change event. I also want to create a new async lookup operation based off the result in StopProvider.getStopResultObservable (see the comment)
Any thoughs?
Here is what I came up with:
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.map(EXTRACT_STRING)
.filter(STRING_IS_NOT_EMPTY)
.concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(final String query) {
return StopProvider.getStopResultObservable(getContentResolver(), query)
.map(new Func1<List<Stop>, Pair<String, List<Stop>>>() {
// I think this map is a bit more readable than the
// combineLatest, and since "query" should not be changing
// anyway, the result should be the same (you have to
// declare it as final in the method signature, though
#Override
public Pair<String, List<Stop>> call(List<Stop> stops) {
return new Pair(query, stops);
}
});
}
)
.concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first)
.map(new Func1<LocationNameResponse, List<Stop>>() {
#Override
public List<Stop> call(LocationNameResponse locationNameResponse) {
// since there was no if-else in your original code (you were always
// just wrapping the List in an Observable) I removed that, too
return locationNameResponse.getAddresses();
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
// since I don't know what your API is returning I think
// it's saver to keep this check in:
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
where:
public static final Func1<EditText, String> EXTRACT_STRING = new Func1<EditText, String>() {
#Override
public void String call(EditText editText) {
return editText.getText().toString();
}
};
public static final Func1<String, Boolean> STRING_IS_NOT_EMPTY = new Func1<String, Boolean>() {
#Override
public void String call(String string) {
return !string.isEmpty();
}
};
So, this at least removes the need to return Observable.just(null) and then check for that down the chain.
You can move your second concatMap to the only place you need it - after combineLatest
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.concatMap(new Func1<EditText, Observable<Pair<String, List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if (query.isEmpty()) {
return Observable.just(null);
}
return Observable
.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s, stops);
}
})
.concatMap(new Func1<R, Observable<? extends Pair<String, List<Stop>>>>() {
#Override
public Observable<? extends Pair<String, List<Stop>>> call(R r) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
});
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
Solved it using concatmap and combine latest:
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if(query.isEmpty()){
return Observable.just(null);
}
return Observable.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s,stops);
}
});
}
}).concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if(queryAndStops!=null) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
return Observable.just(null);
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle()).subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
}else{
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
However is there some nicer way to break out of the chain without sending Observable.just(null) and check for nulls in next call?
I am trying to implement the server connections with retrofit library.
Everything seems fine but when I receive the data on success callback it crashes with the below exception.
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to aandroid.com.retrofitframework.requestData.DataList
at cuiserve.com.volleyframework.activity.RetrofitActivity$1.onDataReceived(RetrofitActivity.java:18)
at cuiserve.com.volleyframework.httpConnection.ConnectionHelper.success(ConnectionHelper.java:44)
at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
here is the activity class making the server calls
public class RetrofitActivity extends SuperActivity {
private ConnectionHelper.ServerListener<DataList> httpListener = new ConnectionHelper.ServerListener<DataList>() {
#Override
public void onDataReceived(DataList data) {
hideProgressBar();
Log.d("ANSH",data.getBalance());
}
#Override
public void onErrorReceived(String errorMsg) {
hideProgressBar();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showProgressBar();
ConnectionHelper<DataList> helper = new ConnectionHelper<DataList>(HttpRequestConstant.LOGIN_REQUEST,httpListener);
helper.getResponse();
}
#Override
public View getLayoutResource() {
return null;
}
#Override
protected void internetAvailable() {
}
}
ConnectionHelper.class making the request
public class ConnectionHelper<T> implements Callback<T> {
private int requestType;
private ServerListener<T> listener;
public ConnectionHelper(int requestType, ServerListener listener) {
this.requestType = requestType;
this.listener = listener;
}
public void getResponse() {
switch (requestType) {
case HttpRequestConstant.LOGIN_REQUEST:
IServerConnection<T> connection = restAdapter.create(IServerConnection.class);
connection.login(this);
break;
}
}
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://www.json-generator.com/api")
.setLogLevel(RestAdapter.LogLevel.FULL)
.setConverter(new JacksonConverter(Mapper.get()))
.build();
#Override
public void success(T t, Response response) {
listener.onDataReceived(t);
// success callback false here with the exception
// as ClassCastException and says LinkedHashMap can not be cast to DataList(which i pass as class that the response has to be mapped to)
}
#Override
public void failure(RetrofitError error) {
}
public interface ServerListener<T> {
public void onDataReceived(T data);
public void onErrorReceived(String errorMsg);
}
}
The interface and the method with retrofit annotation
public interface IServerConnection<T> {
#GET(HttpRequestConstant.JACKSON_FETCH)
void login(Callback<T> cb);
}
The custom JacksonConverter which is my suspicion
public class JacksonConverter implements Converter {
private ObjectMapper mapper = new ObjectMapper();
public JacksonConverter(ObjectMapper objectMapper) {
this.mapper = objectMapper;
}
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
JavaType javaType = mapper.getTypeFactory().constructType(type);
try {
return mapper.readValue(body.in(), javaType);
} catch (IOException e) {
throw new ConversionException(e);
}
}
#Override
public TypedOutput toBody(Object object) {
try {
String charset = "UTF-8";
String json = mapper.writeValueAsString(object);
return new JsonTypedOutput(json.getBytes(charset));
} catch (IOException e) {
throw new AssertionError(e);
}
}
private static class JsonTypedOutput implements TypedOutput {
private final byte[] jsonBytes;
JsonTypedOutput(byte[] jsonBytes) {
this.jsonBytes = jsonBytes;
}
#Override
public String fileName() {
return null;
}
#Override
public String mimeType() {
return "application/json; charset=UTF-8";
}
#Override
public long length() {
return jsonBytes.length;
}
#Override
public void writeTo(OutputStream out) throws IOException {
out.write(jsonBytes);
}
}
}
and the DataList
package cuiserve.com.volleyframework.requestData;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* Created by ansh on 5/4/15.
*/
#JsonIgnoreProperties(ignoreUnknown = true)
public class DataList extends SomeClass{
private String _id;
private int index;
private String guid;
private boolean isActive;
private String balance;
private String picture;
private int age;
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public String getBalance() {
return balance;
}
public void setBalance(String balance) {
this.balance = balance;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Now the problem is when i do it without using generics(which i have done in the code) things work fine and don't give any exception but in case of generics it fails.
Why am I getting LinkedHashMap classCastException where nothing is related to that in my code.Please help.
By using a generic parameter the actual type information is completely lost to the runtime and cannot be inferred. It essentially ends up being the same as Object. When Gson sees that you want an Object type, it uses a Map to place the JSON information in. This way, if you re-serialize that object instance the data will be retained.
You cannot use generic interfaces with Retrofit. An exception has been added when you try to do this rather than letting it fail in this manner for the next release.
Here is what I do to handle such cases:
import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
public abstract class ModelCallback<Model> implements Callback<Object> {
Class classOfT;
public ModelCallback(Class modelClass){
this.classOfT = modelClass;
}
#Override
public void success(Object model, Response response) {
Gson gson = new Gson();
onSuccess((Model) gson.fromJson(gson.toJson(model),classOfT));
}
#Override
public void failure(RetrofitError error) {
onError(new Exception(error.getMessage()));
}
public abstract void onSuccess(Model model);
public abstract void onError(ClyngException e);
}