Giter Site home page Giter Site logo

akatsuki's Introduction

Akatsuki

Android Arsenal Build Status

Akatsuki is an Android library that handles state restoration via annotations. The library automatically generates source files through JSR269 to ensure almost1 zero performance impact.

Typical usage looks like:

public class MainActivity extends Activity {

    @Retained String myString;
    @Retained int myInt;
    @Retained Account account; // Account implements Parcelable

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Akatsuki.restore(this, savedInstanceState);
        //everything restored!   
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Akatsuki.save(this, outState);
    }
}

Annotate the fields you want to persist, Akatsuki takes care of the rest.

If you are new to Android development, retaining fields are painful and error prone. You have to create string keys and then manually call the set<Type> and get<Type>of the Bundle object. To demonstrate, here is the same Activity without using Akatsuki:

public class MainActivity extends Activity {

    private static final String MY_STRING = "myString";
    private static final String MY_INT = "myInt";
    private static final String ACCOUNT = "account";
	
	String myString;
	int myInt;
    Account account;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		myString = savedInstanceState.getString(MY_STRING);
		myInt = savedInstanceState.getInt(MY_INT);
		account = savedInstanceState.getParcelable(ACCOUNT);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putString(MY_STRING, myString);
		outState.putInt(MY_INT, myInt);
		outState.putParcelable(ACCOUNT, account);
	}
}

1Reflection is used only once to locate the generated classes.


Advanced usage

Akatsuki supports any kind of objects(Fragments, Services, and Views), not just Activity. As long as the persisting medium is a Bundle, the code generation will work.

For View states, Akatsuki provides several utility methods to make view state restoration possible. To demonstrate:

public static class MyView extends View {

	@Retained int myInt;

	public MyView(Context context) {
		super(context);
	}

	@Override
	protected Parcelable onSaveInstanceState() {
		return Akatsuki.save(this, super.onSaveInstanceState());
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		super.onRestoreInstanceState(Akatsuki.restore(this, state));
	}
}

Because Bundle is just a type safe container for Parcel, you can also use Akatsuki like so:

// you can have some bean
static class MyStuff{
	@Retained int foo;
	@Retained String bar;
	// your getters/setters etc
}

// serialize your bean into a Bundle
MyStuff stuff = new MyStuff();
Bundle bundle = Akatsuki.serialize(stuff);


// restore your bean from the Bundle
MyStuff stuff = Akatsuki.deserialize(new MyStuff(), bundle);

For debug purposes, @Retain has a method called skip(), when set to true, the field will not be retained. Adding the transient modifier also has the same effect.

Supported types

All data types supported by Bundle is supported by Akatsuki, that includes:

IBinder
boolean
boolean[]
Bundle
byte
byte[]
char
char[]
CharSequence
CharSequence[]
ArrayList<CharSequence>
double
double[]
float
float[]
IBinder
int
int[]
ArrayList<Integer>
long
long[]
String
Parcelable
Parcelable[]
ArrayList<T>
Serializable
short
short[]
Size
SizeF
SparseArray<T>
String
String[]
ArrayList<String>

ETS (Extended Type Support) allows you to retain even more types:

  • Multidimensional array of all supported types (not encouraged though as an unique key will have to be generated for EVERY element)
  • LinkedList, CopyOnWriteArrayList or simply List for all ArrayList supported types

More types will be added, submit an issue or pull request if you feel something is missing from the list.

##Annotated types Types annotated with @Retained will also be saved and restored but not instantiated. What this means is that you would have to do something like this:

@Retained MyType foo; // MyType has fields annotated with @Retained
@Retained int bar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // manually instantiate your custom type
	foo = new MyType(/* some arguments*/); 
	// bar is a supported type so no need to instantiate
    Akatsuki.restore(this, savedInstanceState); 
}

This is because Akatsuki does not know how your custom type is created so you will have to instantiate it yourself BEFORE Akatsuki.restore(Object, Bundle) is called. ##Generic parameters Generic parameters are supported if and only if the type can be computed at compile time. This means that a type <T> must have known bounds.
The following examples will work

<T extends Parcelable> 
<T extends Serializable>
<T extends Parcelable & Serializable> // intersection type

When a intersection type is found, a search for supported type is executed from left to right. If a supported type is found, that type will be used for serialization.

Inheritance

Inheritance is fully supported, annotated fields will continue to persist in subclasses.

##Field hiding Field hiding is supported through traversing the class hierarchy to avoid field name collisions. NOTE: The use of field hiding is discouraged as it makes code hard to follow.

Parceler support

<a name="parceler>Akatsuki supports Parceler annotated beans Simply add @IncludeClasses(ParcelerSupport.class) to any class in your project.(MainActivity or your custom Application class perhaps). And don't forget to import:

compile 'com.sora.util.akatsuki:akatsuki-parceler:0.0.1@aar'

TypeConverters

Akatsuki supports pluggable TypeConverters , this allows you to support any type by writing a TypeConverter for it.

Here's a custom type:

public class Foo {
	String name;
	int id;
	Date timestamp;
	// more stuff
}

The TypeConverter would look something like this:

