├── .gitignore ├── LICENSE ├── README.md ├── androidautowire.jar └── source ├── AndroidManifest.xml ├── ic_launcher-web.png ├── proguard-project.txt ├── project.properties └── src └── com └── cardinalsolutions └── android └── arch └── autowire ├── AndroidAutowire.java ├── AndroidAutowireException.java ├── AndroidLayout.java ├── AndroidView.java ├── BaseAutowireActivity.java └── SaveInstance.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.rar 19 | *.tar 20 | *.zip 21 | 22 | # Logs and databases # 23 | ###################### 24 | *.log 25 | *.sql 26 | *.sqlite 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | .DS_Store? 32 | ._* 33 | .Spotlight-V100 34 | .Trashes 35 | Icon? 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # IDE files # 40 | ############# 41 | *xcuserdata* 42 | source/gen/** 43 | source/out/** 44 | source/bin/** 45 | .classpath 46 | .project 47 | .settings 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) 2013 Cardinal Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Autowire 2 | ====== 3 | 4 | Using Java Annotations and Reflection, this library will allow you to replace some of annoying boilerplate setup from your Activities, Fragments, and Views with an annotation based approach. 5 | 6 | This repository is referenced in the blog post: http://www.cardinalsolutions.com/cardinal/blog/mobile/2014/01/dealing_with_android.html 7 | 8 | Features 9 | ------ 10 | 11 | * Supports Inheritance of Activities. You can inherit views from parent Activities, and every view will be picked up and wired in 12 | * As it uses reflection, it will work with private variables 13 | * Comes with several out of the box ways of specifying IDs allowing for flexibility in naming IDs and implementing the annotations 14 | * Provides an optional required field in the annotation, so if an ID is not found, the variable will be skipped without an Exception being thrown 15 | * Support Annotations for Layout as well as Views 16 | * Support an Annotation based approach for saving instance state. This also allows for inheritance. 17 | * Can be adapted to work with Fragments as well as Activities 18 | * Can be adapted to work with CustomViews 19 | 20 | 21 | The Android Way 22 | --------- 23 | 24 | Here are some Examples of Android Boilerplate code that we can make more clear, readable, and easier to use with Annotations. 25 | 26 | ### findViewById() 27 | 28 | One particularly jarring example of Android boilerplate code is the ```findViewById()``` method. Every time you want to access an Android view defined in your XML, you need to use this method, often with a typecast. For large Activities with many views, this can add a lot of code that does nothing but pull variables out of the xml. 29 | 30 | ```java 31 | public class MainActivity extends BaseActivity{ 32 | 33 | private ImageView logo; 34 | 35 | @Override 36 | public void onCreate(Bundle savedInstanceState){ 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.main); 39 | 40 | logo = (ImageView) findViewById(R.id.logo); 41 | } 42 | } 43 | ``` 44 | 45 | ### setContentView() 46 | 47 | In the code example above, we have the ```setContentView(R.layout.main)``` line. You need something like this in every Activity class, with the sole purpose of inflating your layout. It's not a big deal, but it is one extra step you have to go through when creating your Activity classes because it has to be put in exactly the right spot. It needs to be in ```onCreate()``` before any ```findViewById()``` call. 48 | 49 | ### Saving Instance State 50 | 51 | A quirk of how the Android operating systems works, Activities can be destroyed at almost anytime to make room for other OS processes. They are also destroyed and re-created on rotation. The developer is in charge of saving the Activity's state, making sure the Activity comes back exactly the same way before it was destroyed. 52 | 53 | In the Android way, instance variables that you have to manually store are put into a ```Bundle``` in the ```onSaveInstanceState``` method. Then they must be pulled out again in the ```onCreate()``` method. 54 | 55 | ```java 56 | public class MainActivity extends BaseActivity{ 57 | 58 | private static final String SOME_STATE_KEY = "some_state_key"; 59 | private int someState; 60 | 61 | @Override 62 | public void onCreate(Bundle savedInstanceState){ 63 | super.onCreate(savedInstanceState); 64 | setContentView(R.layout.main); 65 | 66 | if(savedInstanceState != null){ 67 | someState = savedInstanceState.getInt(SOME_STATE_KEY); 68 | } 69 | } 70 | 71 | @Override 72 | protected void onSaveInstanceState(Bundle outState){ 73 | super.onSaveInstanceState(outState); 74 | outState.putInt(SOME_STATE_KEY, someState); 75 | } 76 | } 77 | ``` 78 | 79 | With AndroidAutowire 80 | ------------ 81 | 82 | 83 | This library will help streamline this process into a more readable format using annotations and reflection. 84 | 85 | 86 | ### findViewById() 87 | By annotating a class variable for the View with the ```@AndroidView``` custom annotation, you enable the reflection code to pull the view out of the xml. The variable name will be the view id, or alternatively, the view id can be specified in the annotation. The annotation processing occurs in an overridden method of ```setContentView(int layoutResID)``` in the Activity’s base class. 88 | 89 | 90 | #### MainActivity Class 91 | 92 | ```java 93 | public class MainActivity extends BaseActivity{ 94 | 95 | @AndroidView 96 | private ImageView logo; 97 | 98 | @Override 99 | public void onCreate(Bundle savedInstanceState){ 100 | super.onCreate(savedInstanceState); 101 | setContentView(R.layout.main); 102 | } 103 | } 104 | ``` 105 | 106 | #### BaseActivity class 107 | 108 | ```java 109 | public class BaseActivity extends Activity { 110 | 111 | @Override 112 | public void setContentView(int layoutResID) { 113 | super.setContentView(layoutResID); 114 | AndroidAutowire.autowire(this, BaseActivity.class); 115 | } 116 | } 117 | ``` 118 | 119 | ### setContentView() 120 | 121 | Specifying the layout resource in the onCreate is not difficult, but it can create problems if you forget add the method call, or if you do it out of order. Instead, use an annotation: 122 | 123 | #### MainActivity Class 124 | 125 | ```java 126 | @AndroidLayout(R.layout.main) 127 | public class MainActivity extends BaseActivity{ 128 | 129 | @Override 130 | public void onCreate(Bundle savedInstanceState){ 131 | super.onCreate(savedInstanceState); 132 | } 133 | } 134 | ``` 135 | 136 | #### BaseActivity class 137 | 138 | ```java 139 | public class BaseActivity extends Activity { 140 | 141 | @Override 142 | protected void onCreate(Bundle savedInstanceState){ 143 | super.onCreate(savedInstanceState); 144 | int layoutId = AndroidAutowire.getLayoutResourceByAnnotation(this, this, BaseActivity.class); 145 | //If this activity is not annotated with AndroidLayout, do nothing 146 | if(layoutId == 0){ 147 | return; 148 | } 149 | setContentView(layoutId); 150 | } 151 | 152 | @Override 153 | public void setContentView(int layoutResID) { 154 | super.setContentView(layoutResID); 155 | AndroidAutowire.autowire(this, BaseActivity.class); 156 | } 157 | } 158 | ``` 159 | 160 | ### Saving Instance State 161 | 162 | All of the reading/writing with the Bundle can be done with reflection. Simply annotate the instance variable you want to save/load, and the AndroidAutowire library will do the work for you. 163 | 164 | #### MainActivity Class 165 | 166 | ```java 167 | @AndroidLayout(R.layout.main) 168 | public class MainActivity extends BaseActivity{ 169 | @SaveInstance 170 | private int someState; 171 | 172 | @Override 173 | public void onCreate(Bundle savedInstanceState){ 174 | super.onCreate(savedInstanceState); 175 | } 176 | } 177 | ``` 178 | 179 | #### BaseActivity Class 180 | 181 | ```java 182 | public class BaseActivity extends Activity { 183 | 184 | @Override 185 | protected void onCreate(Bundle savedInstanceState){ 186 | super.onCreate(savedInstanceState); 187 | AndroidAutowire.loadFieldsFromBundle(savedInstanceState, this, BaseActivity.class); 188 | } 189 | 190 | @Override 191 | protected void onSaveInstanceState(Bundle outState){ 192 | super.onSaveInstanceState(outState); 193 | AndroidAutowire.saveFieldsToBundle(outState, this, BaseActivity.class); 194 | } 195 | } 196 | ``` 197 | 198 | Configuration 199 | ------- 200 | 201 | Simply include the jar in your classpath. The process for including the AndroidAutowire library will be IDE specific, but once the library is included in the project, the methods will all be there for you to use. 202 | 203 | You can create your own BaseActivity using the process above, or you can use a provided BaseActivity called ```BaseAutowireActivity```. That will provide support for all features given above, as well as including a new abstract method that acts as a callback once the autowiring is complete. If you use features like ```BaseAutowireActivity``` and ```@AndroidLayout``` it may not even be necessary to override ```onCreate``` in your Activity class. 204 | 205 | Fragments 206 | --------- 207 | 208 | Much like Activities, Fragments have layouts, state to be saved, and views to be autowired. But the process for setting up a Fragment is different than an Activity. None the less, AndroidAutowire provides the ability to do all of this using Annotations as well by providing a new method: ```AndroidAutowire.autowireFragment()```. 209 | 210 | Here is an Example base class for Fragments: 211 | 212 | ```java 213 | public abstract class BaseFragment extends Fragment { 214 | 215 | protected View contentView; 216 | 217 | @Override 218 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 219 | //Load any annotated fields from the bundle 220 | AndroidAutowire.loadFieldsFromBundle(savedInstanceState, this, BaseFragment.class); 221 | 222 | //Load the content view using the AndroidLayout annotation 223 | contentView = super.onCreateView(inflater, container, savedInstanceState); 224 | if (contentView == null) { 225 | int layoutResource = AndroidAutowire.getLayoutResourceByAnnotation(this, getActivity(), BaseFragment.class); 226 | if(layoutResource == 0){ 227 | return null; 228 | } 229 | contentView = inflater.inflate(layoutResource, container, false); 230 | } 231 | //If we have the content view, autowire the Fragment's views 232 | autowireViews(contentView); 233 | //Callback for when autowiring is complete 234 | afterAutowire(savedInstanceState); 235 | return contentView; 236 | } 237 | 238 | protected void autowireViews(View contentView){ 239 | AndroidAutowire.autowireFragment(this, BaseFragment.class, contentView, getActivity()); 240 | } 241 | 242 | @Override 243 | public void onSaveInstanceState(Bundle outState){ 244 | super.onSaveInstanceState(outState); 245 | AndroidAutowire.saveFieldsToBundle(outState, this, BaseFragment.class); 246 | } 247 | 248 | protected abstract void afterAutowire(Bundle savedInstanceState); 249 | } 250 | ``` 251 | 252 | Unfortunately, do to fragmentation between the Android Core API and the Support Library, this class is not included with the Jar (whereas BaseAutowireActivity is included). 253 | 254 | Custom Views 255 | -------------- 256 | 257 | If you are writing a non-trivial Android App, chances are you will need to make your own custom Views at some point. These views may have subviews. Again, rather than being forced to use ```findViewById()```, we can use AndroidAutowire and Annotations with the ```AndroidAutowire.autowireView()``` method. 258 | 259 | ```java 260 | public class CustomView extends RelativeLayout { 261 | 262 | @AndroidView(R.id.title) 263 | private TextView title; 264 | 265 | @AndroidView(R.id.icon) 266 | private ImageView icon; 267 | 268 | public CustomView(Context context, AttributeSet attrs, int defStyle) { 269 | super(context, attrs, defStyle); 270 | LayoutInflater inflater = LayoutInflater.from(context); 271 | inflater.inflate(R.layout.custome_view, this); 272 | AndroidAutowire.autowireView(this, CustomView.class, context); 273 | } 274 | } 275 | ``` 276 | 277 | Comparison to Other Libraries 278 | ------- 279 | 280 | There are some other open source libraries that accomplish something similar to what Android Autowire hopes to provide 281 | 282 | **RoboGuice** is a dependency injection library that can inject views in much the same way. However, you must extend the Robo* classes, and there may be performance issues. (https://github.com/roboguice/roboguice/wiki) 283 | 284 | **Android Annotations** can wire in views by annotation, but the approach they take is quite different. Android Annotations requires you to use an extra compile step, creating generated Activity classes that must be referenced in the AndroidManifest.xml. As this approach will create subclasses of your Activity, you cannot use this on private variables. Additionally, there is much more configuration and initial setup. (https://github.com/excilys/androidannotations/wiki) 285 | 286 | **Butter Knife** does the same compile time annotation approach as Android Annotations, but instead of generating a new Activity, they generate a class to pass your activity into. This way, you don't have to deal with generated sub classes, but you still get some of the heavy hitting features like onClick Listeners. (http://jakewharton.github.io/butterknife/) 287 | 288 | The real advantage to this "Android Autowire" library is ease of use. There is minimal configuration in just about every IDE, and little overhead, allowing you to quickly start using these annotations in your new or existing project. Instead of providing a full feature set, this library concentrates only on limited number of features, such as views, layouts and Bundle resources, allowing it to fill the gap while still being lightweight. 289 | 290 | 291 | Performance 292 | ------------ 293 | 294 | The more you use the library, the more you want to keep an eye out for performance hits. Most of this reflection code is going to be done on the main thread, and that is always a risk. However, I have been using all of the features, from loading Serializable objects from the Bundle to finding views inside of Fragments, and I have not noticed any type of performance decrease. In fact, even some very complex Activities have made full use of this reflection code without any issue. My biggest concern would be older devices that I have not tested on, devices that may be slow to begin with. 295 | 296 | To illustrate this, I did some benchmarks on an HTC Nexus One running 2.3.4 Gingerbread. The application I used is a fairly complex production Android App. The time is the total time for the reflection to complete, not including the time it takes for the system to start the Activity/Fragment and not including any time to inflate XML layouts. 297 | 298 | * Activity wiht 1 Autowired View, 0 Save Instance variables, and layout: 0.7ms 299 | * Activity with 15 Autowired Views, 2 Save Instance variables, and layout: 4.9ms 300 | * Fragment with 1 Autowired View, 0 Save Instance variables, and layout: 2.0ms 301 | * Fragment with 3 Autowired Views, 4 Save Instance variables, and layout: 6.5ms 302 | * Fragment with 18 Autowired Views, 6 Save Instance variables, layout, and inheritance: 44.6ms 303 | 304 | This is hardly a scientific endeavour, but it should give some pretty clear direction as to what the performance impact of using this library would be. Using this library with API level 10 and up seems to be fairly safe, as the most complicated bit of reflection using a Fragment with many views and instance state was still completed in less than 50 milliseconds. 305 | 306 | ## Author / License 307 | 308 | Copyright Cardinal Solutions 2015. Licensed under the MIT license. 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /androidautowire.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/AndroidAutowire/cebb007563fbca4d6089f920e2db9ba2d1da1e9b/androidautowire.jar -------------------------------------------------------------------------------- /source/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /source/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/AndroidAutowire/cebb007563fbca4d6089f920e2db9ba2d1da1e9b/source/ic_launcher-web.png -------------------------------------------------------------------------------- /source/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /source/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=true 16 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/AndroidAutowire.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.Field; 5 | 6 | import android.app.Activity; 7 | import android.content.Context; 8 | import android.os.Bundle; 9 | import android.os.Parcelable; 10 | import android.util.Log; 11 | import android.view.View; 12 | 13 | /** 14 | * Annotation handler class that will wire in the android views at runtime. 15 | *

