I am trying to read custom annotation value loaded through a different classloader.
How do I convert the Annotation object to json?
#Retention(RententionPolicy.RUNTIME)
public #interface SendEmail{
public String id;
}
Gson gson = new Gson();
Object object = (Object)annotation // this holds SendEmail object loaded from different classloader
gson.toJson(object);
//I get UnsupportedOperationException: Attempted to Serialize java.lang.Class: SendEmail. Forget to register a type adapter?
What is the type adapter to be used for interfaces?
I'm not really sure why you need to serialize annotation instances that are constant by design, but you're most likely using an Oracle JVM that instantiates annotations using java.lang.proxy.Proxy. Proxies are treated as reflective-access data objects in Gson (and there are no mentions of proxies in the standard Gson bundle) and Gson just fails on serializing java.lang.Class which does not make much sense in your scenario.
You need to create a new type adapter factory that can emit an annotation-aware type adapter that would use reflection over annotations heavily. Say,
final class AnnotationTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new AnnotationTypeAdapterFactory();
private AnnotationTypeAdapterFactory() {
}
static TypeAdapterFactory getInstance() {
return instance;
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
#Nullable
final Class<? extends Annotation> annotationClass = Annotations.lookupAnnotationClass(typeToken.getRawType());
if ( annotationClass == null ) {
return null;
}
final List<Method> methods = Annotations.lookupMethods(annotationClass);
final int count = methods.size();
final String[] names = new String[count];
#SuppressWarnings("unchecked")
final TypeAdapter<Object>[] typeAdapters = new TypeAdapter[count];
final Map<String, TypeAdapter<Object>> namedTypeAdapters = new HashMap<>();
for ( int i = 0; i < count; i++ ) {
final Method method = methods.get(i);
names[i] = method.getName();
#SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<Object> typeAdapter = (TypeAdapter) gson.getAdapter(method.getReturnType());
typeAdapters[i] = typeAdapter;
namedTypeAdapters.put(names[i], typeAdapter);
}
final TypeAdapter<T> typeAdapter = new TypeAdapter<T>() {
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final T annotation)
throws IOException {
try {
out.beginObject();
for ( int i = 0; i < count; i++ ) {
out.name(names[i]);
typeAdapters[i].write(out, methods.get(i).invoke(annotation));
}
out.endObject();
} catch ( final IllegalAccessException | InvocationTargetException ex ) {
throw new RuntimeException(ex);
}
}
#Override
public T read(final JsonReader in)
throws IOException {
try {
in.beginObject();
final Map<String, Object> properties = new HashMap<>();
while ( in.hasNext() ) {
final String name = in.nextName();
#Nullable
final TypeAdapter<Object> objectTypeAdapter = namedTypeAdapters.get(name);
if ( objectTypeAdapter == null ) {
in.skipValue();
} else {
properties.put(name, objectTypeAdapter.read(in));
}
}
in.endObject();
#SuppressWarnings("unchecked")
final T annotation = (T) Annotations.create(annotationClass, properties);
return annotation;
} catch ( final NoSuchMethodException ex ) {
throw new RuntimeException(ex);
}
}
};
return typeAdapter.nullSafe();
}
}
where the Annotations class is as follows:
final class Annotations {
private static final boolean SUN_PACKAGE = false;
private Annotations() {
}
static <T extends Annotation> T create(final Class<T> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
return create(annotationClass.getClassLoader(), annotationClass, properties);
}
static <T extends Annotation> T create(final ClassLoader classLoader, final Class<T> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
if ( SUN_PACKAGE ) {
#SuppressWarnings("unchecked")
final T annotation = (T) AnnotationParser.annotationForMap(annotationClass, properties);
return annotation;
}
#SuppressWarnings("unchecked")
final T annotation = (T) Proxy.newProxyInstance(
classLoader,
new Class<?>[]{ annotationClass },
DynamicAnnotation.fromMap(annotationClass, lookupProperties(annotationClass, properties))
);
return annotation;
}
#Nullable
static Class<? extends Annotation> lookupAnnotationClass(final Class<?> clazz) {
if ( clazz.isAnnotation() ) {
#SuppressWarnings("unchecked")
final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz;
return annotationClass;
}
final Class<?>[] interfaces = clazz.getInterfaces();
if ( interfaces.length != 1 ) {
return null;
}
final Class<?> iface = interfaces[0];
if ( !Annotation.class.isAssignableFrom(iface) ) {
return null;
}
#SuppressWarnings("unchecked")
final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) iface;
return annotationClass;
}
static List<Method> lookupMethods(final Class<? extends Annotation> annotationClass) {
final List<Method> methods = new ArrayList<>();
for ( final Method method : annotationClass.getMethods() ) {
if ( method.getDeclaringClass() == annotationClass ) {
methods.add(method);
}
}
return Collections.unmodifiableList(methods);
}
static Map<String, Object> lookupProperties(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
final Map<String, Object> namedProperties = new HashMap<>();
namedProperties.putAll(lookupDefaultProperties(annotationClass));
namedProperties.putAll(properties);
return Collections.unmodifiableMap(namedProperties);
}
static Map<String, Object> lookupDefaultProperties(final Class<? extends Annotation> annotationClass) {
final Map<String, Object> defaultProperties = new HashMap<>();
for ( final Method method : lookupMethods(annotationClass) ) {
#Nullable
final Object defaultValue = method.getDefaultValue();
if ( defaultValue != null ) {
defaultProperties.put(method.getName(), defaultValue);
}
}
return Collections.unmodifiableMap(defaultProperties);
}
static String toString(#SuppressWarnings("TypeMayBeWeakened") final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
final StringBuilder builder = new StringBuilder("#")
.append(annotationClass.getTypeName())
.append('(');
boolean atTail = false;
for ( final Map.Entry<String, Object> e : properties.entrySet() ) {
if ( atTail ) {
builder.append(", ");
}
builder.append(e.getKey())
.append('=')
.append(e.getValue());
atTail = true;
}
return builder.append(')')
.toString();
}
}
and a custom implementation of annotations:
abstract class DynamicAnnotation
implements Annotation, InvocationHandler {
private static final Method java_lang_Object_equals;
private static final Method java_lang_Object_hashCode;
private static final Method java_lang_Object_toString;
private static final Method java_lang_annotation_Annotation_annotationType;
static {
try {
java_lang_Object_equals = Object.class.getDeclaredMethod("equals", Object.class);
java_lang_Object_hashCode = Object.class.getDeclaredMethod("hashCode");
java_lang_Object_toString = Object.class.getDeclaredMethod("toString");
java_lang_annotation_Annotation_annotationType = Annotation.class.getDeclaredMethod("annotationType");
} catch ( final NoSuchMethodException ex ) {
throw new Error(ex);
}
}
private final String toString;
private final Class<? extends Annotation> annotationClass;
private DynamicAnnotation(final String toString, final Class<? extends Annotation> annotationClass) {
this.toString = toString;
this.annotationClass = annotationClass;
}
static DynamicAnnotation fromMap(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
return FromMap.create(annotationClass, properties);
}
#Nullable
protected abstract Object invoke(final Method method)
throws Throwable;
#Override
public final Class<? extends Annotation> annotationType() {
return annotationClass;
}
// must conform the https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/Annotation.html#hashCode() contract
#Override
public final int hashCode() {
//return hashCode;
throw new UnsupportedOperationException();
}
// must conform the https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/Annotation.html#equals(java.lang.Object) contract
#Override
public final boolean equals(#Nullable final Object obj) {
throw new UnsupportedOperationException();
}
#Override
public final String toString() {
return toString;
}
#Override
#Nonnull
public final Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ( method.equals(java_lang_annotation_Annotation_annotationType) ) {
return annotationType();
}
if ( method.equals(java_lang_Object_equals) ) {
return equals(args[0]);
}
if ( method.equals(java_lang_Object_hashCode) ) {
return hashCode();
}
if ( method.equals(java_lang_Object_toString) ) {
return toString();
}
#Nullable
final Object returnValue = invoke(method);
if ( returnValue == null ) {
throw new NoSuchMethodException("The instance of " + annotationClass + " has no value associated with " + method.getName());
}
return returnValue;
}
private static final class FromMap
extends DynamicAnnotation {
private final Map<String, Object> properties;
private FromMap(final String toString, final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
super(/*hashCode, */toString, annotationClass);
this.properties = properties;
}
private static DynamicAnnotation create(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
final Map<String, Object> toStringProperties = new LinkedHashMap<>();
for ( final Method method : Annotations.lookupMethods(annotationClass) ) {
final String name = method.getName();
if ( !properties.containsKey(name) ) {
throw new NoSuchMethodException("Cannot find " + name + " in " + properties + " while constructing an instance of " + annotationClass);
}
final Object value = properties.get(name);
toStringProperties.put(name, value);
}
final String toString = Annotations.toString(annotationClass, Collections.unmodifiableMap(toStringProperties));
return new FromMap(toString, annotationClass, properties);
}
#Override
protected Object invoke(final Method method) {
return properties.get(method.getName());
}
}
}
You might also use AnnotationUtils (if it works for you), or AnnotationParser from the "sun package" (if it's an option either) to fulfill the annotation interface contracts.
Here is an example of use for round-trip:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(AnnotationTypeAdapterFactory.getInstance())
.create();
public static void main(final String... args)
throws NoSuchFieldException {
final SendEmail before = Wrapper.class.getDeclaredField("constant").getAnnotation(SendEmail.class);
System.out.println(before);
final String json = gson.toJson(before);
System.out.println(json);
final SendEmail after = gson.fromJson(json, SendEmail.class);
System.out.println(after);
System.out.println(after.annotationType());
System.out.println(gson.toJson(after));
}
private static final class Wrapper {
#SendEmail(id = "email#mail.com")
private static final Object constant = new Object();
}
Related
I'm using spring boot 2.1.7.RELEASE and spring-kafka 2.2.8.RELEASE.And I'm using #KafkaListener annotation to create a consumer and I'm using all default settings for the consumer.And I'm using below configuration as specified in the Spring-Kafka documentation.
// other props
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, AvroDeserializer.class.getName());
return new DefaultKafkaConsumerFactory<>(props);
Now, I've implemented my custom SeekToCurrentErrorHandler by extending SeekToCurrentErrorHandler as per the below thread but the record value is coming as null and the record key is not in a readable format. Please suggest me how can i get the record key and value?
How to capture the exception and message key when using ErrorHandlingDeserializer2 to handle exceptions during deserialization
Here is my custom SeekToCurrentErrorHandler code
#Component
public class MySeekToCurrentErrorHandler extends SeekToCurrentErrorHandler {
private final MyDeadLetterRecoverer deadLetterRecoverer;
#Autowired
public MySeekToCurrentErrorHandler(MyDeadLetterRecoverer deadLetterRecoverer) {
super(-1);
this.deadLetterRecoverer = deadLetterRecoverer;
}
#Override
public void handle(Exception thrownException, List<ConsumerRecord<?, ?>> data, Consumer<?, ?> consumer, MessageListenerContainer container) {
if (thrownException instanceof DeserializationException) {
//Improve to support multiple records
DeserializationException deserializationException = (DeserializationException) thrownException;
deadLetterRecoverer.accept(data.get(0), deserializationException);
ConsumerRecord<?, ?>. consumerRecord = data.get(0);
sout(consumerRecord.key());
sout(consumerRecord.value());
} else {
//Calling super method to let the 'SeekToCurrentErrorHandler' do what it is actually designed for
super.handle(thrownException, data, consumer, container);
}
}
}
If the key fails deserialization, the original byte[] can be obtained by calling getData() on the exception.
Similarly, if the value fails deserialization, use getData() to get the original data.
The DeadLetterPublishingRecoverer does this (since 2.3).
You can tell which of the key or value failed by calling isKey() on the exception.
EDIT
I was wrong, the key and value are available if the value or key failed.
This is written with Boot 2.3.4:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
SeekToCurrentErrorHandler errorHandler(ProducerFactory<String, String> pf) {
Map<String, Object> configs = new HashMap<>(pf.getConfigurationProperties());
configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
ProducerFactory<byte[], byte[]> bytesPF = new DefaultKafkaProducerFactory<>(configs);
KafkaOperations<byte[], byte[]> template = new KafkaTemplate<>(bytesPF);
return new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template),
new FixedBackOff(1000, 5));
}
#KafkaListener(id = "so64597061", topics = "so64597061",
properties = {
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG
+ ":org.springframework.kafka.support.serializer.ErrorHandlingDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG
+ ":org.springframework.kafka.support.serializer.ErrorHandlingDeserializer",
ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS
+ ":com.example.demo.Application$FailSometimesDeserializer",
ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS
+ ":com.example.demo.Application$FailSometimesDeserializer"
})
public void listen(String val, #Header(name = KafkaHeaders.RECEIVED_MESSAGE_KEY) String key) {
System.out.println(key + ":" + val);
}
#KafkaListener(id = "so64597061.dlt", topics = "so64597061.DLT",
properties = {
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG
+ ":org.apache.kafka.common.serialization.ByteArrayDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG
+ ":org.apache.kafka.common.serialization.ByteArrayDeserializer"
})
public void dltListen(byte[] val, #Header(name = KafkaHeaders.RECEIVED_MESSAGE_KEY, required = false) byte[] key) {
String keyStr = key != null ? new String(key) : null;
String valStr = val != null ? new String(val) : null;
System.out.println("DLT:" + keyStr + ":" + valStr);
}
#Bean
public ApplicationRunner runner(KafkaTemplate<String, String> template) {
return args -> {
template.send("so64597061", "foo", "bar");
template.send("so64597061", "fail", "keyFailed");
template.send("so64597061", "valueFailed", "fail");
};
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so64597061").partitions(1).replicas(1).build();
}
#Bean
public NewTopic dlt() {
return TopicBuilder.name("so64597061.DLT").partitions(1).replicas(1).build();
}
public static class FailSometimesDeserializer implements Deserializer<byte[]> {
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
#Override
public byte[] deserialize(String topic, byte[] data) {
return data;
}
#Override
public void close() {
}
#Override
public byte[] deserialize(String topic, Headers headers, byte[] data) {
String string = new String(data);
if ("fail".equals(string)) {
throw new RuntimeException("fail");
}
return data;
}
}
}
spring.kafka.consumer.auto-offset-reset=earliest
foo:bar
DLT:fail:keyFailed
DLT:valueFailed:fail
I am trying to convert / serialize the #Id field (which is not a string) of the model class but keep getting this error. The custom ID class just concatenates two values with a colon, e.g. aaaa:2345.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MyIdClass] to type [byte[]]
This is my model class.
#RedisHash("alert")
public class MyClass implements Serializable
{
public static class MyIdClass
{
public String userId;
public Long sessionExpiry;
public MyIdClass()
{
}
public MyIdClass(String id, Long ex)
{
userId = id;
sessionExpiry = ex;
}
}
public static class MyIdClassSerializer implements RedisSerializer<MyIdClass>
{
#Nullable
#Override
public byte[] serialize(#Nullable MyIdClass uid) throws SerializationException
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
#Nullable
#Override
public MyIdClass deserialize(#Nullable byte[] bytes) throws SerializationException
{
String[] t = new String(bytes).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#ReadingConverter
public static class MyIdClassReader implements Converter<byte[], MyIdClass>
{
#Nullable
#Override
public MyIdClass convert(byte[] source)
{
String[] t = new String(source).split(":");
return new MyIdClass(t[0], Long.parseLong(t[1]));
}
}
#Component
#WritingConverter
public static class MyIdClassWriter implements Converter<MyIdClass, byte[]>
{
#Nullable
#Override
public byte[] convert(MyIdClass uid)
{
return String.format("%s:%d", uid.userId, uid.sessionExpiry).getBytes();
}
}
/**
* User ID
* Key := 'alert:' userId ':' sessionExpiry
*/
#Id
public MyIdClass id;
public String value;
}
Here's how I'm configuring the RedisTemplate.
#Bean("redisTemplateActivityAlert")
public RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> redisTemplateActivityAlert()
{
RedisTemplate<ActivityAlert.UserIdExpiry, ActivityAlert> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new ActivityAlert.UserIdExpirySerializer());
template.setHashKeySerializer(new ActivityAlert.UserIdExpirySerializer());
return template;
}
I've read other posts about configuring ConversionService or TypeConverter but haven't gone far with them.
add this Bean to your RedisConfiguration :
#Bean
public RedisCustomConversions redisCustomConversions(MyIdClassReader myIdClassReader , MyIdClassWriter myIdClassWriter ) {
return new RedisCustomConversions(Arrays.asList(myIdClassWriter,myIdClassReader));
}
Trying to implement a recycleview inside of a fragment using a custom adapter and receiving the following error.
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference. AtStoreAdapter.getItemCount(StoreAdapter.java:xx)
I have looked around SO, but have not seen anything that is leading to a solution. Any help would be appreciated.
Android app - Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
and a few others
StoreAdapter
public class StoreAdapter extends RecyclerView.Adapter<StoreAdapter.StoreHolder>{
private LayoutInflater mInflator;
private List<Store> mStore= Collections.emptyList();
private Context mContext;
public StoreAdapter(Context mContext, List<Store> mStore){
mInflator = LayoutInflater.from(mContext);
this.mStore = mStore;
this.mContext = mContext;
}
// Simple nested class that holds the various view components for the adapter
// and as specified in *layout.xml .
public class StoreHolder extends RecyclerView.ViewHolder{
TextView mStoreTitle, mStoreDetails, mStoreCategory;
public StoreHolder (View itemView){
super(itemView);
mStoreTitle = (TextView) itemView.findViewById(R.id.title);
mStoreDetails = (TextView) itemView.findViewById(R.id.details);
mStoreCategory = (TextView) itemView.findViewById(R.id.category);
}
}
// Called when the RecyclerView needs a new RecyclerView.ViewHolder (*Holder)
// to represent an item. We inflate the XML layout and return our view (*Holder)
#Override
public StoreHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflator.inflate(R.layout.todo_list_row, parent,false);
return new StoreHolder(view);
}
// Called by RecyclerView to display the data at the specified position.
// This method needs to update the contents of the view to reflect the item at the
// given position e.g. we are updating the view here with the data
#Override
public void onBindViewHolder(StoreAdapter.StoreHolder holder, int position) {
holder.mStoreTitle.setText(mStore.get(position).getStoreTitle());
holder.mStoreDetails.setText(mStore.get(position).getStoreDetails());
holder.mStoreCategory.setText(mStore.get(position).getStoreCategory());
}
public void setData(List<Store> store){
this.mStore = store;
}
public void delete(int position){
mStore.remove(position);
notifyItemRemoved(position);
}
#Override
public int getItemCount() {
return mStore.size();
}
}
StoreSectionLoader
public class StoreSectionLoader extends AsyncTaskLoader<List<Store>> {
private static final String LOG_TAG = StoreSectionLoader.class.getSimpleName();
private List<Store> mStore;
private ContentResolver mContentResolver;
private Cursor mCursor;
public StoreSectionLoader(Context context, Uri uri, ContentResolver contentResolver){
super(context);
mContentResolver = contentResolver;
}
#Override
public List<Store> loadInBackground() {
String[] projection = {BaseColumns._ID,
RetailFinderContract.RetailFinderColumns.STORE_COMPLETE,
RetailFinderContract.RetailFinderColumns.STORE_CREATED,
RetailFinderContract.RetailFinderColumns.STORE_TITLE,
RetailFinderContract.RetailFinderColumns.STORE_DETAILS,
RetailFinderContract.RetailFinderColumns.STORE_CATEGRORY};
List<Store> entry = new ArrayList<>();
Uri uri = RetailFinderContract.URI_LOCATION_TABLE;
mCursor = mContentResolver.query(uri, projection, null, null, null);
if(mCursor != null){
if(mCursor.moveToFirst()){
do {
int _id = mCursor.getInt(mCursor.getColumnIndex(BaseColumns._ID));
String store_title = mCursor.getString(
mCursor.getColumnIndex(RetailFinderContract.RetailFinderColumns.STORE_TITLE));
String store_details = mCursor.getString(
mCursor.getColumnIndex(RetailFinderContract.RetailFinderColumns.STORE_DETAILS));
String store_category = mCursor.getString(
mCursor.getColumnIndex(RetailFinderContract.RetailFinderColumns.STORE_CATEGRORY));
Store store = new Store(_id,store_title,store_details,store_category);
entry.add(store);
} while (mCursor.moveToNext());
}
}
return entry;
}
#Override
public void deliverResult(List<Store> store) {
if(isReset()){
if (mStore != null){
mCursor.close();
}
}
List<Store> oldStoreList = mStore;
if(mStore == null || mStore.size() == 0 ){
Log.d(LOG_TAG, "+++++++++++++++ No Data returned");
}
mStore = store;
if(isStarted()){
super.deliverResult(store);
}
if(oldStoreList != null && oldStoreList != store){
mCursor.close();
}
}
#Override
protected void onStartLoading() {
if(mStore != null){
deliverResult(mStore);
}
if(takeContentChanged() || mStore == null){
forceLoad();
}
}
#Override
protected void onStopLoading() {
cancelLoad();
}
#Override
protected void onReset() {
onStopLoading();
if(mCursor != null){
mCursor.close();
}
mStore = null;
}
#Override
public void onCanceled(List<Store> store) {
super.onCanceled(store);
if(mCursor != null){
mCursor.close();
}
}
#Override
public void forceLoad() {
super.forceLoad();
}
}
StoreFragment
public class StoreFragment extends Fragment implements
LoaderManager.LoaderCallbacks<List<Store>> {
private static final String LOG_TAG = StoreListFragment.class.getSimpleName();
private StoreAdapter mAdapter;
private static final int LOADER_ID = 1;
private ContentResolver mContentReslover;
private List<Store> mStore;
private Context mContext;
protected RecyclerView mRecyclerView;
private static final String TAG = "RecyclerViewFragment";
private static final String KEY_LAYOUT_MANAGER = "layoutManager";
private enum LayoutManagerType {
GRID_LAYOUT_MANAGER,
LINEAR_LAYOUT_MANAGER
}
protected LayoutManagerType mCurrentLayoutManagerType;
protected RecyclerView.LayoutManager mLayoutManager;
// end added
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mContentReslover = getActivity().getContentResolver();
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.store_list_row, container, false);
rootView.setTag(TAG);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.t_recycler);
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
if (savedInstanceState != null) {
// Restore saved layout manager type.
mCurrentLayoutManagerType = (LayoutManagerType) savedInstanceState
.getSerializable(KEY_LAYOUT_MANAGER);
}
setRecyclerViewLayoutManager(mCurrentLayoutManagerType);
mAdapter = StoreAdapter(getContext(), mStore); //issue
mRecyclerView.setAdapter(mAdapter);
return rootView;
}
public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) {
int scrollPosition = 0;
// If a layout manager has already been set, get current scroll position.
if (mRecyclerView.getLayoutManager() != null) {
scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
}
switch (layoutManagerType) {
case LINEAR_LAYOUT_MANAGER:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
break;
default:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
}
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.scrollToPosition(scrollPosition);
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save currently selected layout manager.
savedInstanceState.putSerializable(KEY_LAYOUT_MANAGER, mCurrentLayoutManagerType);
super.onSaveInstanceState(savedInstanceState);
}
#Override
public Loader<List<Store>> onCreateLoader(int id, Bundle args) {
mContentReslover = getActivity().getContentResolver();
return new StoreSectionLoader(getActivity(), RetailFinderContract.URI_STORES_TABLE, mContentReslover);
}
#Override
public void onLoadFinished(Loader<List<Store>> loader, List<Store> store) {
mAdapter.setData(store);
mStore = store;
}
#Override
public void onLoaderReset(Loader<List<Store>> loader) {
mAdapter.setData(null);
}
}
I am new in android using fragments in my Project. first time my fragment is creating then api called and get data load in fragment. here when i clicked at any item i replaced fragment by another fragment there also another api called and load data to fragment.
now here problem situation generated for me.
from here i back Button pressed.
fragment reloading same as first time creating but it should be show data as i left before going to next fragment.
so please provide me solution how i can get same data as i left means savedInstanceState data.
im my first fragment getCategory method call Api and get Data first time when i choose any category i replace fragment with another fragment but when i m returning same getCategory method recall perform same process as it first time.
fragment should not call api method again on backpressed it should show same category on this i clicked before.
my first fragment where calling api......
public class LandingFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private GridLayoutManager gridLayoutManager;
private static RecyclerView category_Grid;
private Fragment myFragment = null;
ProgressBar mProgressView;
View mLoginFormView;
private Category category;
private CategoryAdapter categoryAdapter;
private List<CategoryObject> rowListItem;
private String productId;
private OnFragmentInteractionListener mListener;
public LandingFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment LandingFragment.
*/
// TODO: Rename and change types and number of parameters
public static LandingFragment newInstance(String param1, String param2) {
LandingFragment fragment = new LandingFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v= inflater.inflate(R.layout.fragment_landing, container, false);
return v;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViews(view);
RecyclerViewListeners();
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
#Override
public void onAttach(Activity context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
private void initViews(final View v) {
mLoginFormView = (View)v.findViewById(R.id.mainView);
mProgressView = (ProgressBar)v.findViewById(R.id.login_progress);
category_Grid = (RecyclerView)v.findViewById(R.id.cat_grid);
category_Grid.setHasFixedSize(true);
gridLayoutManager = new GridLayoutManager(getActivity(), 3);
category_Grid.setLayoutManager(gridLayoutManager);
}
private void RecyclerViewListeners(){
category_Grid.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), category_Grid, new ItemClickListener(){
#Override
public void onClick(View view, int position) {
String entityId = rowListItem.get(position).getCategoryId();
String catName = rowListItem.get(position).getName();
Integer ishave = rowListItem.get(position).getIshaveSubcategories();
if(ishave==1) {
myFragment = SubcategoryFragment.newInstance(""+catName, "" + entityId);
ActivityUtils.launchFragementWithAnimation(myFragment, getActivity());
}else{
myFragment = ProductListFragment.newInstance("", "" + entityId);
ActivityUtils.launchFragementWithAnimation(myFragment, getActivity());
}
}
#Override
public void onLongClick(View view, int position) {
}
}));
}
public void getCategory() {
showProgress(true);
String URL = getResources().getString(R.string.category_api);
StringRequest req = new StringRequest(Request.Method.POST,URL, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
VolleyLog.v("Response:%n %s", response);
Gson gson = new GsonBuilder().serializeNulls().create();
try {
JSONObject jsonObject = new JSONObject(response);
if (jsonObject.getString("status").equals(getResources().getString(R.string.response_success))){
category = gson.fromJson(response, Category.class);
rowListItem = category.getCategory();
if(navigationUpdated){
someEventListener.someEvent(rowListItem);
navigationUpdated = false;
}
Log.d("CATEGORYID::::::::",""+rowListItem.get(1).getCategoryId());
categoryAdapter = new CategoryAdapter(getActivity(),rowListItem);
category_Grid.setAdapter(categoryAdapter);
categoryAdapter.notifyDataSetChanged();
return;
}
else if (jsonObject.getString("status").equals(getResources().getString(R.string.login_Er_respose))){
Log.e("","ERRORRRRRR");
return;
}
} catch (JSONException e) {
showProgress(false);
Log.e("My App", "Could not parse malformed JSON: \"" + response + "\"");
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
showProgress(false);
VolleyLog.e("Error: ", error.getMessage());
}
}){
#Override
protected Map<String,String> getParams(){
Map<String,String> params = new HashMap<String, String>();
return params;
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String,String> params = new HashMap<String, String>();
params.put("Content-Type","application/x-www-form-urlencoded");
return params;
}
};
AppController.getInstance().addToRequestQueue(req);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
You can check your rowListItem.size(), if it's size is 0 then call getCategory() service, otherwise load your data from your rowListItem. Here is sample code which I am using to load data from arraylist if it is not empty:
if (mArrayArticle.size() == 0) {
isDataLoading = true;
mRecyclerList.setVisibility(View.INVISIBLE);
getCategory();
} else {
mHomeItemAdapter = new HomeItemAdapter(getActivity(), mArrayArticle, this);
mRecyclerList.setAdapter(mHomeItemAdapter);
}
Here mArrayArticle is my ArrayList, Hope it will help you.
for more clarification i want to tell..
how i implement the #Bhvk_AndroidBee solution
fragment backpressed call onActivityCreated Method so first overridethis method in fragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//here you can check null condition for rowListItem
}
}
inside onActivityCreated method I checked the condition like that
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(rowListItem!=null){
categoryAdapter = new CategoryAdapter(getActivity(),rowListItem);
category_Grid.setAdapter(categoryAdapter);
categoryAdapter.notifyDataSetChanged();
}else {
//call the method for first time creating your view
getCategory();
}
}
hope this would be helpfull for more strugglers like me...
I want to load data from a remote server in the grid. The following code:
final RepServiceAsync service = GWT.create(RepService.class);
final RepProperties props = GWT.create(RepProperties.class);
RpcProxy<PagingLoadConfig, PagingLoadResult<ReportsList>> proxy = new RpcProxy<PagingLoadConfig, PagingLoadResult<ReportsList>>() {
#SuppressWarnings("unchecked")
#Override
public void load(PagingLoadConfig loadConfig, AsyncCallback callback) {
service.getReports(callback);
}
};
ListStore<ReportsList> store = new ListStore<ReportsList>(props.key());
final PagingLoader<PagingLoadConfig, PagingLoadResult<ReportsList>> loader = new PagingLoader<PagingLoadConfig, PagingLoadResult<ReportsList>>(
proxy);
loader.setRemoteSort(true);
loader.addLoadHandler(new LoadResultListStoreBinding<PagingLoadConfig, ReportsList, PagingLoadResult<ReportsList>>(
store));
final PagingToolBar toolBar = new PagingToolBar(50);
toolBar.getElement().getStyle().setProperty("borderBottom", "none");
toolBar.bind(loader);
ColumnConfig<ReportsList, String> nameCol = new ColumnConfig<ReportsList, String>(
props.name(), 150, "Name");
ColumnConfig<ReportsList, String> pathCol = new ColumnConfig<ReportsList, String>(
props.path_name(), 150, "Path");
List<ColumnConfig<ReportsList, ?>> l = new ArrayList<ColumnConfig<ReportsList, ?>>();
l.add(nameCol);
l.add(pathCol);
ColumnModel<ReportsList> cm = new ColumnModel<ReportsList>(l);
Grid<ReportsList> grid = new Grid<ReportsList>(store, cm) {
#Override
protected void onAfterFirstAttach() {
super.onAfterFirstAttach();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
loader.load();
}
});
}
};
grid.getView().setForceFit(true);
grid.setLoadMask(true);
grid.setLoader(loader);
RepProperties:
public interface RepProperties extends PropertyAccess<ReportsList> {
#Path("id")
ModelKeyProvider<ReportsList> key();
ValueProvider<ReportsList, String> name();
ValueProvider<ReportsList, String> path_name();
}
ReportsList code:
public class ReportsList implements Serializable {
private static final long serialVersionUID = 1L;
int id;
String name;
String path_name;
public ReportsList() {
}
public ReportsList(int id, String name, String path_name) {
super();
this.id = id;
this.name = name;
this.path_name = path_name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath_name() {
return path_name;
}
public void setPath_name(String path_name) {
this.path_name = path_name;
}
}
GWT Servlet Impl:
public class RepServiceImpl extends RemoteServiceServlet implements RepService {
private static final long serialVersionUID = 1L;
#EJB
private ReportEjb repManager;
#Override
public List<Report> getReports() {
List<Report> reports = null;
reports = repManager.getReports();
return reports ;
}
}
The code is executed without error, the query to the database is performed ( EJB-call ), but the Grid is not populated.
In what could be the problem?
In my experience this usually means that there is an exception while trying to put data into the grid itself. Try attaching a LoadExceptionHandler to your loader and see what it gives you
e.g.
public class DebugLoadHandler implements LoadExceptioniHandler<ListLoadConfig> {
#Override
public void onLoadException(LoadExceptionEvent<ListLoadConfig> event) {
Window.alert("Load Exception" + event.getException().getMessage());
}
}