public class FooConverter implements TypeConverter<Foo> {
	@Override
	public void save(Bundle bundle, Foo foo, String key) {
		bundle.putString(key + "name", foo.name);
		bundle.putInt(key + "id", foo.id);
		// Date.toString() is for illustration purpose only, do not use!
		bundle.putString(key + "timestamp", foo.timestamp.toString());
	}
	@Override
	public Foo restore(Bundle bundle, Foo foo, String key) {
		foo.name = bundle.getString(key + "name");
		foo.id = bundle.getInt(key + "id");
		// new Date(String) is for illustration purpose only, do not use!
		foo.timestamp = new Date(bundle.getString(key + "timestamp"));
		return foo;
	}
}

The example above can be simpler:

public class BetterFooConverter extends MultiKeyTypeConverter<Foo> {
	@Override
	protected String[] saveMultiple(Bundle bundle, Foo foo) {
		String[] keys = generateKey(3);
		bundle.putString(keys[0], foo.name);
		bundle.putInt(keys[1], foo.id);
		bundle.putString(keys[2], foo.timestamp.toString());
		return keys;
	}
	@Override
	protected Foo restoreMultiple(Bundle bundle, Foo foo, String[] keys) {
		foo.name = bundle.getString(keys[0]);
		foo.id = bundle.getInt(keys[1]);
		foo.timestamp = new Date(bundle.getString(keys[2]));
		return foo;
	}
}

To let Akatsuki know about the converter, simply use:

@Retained(converter = BetterFooConverter.class) Foo myFoo;

or if you have many fields, register the converter so that Foo automatically uses the converter:

@DeclaredConverter(@TypeFilter(type=@TypeConstraint(type = Foo.class)))
public class BetterFooConverter extends MultiKeyTypeConverter<Foo> {
	// ...
}

Using converters do incur a tiny performance impact as the converter has to be instantiated before the type can be properly serialized(Only once through reflection). Also, custom types can also have @Retained like described here. In that case we can just add @Retained to all the fields like:

public class Foo {
	@Retained String name;
	@Retained int id;
	@Retained Date timestamp; // not supported natively...
}

And write your custom TypeConverter just for the Date type. It's up to you to decide whether you want a TypeConverter or annotate fields with @Retained. Normally you would prefer @Retianed for codes that you control and TypeConverter for other types that you cannot modify(eg: types from another library ).

@TransformationTemplate

@TransformationTemplate allows you to support arbitrary types. This is the zero performance impact alternative to TypeConverter. To understand how this works, here's the what the class ParcelerSupport actually looks like:

@TransformationTemplate(
		save = "{{bundle}}.putParcelable(\"{{fieldName}}\", org.parceler.Parcels.wrap({{fieldName}}))",
		restore = "{{fieldName}} = org.parceler.Parcels.unwrap({{bundle}}.getParcelable(\"{{fieldName}}\"))",
		filters = @TypeFilter(type=@TypeConstraint(type = Parcel.class)),
		execution = Execution.BEFORE)
public class ParcelerSupport {
    // dummy class, any class in the project will work
}

Parceler annotated beans need Parcels.wrap() and Parcels.unwrap() before the object becomes usable. In the example above, we simply create a code template that includes the custom logic for wrapping and unwrapping Parceler objects. Akatsuki uses mustache to convert the template into code that gets emitted into the generated source file. Everything is done in compile time so there will be no runtime cost. The @TransformationTemplate annotation has a retention of RetentionPolicy.CLASS so that it has no effect in runtime while other libraries using the annotation could still retain the template. For more information on how to write Transformation templates, the javadoc contains examples and docs for all methods in the annotation.

Due to a limitation of APT, if the template is located in another library, you have to include the class that is annotated with @TransformationTemplate with @IncludeClasses(TheClass.class), see the Parceler support section for more info.

Warning: @TransformationTemplate does not do any validation on the templates, however, syntax errors does prevent your project from compiling which can save you from debugging heedlessly.

Opt-out

Akatsuki is designed to be flexible. You can still retain your fields the old way in conjunction with Akatsuki and nothing will break. There is only one thing to look out for: key collisions (Akatsuki uses the field name as key).

Why another library?

Currently, we have Icepick and possibly some other libraries that I'm not aware of. The main motivation for this library is due to the inflexibility of Icepick. Icepick does not support Parceler which is a deal breaker for me (there's a discussion on why that's the case). Incompatibility between Parceler and Icepick does not justify the creation of a another library, I could have forked Icepick and added the support myself and be happy. But no, I want to see how APT works so I did a clean room implementation of Icepick and added some other features.

Download

 compile 'com.sora.util.akatsuki:akatsuki-api:0.0.1'
 apt 'com.sora.util.akatsuki:akatsuki-compiler:0.0.1'

Optional parceler support:

 compile 'com.sora.util.akatsuki:akatsuki-parceler:0.0.1@aar'

You can download the sample app here if you want to test it out (nothing surprising though, just a very simple demo with a Fragment + NumberPicker/EditText).

License

Copyright 2015 WEI CHEN LIN

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

akatsuki's People

Contributors

tom91136 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.