16 | * This class will look for the {@code @AndroidView} annotation in the activity class. 17 | *

18 | * Example Usage: 19 | *

20 | * Base Class for Activity 21 | *
 22 |  * public class BaseActivity extends Activity {
 23 |  * 	...
 24 |  * 	{@code @Override}
 25 |  *	public void setContentView(int layoutResID){
 26 |  *		super.setContentView(layoutResID);
 27 |  *		AndroidAutowire.autowire(this, BaseActivity.class);
 28 |  *	}
 29 |  * }
 30 |  * 
31 | * Activity Class 32 | *
 33 |  * public class MainActivity extends BaseActivity{
 34 |  * 	{@code @AndroidView}
 35 |  * 	private Button main_button;
 36 |  * 
 37 |  * 	{@code @AndroidView(id="edit_text_field")}
 38 |  * 	private EditText editText;
 39 |  * 
 40 |  * 	{@code @AndroidView(value=R.id.img_logo, required=false)}
 41 |  * 	private ImageView logo;
 42 |  * 
 43 |  * 	{@code @Override}
 44 |  *	protected void onCreate(Bundle savedInstanceState) {		
 45 |  *		super.onCreate(savedInstanceState);
 46 |  *		setContentView(R.layout.activity_main)
 47 |  * 	}
 48 |  * }
 49 |  * 
