Skip to content
This repository has been archived by the owner on Mar 25, 2020. It is now read-only.

Commit

Permalink
Merge pull request #19 from Fondesa/development
Browse files Browse the repository at this point in the history
Added parcelable save and restore.
  • Loading branch information
fondesa authored May 3, 2017
2 parents 74cbf06 + 0d19b11 commit 34c7521
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 77 deletions.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ You can optionally use the dependency `lyra-coder-gson` if you want to include t

```gradle
dependencies {
compile 'com.github.fondesa:lyra:1.0.0'
compile 'com.github.fondesa:lyra:1.0.1'
// Use this dependency if you want to include the Gson coder.
compile 'com.github.fondesa:lyra-coder-gson:1.0.0'
compile 'com.github.fondesa:lyra-coder-gson:1.0.1'
}
```

Expand All @@ -29,7 +29,7 @@ dependencies {
<dependency>
<groupId>com.github.fondesa</groupId>
<artifactId>lyra</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
<type>pom</type>
</dependency>
```
Expand Down Expand Up @@ -118,6 +118,25 @@ public class MainActivity extends Activity {
}
```

The save/restore of the state is supported also in a custom `View`. For example:

```java
public class AutoSaveEditText extends AppCompatEditText {
@SaveState
CharSequence mText;

@Override
public Parcelable onSaveInstanceState() {
return Lyra.instance().saveState(this, super.onSaveInstanceState());
}

@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(Lyra.instance().restoreState(this, state));
}
}
```

As shown above, you can create your own custom `StateCoder`. For example, this coder will save/restore a `String` in `Base64`:

```java
Expand Down
2 changes: 1 addition & 1 deletion bintray-deploy.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ BINTRAY_LIB_ISSUE_TRACKER_URL=https://github.com/Fondesa/Lyra/issues
BINTRAY_LIB_GIT_URL=https://github.com/Fondesa/Lyra.git
BINTRAY_LIB_GITHUB_REPO=Fondesa/Lyra
BINTRAY_LIB_TAGS=android|lyra|saveinstancestate|bundle
BINTRAY_LIB_VERSION=1.0.0
BINTRAY_LIB_VERSION=1.0.1
BINTRAY_LIB_VERSION_DESCRIPTION=For further information check: https://github.com/Fondesa/Lyra
48 changes: 46 additions & 2 deletions lyra/src/main/java/com/fondesa/lyra/Lyra.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.View;

import com.fondesa.lyra.annotation.SaveState;
import com.fondesa.lyra.coder.CoderRetriever;
Expand Down Expand Up @@ -54,6 +56,7 @@
*/
public class Lyra {
public static final String SUB_BUNDLE_KEY = "lyra:";
public static final String VIEW_SUPER_STATE_BUNDLE_KEY = "view:superState";
private static final String TAG = Lyra.class.getSimpleName();

private Application mApplication;
Expand Down Expand Up @@ -128,7 +131,7 @@ public static String getKeyFromField(@NonNull Field field) {

/**
* Save the annotated fields' state of a class into a {@link Bundle}.
* The fields of a class will be retrieved through a {@link FieldsRetriever}.
* The fields of the class will be retrieved through a {@link FieldsRetriever}.
* The coder to serialize the fields into a {@link Bundle} will be retrieved through a {@link CoderRetriever}.
*
* @param stateHolder instance of the class with annotated fields
Expand Down Expand Up @@ -182,7 +185,7 @@ public void saveState(@NonNull Object stateHolder, @NonNull Bundle state) {

/**
* Restore the annotated fields' state of a class from a {@link Bundle}.
* The fields of a class will be retrieved through a {@link FieldsRetriever}.
* The fields of the class will be retrieved through a {@link FieldsRetriever}.
* The coder to deserialize the fields from a {@link Bundle} will be retrieved through a {@link CoderRetriever}.
*
* @param stateHolder instance of the class with annotated fields
Expand Down Expand Up @@ -230,6 +233,47 @@ public void restoreState(@NonNull Object stateHolder, @Nullable Bundle state) {
}
}

/**
* Save the annotated fields' state of a {@link View} class into a {@link Bundle}.
* The {@link Bundle} will contain the parcelable state of the {@link View}
* with the key {@link #VIEW_SUPER_STATE_BUNDLE_KEY}.
* The fields of the class will be retrieved through a {@link FieldsRetriever}.
* The coder to serialize the fields into a {@link Bundle} will be retrieved through a {@link CoderRetriever}.
*
* @param stateHolder instance of the class with annotated fields
* @param state {@link Bundle} in which you want to save the annotated fields
* @return {@link Bundle} containing view state and Lyra sub-bundle
*/
public Parcelable saveState(@NonNull View stateHolder, @Nullable Parcelable state) {
Bundle wrapper = new Bundle();
// Put the original super state.
wrapper.putParcelable(VIEW_SUPER_STATE_BUNDLE_KEY, state);
// Save the state into the Lyra Bundle.
saveState((Object) stateHolder, wrapper);
return wrapper;
}

/**
* Restore the annotated fields' state of a {@link View} class from a {@link Parcelable}.
* If the state is a {@link Bundle}, the {@link View}'s state and the fields' values will be restored.
* The fields of the class will be retrieved through a {@link FieldsRetriever}.
* The coder to deserialize the fields from a {@link Bundle} will be retrieved through a {@link CoderRetriever}.
*
* @param stateHolder instance of the class with annotated fields
* @param state {@link Bundle} from which you want to restore the value of the annotated fields
* @return {@link Parcelable} containing {@link View}'s saved state
*/
public Parcelable restoreState(@NonNull View stateHolder, @Nullable Parcelable state) {
if (state instanceof Bundle) {
Bundle wrapper = (Bundle) state;
// Get the original super state.
state = wrapper.getParcelable(VIEW_SUPER_STATE_BUNDLE_KEY);
// Restore the state saved in the Lyra Bundle.
restoreState((Object) stateHolder, wrapper);
}
return state;
}

@NonNull
private static SaveState getSaveStateAnnotation(@NonNull Field field) {
SaveState saveState = field.getAnnotation(SaveState.class);
Expand Down
121 changes: 112 additions & 9 deletions lyra/src/test/java/com/fondesa/lyra/LyraTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package com.fondesa.lyra;

import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;

import com.fondesa.lyra.annotation.SaveState;
Expand All @@ -32,6 +34,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.ApplicationTestUtil;

import java.lang.reflect.Field;
Expand All @@ -41,6 +44,7 @@
import io.github.benas.randombeans.EnhancedRandomBuilder;
import io.github.benas.randombeans.api.EnhancedRandom;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
Expand Down Expand Up @@ -92,11 +96,11 @@ public void testKeyFromFieldNotNull() {

@Test
public void testSaveState() throws Exception {
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
final Bundle stateBundle = new Bundle();
final TestModels.SubClassAllModifiersAnnotatedFields saveStateObject =
new TestModels.SubClassAllModifiersAnnotatedFields();

final Bundle stateBundle = new Bundle();
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
Field[] fields = retriever.getFields(saveStateObject.getClass());
for (Field field : fields) {
new FieldAccessibleRunner(field) {
Expand Down Expand Up @@ -125,14 +129,13 @@ public void runWithField(@NonNull Field field) throws Exception {

@Test
public void testRestoreState() throws Exception {
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
final DefaultCoderRetriever coderRetriever = new DefaultCoderRetriever();
final Bundle stateBundle = new Bundle();
final Bundle lyraBundle = new Bundle();

final TestModels.SubClassAllModifiersAnnotatedFields saveStateObject =
new TestModels.SubClassAllModifiersAnnotatedFields();

final Bundle stateBundle = new Bundle();
final Bundle lyraBundle = new Bundle();
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
final DefaultCoderRetriever coderRetriever = new DefaultCoderRetriever();
Field[] fields = retriever.getFields(saveStateObject.getClass());
List<Object> savedValues = new ArrayList<>(fields.length);
for (Field field : fields) {
Expand All @@ -156,11 +159,11 @@ public void testRestoreState() throws Exception {

@Test
public void testSaveAndRestoreState() throws Exception {
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
final Bundle stateBundle = new Bundle();
final TestModels.SubClassAllModifiersAnnotatedFields saveStateObject =
new TestModels.SubClassAllModifiersAnnotatedFields();

final Bundle stateBundle = new Bundle();
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
Field[] fields = retriever.getFields(saveStateObject.getClass());
for (Field field : fields) {
new FieldAccessibleRunner(field) {
Expand All @@ -181,6 +184,106 @@ public void runWithField(@NonNull Field field) throws Exception {
assertThat(fieldRestoredValues, containsInAnyOrder(fieldSavedValues.toArray()));
}

@Test
public void testSaveParcelableState() throws Exception {
final Context context = RuntimeEnvironment.application.getApplicationContext();
final TestModels.SaveStateView saveStateView = new TestModels.SaveStateView(context);

final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
Field[] fields = retriever.getFields(saveStateView.getClass());
for (Field field : fields) {
new FieldAccessibleRunner(field) {
@Override
public void runWithField(@NonNull Field field) throws Exception {
field.set(saveStateView, mRandomizer.nextObject(field.getType()));
}
};
}

Parcelable parcelable = TestModels.ImplementedParcelable.getDefault();
Parcelable stateParcelable = Lyra.instance().saveState(saveStateView, parcelable);

assertTrue(stateParcelable instanceof Bundle);

Bundle stateBundle = (Bundle) stateParcelable;
List<Object> fieldValues = listOfValuesFromFields(saveStateView, fields);
List<Object> savedValues = new ArrayList<>(fieldValues.size());

Bundle lyraBundle = stateBundle.getBundle(Lyra.SUB_BUNDLE_KEY);
// Assert that the sub Bundle has been written.
assertNotNull(lyraBundle);

for (String key : lyraBundle.keySet()) {
savedValues.add(lyraBundle.get(key));
}

assertThat(savedValues, containsInAnyOrder(fieldValues.toArray()));
}

@Test
public void testRestoreParcelableState() throws Exception {
final Context context = RuntimeEnvironment.application.getApplicationContext();
final TestModels.SaveStateView saveStateView = new TestModels.SaveStateView(context);

Parcelable parcelable = TestModels.ImplementedParcelable.getDefault();

final Bundle stateBundle = new Bundle();
stateBundle.putParcelable(Lyra.VIEW_SUPER_STATE_BUNDLE_KEY, parcelable);

final Bundle lyraBundle = new Bundle();
final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
final DefaultCoderRetriever coderRetriever = new DefaultCoderRetriever();
Field[] fields = retriever.getFields(saveStateView.getClass());
List<Object> savedValues = new ArrayList<>(fields.length);
for (Field field : fields) {
SaveState saveState = field.getAnnotation(SaveState.class);
Class<?> fieldType = field.getType();
StateCoder stateCoder = coderRetriever.getCoder(saveState, fieldType);
Object randomObj = mRandomizer.nextObject(fieldType);
savedValues.add(randomObj);
//noinspection unchecked
stateCoder.serialize(lyraBundle, Lyra.getKeyFromField(field), randomObj);
}

// Insert the sub Bundle used by Lyra.
stateBundle.putBundle(Lyra.SUB_BUNDLE_KEY, lyraBundle);
// Restore the state from the sub Bundle.
Parcelable restoredParcelable = Lyra.instance().restoreState(saveStateView, (Parcelable) stateBundle);
assertEquals(restoredParcelable, parcelable);

List<Object> fieldValues = listOfValuesFromFields(saveStateView, fields);
assertThat(savedValues, containsInAnyOrder(fieldValues.toArray()));
}

@Test
public void testSaveAndRestoreParcelableState() throws Exception {
final Context context = RuntimeEnvironment.application.getApplicationContext();
final TestModels.SaveStateView saveStateView = new TestModels.SaveStateView(context);

Parcelable parcelable = TestModels.ImplementedParcelable.getDefault();

final DefaultFieldsRetriever retriever = new DefaultFieldsRetriever();
Field[] fields = retriever.getFields(saveStateView.getClass());
for (Field field : fields) {
new FieldAccessibleRunner(field) {
@Override
public void runWithField(@NonNull Field field) throws Exception {
field.set(saveStateView, mRandomizer.nextObject(field.getType()));
}
};
}

List<Object> fieldSavedValues = listOfValuesFromFields(saveStateView, fields);

Parcelable savedState = Lyra.instance().saveState(saveStateView, parcelable);
Parcelable restoredParcelable = Lyra.instance().restoreState(saveStateView, savedState);

assertEquals(restoredParcelable, parcelable);

List<Object> fieldRestoredValues = listOfValuesFromFields(saveStateView, fields);
assertThat(fieldRestoredValues, containsInAnyOrder(fieldSavedValues.toArray()));
}

private static List<Object> listOfValuesFromFields(@NonNull final Object holder, @NonNull Field[] fields) throws Exception {
final List<Object> fieldValues = new ArrayList<>(fields.length);
for (Field field : fields) {
Expand Down
15 changes: 15 additions & 0 deletions lyra/src/test/java/com/fondesa/lyra/common/TestModels.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;

import com.fondesa.lyra.annotation.SaveState;

Expand Down Expand Up @@ -186,4 +188,17 @@ public static class SaveStateActivity extends Activity {
@SaveState
float h;
}

public static class SaveStateView extends View {
@SaveState
protected ImplementedSerializable f;
@SaveState
public double g;
@SaveState
float h;

public SaveStateView(Context context) {
super(context);
}
}
}
2 changes: 1 addition & 1 deletion sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ new AndroidSharedPlugin(
}
).apply(project)

final def libraryMavenCompileVersion = "1.0.0"
final def libraryMavenCompileVersion = "1.0.1"

dependencies {
compile "com.android.support:appcompat-v7:$ANDROID_SUPPORT_VERSION"
Expand Down
6 changes: 1 addition & 5 deletions sample/src/main/java/com/fondesa/lyra/sample/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
import com.fondesa.lyra.coder.gson.DefaultGsonCoderRetriever;
import com.fondesa.lyra.field.DefaultFieldsRetriever;

/**
* Created by antoniolig on 17/02/17.
*/

public class App extends Application {
@Override
public void onCreate() {
Expand All @@ -25,4 +21,4 @@ public void onCreate() {
}
builder.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

import java.util.ArrayList;

/**
* Created by antoniolig on 24/02/17.
*/
public class BaseMainActivity extends AppCompatActivity {
@SaveState
Byte mByte;
Expand Down
Loading

0 comments on commit 34c7521

Please sign in to comment.