Home

Awesome

android-retention-magic

Helper to retain instance states in Android Activities and Fragments

This is a small library to make it easier to retain the instance state of an Android Fragment or Activity. In addition it can persist fields to SharedPreferences and initialize fields from the given extras or arguments.

Requirements

It has been tested on Android SDK Level 7 and above, but it might work on lower levels as well. Some class types are not supported on older SDK levels.

Example code

When writing an Activity or Fragment you often have to write code like this to initialize, save and restore the state of the instance:

import android.app.Activity;
import android.content.SharedPreferences;
...


public class DemoActivity extends Activity
{
	/** key for the value in the extras Bundle */
	public static final String EXTRA_VALUE = "com.example.EXTRA_VALUE";

	private static final String KEY_INT1 = "int1";
	private static final String KEY_STRING1 = "string1";
	private static final String KEY_BUNDLE1 = "bundle1";


	private static final String PREF_KEY_STRING1 = "string1pref";

	private int mInt1;
	private String mString1 = "initial value";
	private Bundle mBundle1;
	private String mValue;


	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		mValue = getIntent().getStringExtra(EXTRA_VALUE);

		if (savedInstanceState != null)
		{
			mInt1 = savedInstanceState.getInt(KEY_INT1);
			mString1 = savedInstanceState.getString(KEY_STRING1);
			mBundle1 = savedInstanceState.getBundle(KEY_BUNDLE1);
		}
		else
		{
			SharedPreferences prefs = getSharedPreferences(getPackageName() + ".sharedPrefences", 0);
			mString1 = prefs.getString(PREF_KEY_STRING1, mString1);
		}

		...
	}

	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(Bundle outState)
		outState.putInt(KEY_INT1, mInt1);
		outState.putString(KEY_STRING1, mString1);
		outState.putBundle(KEY_BUNDLE1, mBundle1);
	}


	@Override
	protected void onPause()
	{
		super.onPause();
		if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB)
		{
			SharedPreferences.Editor editor = mPrefs.edit();
			editor.putString(PREF_KEY_STRING1, string1);
			if (VERSION.SDK_INT > VERSION_CODES.FROYO)
			{
				editor.apply();
			}
			else
			{
				editor.commit();
			}
		}
	}

	@Override
	protected void onStop()
	{
		super.onStop();
		if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
		{
			SharedPreferences.Editor editor = mPrefs.edit();
			editor.putString(PREF_KEY_STRING1, string1);
			editor.apply();
		}
	}


	...
}

Using this library the same is achieved by

import android.app.Activity;
...

public class DemoActivity extends org.dmfs.android.retentionmagic.Activity
{
	/** key for the value in the extras Bundle */
	public static final String EXTRA_VALUE = "com.example.EXTRA_VALUE";

	@Retain
	private int mInt1;

	@Retain(permanent = true)
	private String mString1 = "initial Value";

	@Retain
	private Bundle mBundle1;

	@Parameter(key = EXTRA_VALUE) // initialize mValue with the value EXTRA_VALUE in the extras Bundle
	private String mValue;

	...
}

If for some reason you can't inherit from the Activity and Fragment classes in org.dmfs.android.retentionmagic you still can do this:

import android.app.Activity;
import org.dmfs.android.retentionmagic.RetentionMagic;
...

public class DemoActivity extends Activity
{
	/** key for the value in the extras Bundle */
	public static final String EXTRA_VALUE = "com.example.EXTRA_VALUE";

	@Retain
	private int mInt1;

	@Retain(permanent = true)
	private String mString1 = "initial Value";

	@Retain
	private Bundle mBundle1;

	@Parameter(key = EXTRA_VALUE)
	private String mValue;

	private SharedPreferences mPrefs;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		mPrefs = getSharedPreferences(getPackageName() + ".sharedPrefences", 0);

		RetentionMagic.init(this, getIntent().getExtras());

		if (savedInstanceState == null)
		{
			RetentionMagic.init(this, mPrefs);
		}
		else
		{
			RetentionMagic.restore(this, savedInstanceState);
		}
		...
	}

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

	@Override
	protected void onPause()
	{
		super.onPause();
		if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB)
		{
			RetentionMagic.persist(this, mPrefs);
		}
	}

	@Override
	protected void onStop()
	{
		super.onStop();
		if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
		{
			RetentionMagic.persist(this, mPrefs);
		}
	}

	...
}

CAVEATS

When using a tool like ProGuard you'll have to take special care, since it may remove or rename fields and annotations.

It's recommended to add the following lines to your proguard.cfg (adjust to your needs):

# this is required to keep the Annotations
-keepattributes *Annotation*

# keep relevant members in Activities
-keepclassmembers class * extends android.app.Activity 
{
	# optional, keep TAG fields if you use them for automatic namespacing
	# you don't need this line if don't use the "permanent" feature or
	# if you set the namespace like so:
	# @Retain(permanent = true, classNS = TAG)
	# or
	# @Retain(permanent = true, classNS = "someNameSpace")
	java.lang.String TAG;

	# optional, keep names of retained fields
	# you don't need this line if don't use the "permanent" feature or
	# if you set the key manually like in @Retain(key = "someKey");
	@org.dmfs.android.retentionmagic.annotations.* <fields>;

	# optional, keep names of fields considered for the instance names space, adjust to your needs
	# you don't need this line if don't use the "permanent" feature or
	# if you don't use per instance fields 
	private java.lang.String instanceTag;
}

# same for Fragments
-keepclassmembers class * extends android.app.Fragment 
{
	java.lang.String TAG;
	@org.dmfs.android.retentionmagic.annotations.* <fields>;
	private java.lang.String instanceTag;
}

# same for support library Fragments
-keepclassmembers class * extends android.support.v4.app.Fragment 
{
	java.lang.String TAG;
	@org.dmfs.android.retentionmagic.annotations.* <fields>;
	private java.lang.String instanceTag;
}

TODO

License

Copyright (c) Marten Gajda 2013, licensed under Apache 2 (see LICENSE).

<pre> Copyright (C) 2013 Marten Gajda <marten@dmfs.org> 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. </pre>