50 | * The layout xml : 51 | *
 52 |  *  
 53 |  * <EditText
 54 |  *    android:id="@+id/edit_text_field"
 55 |  *    android:layout_width="fill_parent" 
 56 |  *    android:layout_height="wrap_content"
 57 |  *    android:inputType="textUri" 
 58 |  *    />
 59 |  *  
 60 |  * <Button  
 61 |  *    android:id="@+id/main_button"
 62 |  *    android:layout_width="fill_parent" 
 63 |  *    android:layout_height="wrap_content" 
 64 |  *    android:text="@string/test"
 65 |  *    />
 66 |  *    
 67 |  * <ImageView  
 68 |  *    android:id="@+id/img_logo"
 69 |  *    android:layout_width="fill_parent" 
 70 |  *    android:layout_height="wrap_content" 
 71 |  *    android:text="@string/hello"
 72 |  *   />
 73 |  * 
74 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 75 | * Copyright (c) 2013 76 | */ 77 | public class AndroidAutowire { 78 | 79 | /** 80 | * Perform the wiring of the Android View using the {@link AndroidView} annotation. 81 | *

82 | * Usage:

83 | * Annotation all view fields in the activity to be autowired. Use {@code @AndroidView}.
84 | * If you do not specify the {@code id} or the {@code value} parameters in the annotation, the name of the variable will be used as the id. 85 | *
86 | * You may specify whether or not the field is required (true by default). 87 | *

88 | * After the call to {@code setContentView(layoutResID)} in the onCreate() method, you will call this 89 | * {@code autowire(Activity thisClass, Class baseClass)} method. 90 | *
91 | * The first parameter is the Activity class being loaded.
92 | * The second parameter is the class of the base activity (if applicable). 93 | * 94 | * @param thisClass The activity being created. 95 | * @param baseClass The Base activity. If there is inheritance in the activities, this is the highest level, the base activity, 96 | * but not {@link Activity}.

All views annotated with {@code @AndroidView} will be autowired in all Activity classes in the 97 | * inheritance structure, from thisClass to baseClass inclusive. baseClass should not be {@link Activity} because no fields 98 | * in {@link Activity} will need to be autowired.

If there is no parent class for your activity, use thisClass.class as baseClass. 99 | * @throws AndroidAutowireException Indicates that there was an issue autowiring a view to an annotated field. Will not be thrown if required=false 100 | * on the {@link AndroidView} annotation. 101 | */ 102 | public static void autowire(Activity thisClass, Class baseClass) throws AndroidAutowireException{ 103 | Class clazz = thisClass.getClass(); 104 | autowireViewsForClass(thisClass, clazz); 105 | //Do this for all classes in the inheritance chain, until we get to the base class 106 | while(baseClass.isAssignableFrom(clazz.getSuperclass())){ 107 | clazz = clazz.getSuperclass(); 108 | autowireViewsForClass(thisClass, clazz); 109 | } 110 | } 111 | 112 | /** 113 | * Gets the layout resource id based on the Activity or Fragment. This class (or a parent class) must be annotated with the 114 | * {@link AndroidLayout} annotation, or a valid layout id will not be returned. This will work with Activity, 115 | * Android core Fragment, and Android Support Library Fragment. 116 | * @param thisClass Annotated class with the layout. This is generally the Activity or Fragment class 117 | * @param thisActivity Context for the Activity or Fragment that is being laid out. 118 | * @param the base activity/fragment allowing inheritance of layout 119 | * @return layout id for the layout of this activity/fragment. If no layout resource is found, or if there is 120 | * no annotation for AndroidLayout present, then 0 is returned. 121 | */ 122 | public static int getLayoutResourceByAnnotation(Object thisClass, Context thisActivity, Class baseClass) { 123 | AndroidLayout layoutAnnotation = thisClass.getClass().getAnnotation(AndroidLayout.class); 124 | Class clazz = thisClass.getClass(); 125 | while(layoutAnnotation == null && baseClass.isAssignableFrom(clazz.getSuperclass())){ 126 | clazz = clazz.getSuperclass(); 127 | layoutAnnotation = clazz.getAnnotation(AndroidLayout.class); 128 | } 129 | if(layoutAnnotation == null){ 130 | return 0; 131 | } 132 | if(layoutAnnotation.value() != 0){ 133 | return layoutAnnotation.value(); 134 | } 135 | String className = thisClass.getClass().getSimpleName(); 136 | int layoutId = thisActivity.getResources().getIdentifier(className, "layout", thisActivity.getPackageName()); 137 | return layoutId; 138 | } 139 | 140 | /** 141 | * Find all the fields (class variables) in the Activity/Fragment, and the base classes, that are annotated 142 | * with the {@link SaveInstance} annotation. These will be put in the Bundle object. 143 | * @param bundle {@link Bundle} to save the Activity/Fragment's state 144 | * @param thisClass Class with values being saved 145 | * @param baseClass Bass class of the Activity or Fragment 146 | */ 147 | public static void saveFieldsToBundle(Bundle bundle, Object thisClass, Class baseClass){ 148 | Class clazz = thisClass.getClass(); 149 | while(baseClass.isAssignableFrom(clazz)){ 150 | String className = clazz.getName(); 151 | for(Field field : clazz.getDeclaredFields()){ 152 | if(field.isAnnotationPresent(SaveInstance.class)){ 153 | String name = field.getName(); 154 | field.setAccessible(true); 155 | try { 156 | Object value = field.get(thisClass); 157 | String key = className + name; 158 | if(value instanceof Serializable){ 159 | bundle.putSerializable(key, (Serializable) value); 160 | }else if(value instanceof Parcelable){ 161 | bundle.putParcelable(key, (Parcelable) value); 162 | } 163 | } 164 | catch (Exception e){ 165 | //Could not put this field in the bundle. 166 | Log.w("AndroidAutowire", "The field \"" + name + "\" was not added to the bundle"); 167 | } 168 | } 169 | } 170 | clazz = clazz.getSuperclass(); 171 | } 172 | } 173 | 174 | /** 175 | * Look through the Activity/Fragment and the Base Classes. Find all fields (member variables) annotated 176 | * with the {@link SaveInstance} annotation. Get the saved value for these fields from the Bundle, and 177 | * load the value into the field. 178 | * @param bundle {@link Bundle} with the Activity/Fragment's saved state. 179 | * @param thisClass Activity/Fragment being re-loaded 180 | * @param baseClass Base class of the Activity/Fragment 181 | */ 182 | public static void loadFieldsFromBundle(Bundle bundle, Object thisClass, Class baseClass){ 183 | if(bundle == null){ 184 | return; 185 | } 186 | Class clazz = thisClass.getClass(); 187 | while(baseClass.isAssignableFrom(clazz)){ 188 | for(Field field : clazz.getDeclaredFields()){ 189 | if(field.isAnnotationPresent(SaveInstance.class)){ 190 | field.setAccessible(true); 191 | try { 192 | Object fieldVal = bundle.get(clazz.getName() + field.getName()); 193 | if(fieldVal != null){ 194 | field.set(thisClass, fieldVal); 195 | } 196 | } catch (Exception e){ 197 | //Could not get this field from the bundle. 198 | Log.w("AndroidAutowire", "The field \"" + field.getName() + "\" was not retrieved from the bundle"); 199 | } 200 | } 201 | } 202 | clazz = clazz.getSuperclass(); 203 | } 204 | } 205 | 206 | /** 207 | * Autowire views for a fragment. This method works in as similar way to {@code autowire(Activity thisClass, Class baseClass)} 208 | * but for a Fragment instead of Activity. This will work with both an Android Fragment a Support Library Fragment. 209 | * 210 | * @param thisClass This fragment class. The type is Object to work around android backwards compatibility 211 | * with the API, as Fragment can come from the core API or from the support library. 212 | * @param baseClass The Fragment's base class. Allows inherited views. 213 | * @param contentView The Fragment's main content view 214 | * @param context Context for the fragment's activity. Generally, this should be {@code getActivity()} 215 | * @throws AndroidAutowireException Indicates that there was an issue autowiring a view to an annotated field. Will not be thrown if required=false 216 | * on the {@link AndroidView} annotation. 217 | */ 218 | public static void autowireFragment(Object thisClass, Class baseClass, View contentView, Context context) throws AndroidAutowireException{ 219 | Class clazz = thisClass.getClass(); 220 | autowireViewsForFragment(thisClass, clazz, contentView, context); 221 | //Do this for all classes in the inheritance chain, until we get to this class 222 | while(baseClass.isAssignableFrom(clazz.getSuperclass())){ 223 | clazz = clazz.getSuperclass(); 224 | autowireViewsForFragment(thisClass, clazz, contentView, context); 225 | } 226 | } 227 | 228 | /** 229 | * Autowire a custom view class. Load the sub views for the custom view using the {@link AndroidView} annotation. 230 | * Inheritance structures are supported. 231 | * @param thisClass This Android View class to be autowired. 232 | * @param baseClass The views parent, allowing inherited views to be autowired, if necessary. If there is no custom 233 | * base class, just use this custom view's class. 234 | * @param context Context 235 | * @throws AndroidAutowireException Indicates that there was an issue autowiring a view to an annotated field. 236 | * Will not be thrown if required=false on the {@link AndroidView} annotation. 237 | */ 238 | public static void autowireView(View thisClass, Class baseClass, Context context) throws AndroidAutowireException{ 239 | autowireFragment(thisClass, baseClass, thisClass, context); 240 | } 241 | 242 | private static void autowireViewsForFragment(Object thisFragment, Class clazz, View contentView, Context context){ 243 | for (Field field : clazz.getDeclaredFields()){ 244 | if(!field.isAnnotationPresent(AndroidView.class)){ 245 | continue; 246 | } 247 | if(!View.class.isAssignableFrom(field.getType())){ 248 | continue; 249 | } 250 | AndroidView androidView = field.getAnnotation(AndroidView.class); 251 | int resId = androidView.value(); 252 | if(resId == 0){ 253 | String viewId = androidView.id(); 254 | if(androidView.id().equals("")){ 255 | viewId = field.getName(); 256 | } 257 | resId = context.getResources().getIdentifier(viewId, "id", context.getPackageName()); 258 | } 259 | try { 260 | View view = contentView.findViewById(resId); 261 | if(view == null){ 262 | if(!androidView.required()){ 263 | continue; 264 | }else{ 265 | throw new AndroidAutowireException("No view resource with the id of " + resId + " found. " 266 | +" The required field " + field.getName() + " could not be autowired" ); 267 | } 268 | } 269 | field.setAccessible(true); 270 | field.set(thisFragment,view); 271 | } catch (Exception e){ 272 | if(e instanceof AndroidAutowireException){ 273 | throw (AndroidAutowireException) e; 274 | } 275 | throw new AndroidAutowireException("Cound not Autowire AndroidView: " + field.getName() + ". " + e.getMessage()); 276 | } 277 | } 278 | } 279 | 280 | private static void autowireViewsForClass(Activity thisActivity, Class clazz){ 281 | for (Field field : clazz.getDeclaredFields()){ 282 | if(!field.isAnnotationPresent(AndroidView.class)){ 283 | continue; 284 | } 285 | if(!View.class.isAssignableFrom(field.getType())){ 286 | continue; 287 | } 288 | AndroidView androidView = field.getAnnotation(AndroidView.class); 289 | int resId = androidView.value(); 290 | if(resId == 0){ 291 | String viewId = androidView.id(); 292 | if(androidView.id().equals("")){ 293 | viewId = field.getName(); 294 | } 295 | resId = thisActivity.getResources().getIdentifier(viewId, "id", thisActivity.getPackageName()); 296 | } 297 | try { 298 | View view = thisActivity.findViewById(resId); 299 | if(view == null){ 300 | if(!androidView.required()){ 301 | continue; 302 | }else{ 303 | throw new AndroidAutowireException("No view resource with the id of " + resId + " found. " 304 | +" The required field " + field.getName() + " could not be autowired" ); 305 | } 306 | } 307 | field.setAccessible(true); 308 | field.set(thisActivity,view); 309 | } catch (Exception e){ 310 | if(e instanceof AndroidAutowireException){ 311 | throw (AndroidAutowireException) e; 312 | } 313 | throw new AndroidAutowireException("Cound not Autowire AndroidView: " + field.getName() + ". " + e.getMessage()); 314 | } 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/AndroidAutowireException.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | /** 4 | * Exception dealing with an error while autowiring an android view 5 | * 6 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 7 | * Copyright (c) 2013 8 | */ 9 | public class AndroidAutowireException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = 7445208526652970323L; 12 | 13 | public AndroidAutowireException(String message){ 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/AndroidLayout.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotation to denote the layout resource for an activity class that can be found by layout id at runtime. 11 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 12 | * Copyright (c) 2013 13 | */ 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.TYPE}) 17 | public @interface AndroidLayout { 18 | /** 19 | * The value expects the layout resource id to be used for the Activity's layout. 20 | * This is the layout id specified in {@code setConentView()}, which will not be 21 | * needed in your activity if this annotation is used. 22 | *

23 | * If the value is not specified, the annotation will assume the resource name is 24 | * {@code R.layout.ThisActivityName} (assuming the annotated class has the class name: 25 | * {@code ThisActivityName}, and an exception will be thrown if the layout resource cannot be found. 26 | * @return value 27 | */ 28 | int value() default 0; 29 | } 30 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/AndroidView.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotation to denote a field variable in an activity class that can be found by id at runtime. 11 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 12 | * Copyright (c) 2013 13 | */ 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.FIELD}) 17 | public @interface AndroidView { 18 | /** 19 | * You may optionally specify the id of the android view. 20 | * If id is not set, assume the name of the field is the id. 21 | * If the {@code value} is set, this field will be ignored 22 | * @return id 23 | */ 24 | String id() default ""; 25 | 26 | /** 27 | * This View must be autowired. If required is true, then if the field cannot 28 | * be autowired, and exception is thrown. No exception is thrown and the 29 | * autowire will fail silently if required is false. 30 | *

31 | * defaults to {@code true} 32 | * @return 33 | */ 34 | boolean required() default true; 35 | 36 | /** 37 | * Resource ID for the view. Example: {@code R.id.viewname} 38 | *

39 | * You may optionally specify this id of the android view. If the value is set, 40 | * this will be used to autowire the field. If it is not set, the {@code id} variable will 41 | * be used. If the {@code id} is not set, then the name of the field is the id. 42 | *

43 | * Using value is recommended as it is the most efficient way to autowire the view. 44 | * @return 45 | */ 46 | int value() default 0; 47 | } 48 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/BaseAutowireActivity.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | /** 7 | * Provided BaseActivity for use of AndroidAutowire annotations.

8 | * Use of this class means that you do not need to provide your own custom BaseActivity to 9 | * integrate with the AndroidAutowire library. 10 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 11 | * Copyright (c) 2013 12 | */ 13 | public abstract class BaseAutowireActivity extends Activity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState){ 17 | super.onCreate(savedInstanceState); 18 | AndroidAutowire.loadFieldsFromBundle(savedInstanceState, this, BaseAutowireActivity.class); 19 | 20 | int layoutId = AndroidAutowire.getLayoutResourceByAnnotation(this, this, BaseAutowireActivity.class); 21 | //If this activity is not annotated with AndroidLayout, do nothing 22 | if(layoutId == 0){ 23 | return; 24 | } 25 | setContentView(layoutId); 26 | afterAutowire(savedInstanceState); 27 | } 28 | 29 | @Override 30 | public void setContentView(int layoutResID){ 31 | super.setContentView(layoutResID); 32 | //autowire the AndroidView fields 33 | AndroidAutowire.autowire(this, BaseAutowireActivity.class); 34 | } 35 | 36 | @Override 37 | protected void onSaveInstanceState(Bundle outState){ 38 | super.onSaveInstanceState(outState); 39 | AndroidAutowire.saveFieldsToBundle(outState, this, BaseAutowireActivity.class); 40 | } 41 | 42 | /** 43 | * This method will be called after views are autowired by AndroidAutowire 44 | * and after the layout is created. This method will only be called when the 45 | * {@link AndroidLayout} annotation is used to load the layout resource for the Activity. 46 | *

47 | * This method can be used as a substitute for {@code onCreate()}, as actually overriding 48 | * {@code onCreate()} is not necessary when this base class does it for you. Activity set up 49 | * that is usually done in {@code onCreate()} can be done in this method instead. 50 | */ 51 | protected abstract void afterAutowire(Bundle savedInstanceState); 52 | } 53 | -------------------------------------------------------------------------------- /source/src/com/cardinalsolutions/android/arch/autowire/SaveInstance.java: -------------------------------------------------------------------------------- 1 | package com.cardinalsolutions.android.arch.autowire; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotation to denote that a field should be saved into the Bundle {@code onSaveInstanceState()}. 11 | * The actual saving should be done using the {@link AndroidAutowire} library, but that library will 12 | * use this annotation to see what it should save and re-populate from the Bundle in {@code onCreate()} 13 | * @author Jacob Kanipe-Illig (jkanipe-illig@cardinalsolutions.com) 14 | * Copyright (c) 2013 15 | */ 16 | @Documented 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target({ElementType.FIELD}) 19 | public @interface SaveInstance { 20 | 21 | } 22 | --------------------------------------------------------------------------------