Skip to content

Instantly share code, notes, and snippets.

@Fbalashov
Last active June 25, 2016 21:23
Show Gist options
  • Save Fbalashov/7dea76140ac06ef36fc97b1c82d080eb to your computer and use it in GitHub Desktop.
Save Fbalashov/7dea76140ac06ef36fc97b1c82d080eb to your computer and use it in GitHub Desktop.
Declarative Persistence in Android
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.fbalashov.persistableannotations.MainActivity">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"/>
<Button
android:id="@+id/increment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/increment_button"
android:layout_below="@id/text_view"/>
</RelativeLayout>
package com.fbalashov.persistableannotations;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
/**
* @author Fuad.Balashov on 6/22/2016.
*/
public class BaseActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
package com.fbalashov.persistableannotations;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private int persistedCount = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.text_view);
Button button = (Button) findViewById(R.id.increment_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
persistedCount++;
textView.setText("Button clicked " + persistedCount + " times");
}
});
textView.setText("Button clicked " + persistedCount + " times");
}
}
package com.fbalashov.persistableannotations;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends BaseActivity {
@Persistable private int persistedCount = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.text_view);
Button button = (Button) findViewById(R.id.increment_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
persistedCount++;
textView.setText("Button clicked " + persistedCount + " times");
}
});
textView.setText("Button clicked " + persistedCount + " times");
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
Map<String, Object> objectsToPersist = new HashMap<>();
// get all fields with the Persistable annotation
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Persistable.class)) {
// for each field, make the field accessible (in case it's private) and then write its value into the map
field.setAccessible(true);
try {
objectsToPersist.put(field.getName(), field.get(this));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return objectsToPersist;
}
@Override
public final Object onRetainCustomNonConfigurationInstance() {
Map<String, Object> objectsToPersist = new HashMap<>();
// get all fields with the Persistable annotation
List<Field> annotatedFields = getAllFieldsWithAnnotation(new ArrayList<Field>(), Persistable.class, this.getClass(), BaseActivity.class);
for (Field field : annotatedFields) {
// for each field, make the field accessible (in case it's private) and then write its value into the map
field.setAccessible(true);
try {
objectsToPersist.put(field.getName(), field.get(this));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return objectsToPersist;
}
/**
* Finds all the fields with the given annotation (public and private0 in the given class. Recursively repeats
* this for super classes until reaching the given endClass.
* @param annotatedFields an empty list that will be populated with any fields that match the annotationClazz
* @param annotationClass the annotation to look for
* @param startClazz the starting subclass to look for fields in
* @param endClazz the super class at which to stop looking for fields
* @return all fields with the given annotation
*/
private List<Field> getAllFieldsWithAnnotation(List<Field> annotatedFields, Class<Persistable> annotationClass, Class startClazz, Class<BaseActivity> endClazz) {
Field[] fields = startClazz.getDeclaredFields();
for (Field field : fields) {
if(field.isAnnotationPresent(annotationClass)) {
annotatedFields.add(field);
}
}
if (endClazz.equals(startClazz)) {
return annotatedFields;
}
return getAllFieldsWithAnnotation(annotatedFields, annotationClass, startClazz.getSuperclass(), endClazz);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Right after super.onCreate we get the retained instance state and reinitiate any persisted fields.
if (getLastCustomNonConfigurationInstance() instanceof Map) {
List<Field> annotatedFields = getAllFieldsWithAnnotation(new ArrayList<Field>(), Persistable.class, this.getClass(), BaseActivity.class);
for (Field field : annotatedFields) {
// for each field, make the field accessible (in case it's private) and then set it's value based on the value in the map
field.setAccessible(true);
try {
field.set(this, ((Map<String, Object>) getLastCustomNonConfigurationInstance()).get(field.getName()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Right after super.onCreate we get the retained instance state and reinitiate any persisted fields.
if (getLastCustomNonConfigurationInstance() instanceof Map) {
reinitializePersistedFields(((Map<String, Object>) getLastCustomNonConfigurationInstance()));
}
}
/**
* Reinitializes any fields that have the Persistable annotation after state change
*/
private void reinitializePersistedFields(Map<String, Object> persistedObjects) {
// get all fields with the Persistable annotation
List<Field> annotatedFields = getAllFieldsWithAnnotation(new ArrayList<Field>(), Persistable.class, this.getClass(), BaseActivity.class);
for (Field field : annotatedFields) {
// for each field, make the field accessible (in case it's private) and then set it's value based on the value in the map
field.setAccessible(true);
try {
field.set(this, persistedObjects.get(field.getName()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* Any field marked with this annotation will be persisted on state changes.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
protected @interface Persistable{}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment