├── .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 |
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 | *