A Swiss army Knife is like a Dagger, but although it may be less sharp, it can be more handy
1. Intro
SwissKnife is a multi-purpose library based -only in behavior, not in code- on other libraries such as ButterKnife or AndroidAnnotations.
This library is only compatible with Groovy language as it uses its AST processing to generate the final code. If you don’t use Groovy on your Android projects yet, you can find some info about how to use it here.
Don’t be afraid of using Groovy, Android Studio supports it really well, its syntax is almost the same as Java’s, it’s compatible with Dalvik and ART and can be mixed with your existing Java code. Also, most times turning a Java class into a Groovy one is as easy as changing the file extension from .java to .groovy.
1.1. Annotations
SwissKnife is heavily based on annotations. Most annotations will need only one parameter named value which can be a View id or a list of ids. When it’s the only annotation parameter, the "value=" part can be ignored.
@InjectView(R.id.first_button)
Button firstButton
@InjectView(value=R.id.button)
Button myButton;
// To inject several views, the annotation is @InjectViews, in plural
@InjectViews([R.id.view_1, R.id.view_2])
List<View> myViews;
// This case here is special. If the name of the field is the same as the id,
// the @InjectView annotation doesn't need any parameters
@InjectView()
Button button_1;
Also, you can have several listener injection methods. When the listener is triggered on the provided view, they will execute the code specified in the method. On some annotations, a "method" parameter is also required to specify which method of the listener interface will be triggered.
@OnClick(R.id.first_button)
@Profile
public void clicked() {
firstButton.text = 'I\'ve been clicked! Click me longer!'
profileMethod('param1Value', 'param2Value')
}
@OnClick([R.id.third_button, R.id.fourth_button])
public void onClick() {
toast 'Button three or four has been clicked' show()
}
@OnTextChanged(value = R.id.edit_text, method = OnTextChanged.Method.ON_TEXT_CHANGED)
@Profile
public void onTextChanged(CharSequence sequence) {
writtenTextView.text = sequence
}
1.2. Injecting Views and Listeners
To make those annotations work, both view injection and listener injection, you will need to tell your class to use SwissKnife. You can do it like this:
// Inject an Activity or a View in which "findViewById()" method is accesible
SwissKnife.inject(Object objectInjected);
// If you are using a Fragment or want to inject a View's children into any object, you use this
SwissKnife.inject(Object objectInjected, View viewToInject);
For example, in an Activity, you would do it like this:
SwissKnife.inject(this);
And in a Fragment, it would be:
SwissKnife.inject(this, getView());
You can also inject views and listeners on other objects, like a Controller or a Presenter like this:
SwissKnife.inject(controller, view);
This means that injection can be used on any class and is done on runtime, so you can choose when you want that injection to happen.
1.3. Method declaration
As stated before, annotated method declarations can’t be anything you want, as this library uses reflection to call them and must have a specified argument structure. To see compatible declarations with each annotation, please visit their wiki pages.
Also, as I realized this was difficult to remember, I started working on an IntelliJ Plugin which creates the annotations, the declarations and imports the needed classes for you. You can find it here.
2. How to use
Okay, so you’ve seen this project and wondered WTF is Groovy. You’ve made some research, and seen how awesome it is.
So, do you want to use it on your Android application development? Here’s how.
2.1. Android Studio
First of all, Android Studio has native Groovy support, so here you don’t really have to add anything.
Every Groovy class or Java class which references a Groovy one must be inside of src/main/groovy instead of src/main/java . To add a Groovy class you’ll only need to create your new Class files as a .groovy file, instead of a Java class (you could define your own template on IDEA’s or Android Studio’s Settings > File Code and Templates).
|
2.2. Gradle
In order to make Android Studio compile your Android Groovy code, you will need a plugin and a few changes at your module’s build.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6'
}
}
apply plugin: 'com.android.application'
apply plugin: 'groovyx.grooid.groovy-android'
dependencies{
compile 'org.codehaus.groovy:groovy:2.4.3:grooid'
}
After this little edits, you’re ready to start using Groovy in your Android development.
2.3. References
3. Annotations
3.1. @OnClick
This annotation will trigger an event when a View (for example, a Button) is clicked.
3.1.1. How to use
The method should be this way:
public void onClick()
public void onClick(View v)
3.1.2. Example
@OnClick(R.id.button)
public void onClick() {
Toast.makeText(this, "Button clicked", Toast.LENGTH_SHORT).show();
}
3.2. @OnLongClick
This annotation will trigger an event when a View (for example, a Button) is long-clicked.
3.2.1. How to use
The method should be this way:
public boolean onLongClick() public boolean onLongClick(View v) Notice it must return a boolean
3.2.2. Example
@OnLongClick(R.id.button)
public boolean onLongClick() {
Toast.makeText(this, "Button long clicked", Toast.LENGTH_SHORT).show();
return true;
}
More information about return View.OnLongClickListener |
3.3. @OnItemClick
This annotation will trigger an event when an AbsListView (ListView, GridView…) item is clicked.
3.3.1. How to use
The method should be this way (the method and variable names can be changed):
public void onItemClick(int position)
public void onItemClick(int position, AdapterView adapterView)
public void onItemClick(int position, AdapterView adapterView, View clickedView)
3.3.2. Example
@OnItemClick(R.id.list)
public void onItemClick(int position) {
Toast.makeText(this, "Item $position clicked", Toast.LENGTH_SHORT).show();
}
3.4. @OnItemLongClick
This annotation will trigger an event when an AbsListView (ListView, GridView…) item is long-clicked.
3.4.1. How to use
The method should be this way (the method and variable names can be changed):
public boolean onItemClick(int position)
public boolean onItemClick(int position, AdapterView adapterView)
public boolean onItemClick(int position, AdapterView adapterView, View clickedView)
Notice it must return a boolean
3.4.2. Example
@OnItemLongClick(R.id.list)
public void onItemLongClick(int position) {
Toast.makeText(this, "Item $position long-clicked", Toast.LENGTH_SHORT).show();
}
More information about return View.OnItemLongClick |
3.5. @OnItemSelected
This annotation will trigger an event when an AbsListView (ListView, GridView…) item is selected.
3.5.1. Annotation arguments
@OnItemSelected uses an extra "method" argument to specify the listener method which will call this method.
The annotation must be:
@OnItemSelected(value=R.id.my_view, method=OnItemSelected.Method.ITEM_SELECTED)
// or
@OnItemSelected(value=R.id.my_view, method=OnItemSelected.Method.NOTHING_SELECTED)
3.5.2. How to use
The method should be this way (the method and variable names can be changed):
public void onItemSelected(int position)
public void onItemSelected(int position, AdapterView adapterView)
public void onItemSelected(int position, AdapterView adapterView, View clickedView)
public void onNothingSelected(AdapterView adapterView)
3.5.3. Example
@OnItemSelected(value=R.id.list, method=OnItemSelected.Method.ITEM_SELECTED)
public void onItemSelected(int position) {
Toast.makeText(this, "Item $position selected", Toast.LENGTH_SHORT).show();
}
3.6. @OnChecked
This annotation will trigger an event when a CompoundButton (for example, a RadioButton) is checked.
3.6.1. How to use
The method should be this way:
public boolean onChecked(boolean checked)
public boolean onChecked(CompoundButton button, boolean checked)
3.6.2. Example
@OnChecked(R.id.radio_button)
public public boolean onChecked(CompoundButton button, boolean checked) {
Toast.makeText(this, "Button checked: $checked", Toast.LENGTH_SHORT).show();
return false;
}
3.7. @OnFocusChanged
This annotation will trigger an event when a View receives or loses focus.
3.7.1. How to use
The method should be this way:
public void onFocusChanged(boolean hasFocus)
public void onFocusChanged(View v, boolean hasFocus)
3.7.2. Example
@OnFocusChanged(R.id.my_view)
public void onFocusChanged(boolean hasFocus) {
Toast.makeText(this, "View has focus: $hasFocus", Toast.LENGTH_SHORT).show();
}
3.8. @OnTouch
This annotation will trigger an event when a View is touched.
3.8.1. How to use
The method should be this way:
public boolean onTouch(MotionEvent event)
public boolean onClick(View v, MotionEvent event)
3.8.2. Example
@OnTouch(R.id.button)
public boolean onTouch(MotionEvent event) {
Toast.makeText(this, "Button touched, action: $event.action", Toast.LENGTH_SHORT).show();
return false;
}
3.9. @OnPageChanged
This annotation will trigger an event when a ViewPager scroll changes.
3.9.1. Annotation arguments
@OnPageChanged uses an extra "method" argument to specify the listener method which will call this method.
The annotation must be:
@OnPageChanged(value=R.id.view_pager, method=OnPageChanged.Method.PAGE_SCROLLED)
// or
@OnPageChanged(value=R.id.view_pager, method=OnPageChanged.Method.PAGE_SELECTED)
// or
@OnPageChanged(value=R.id.view_pager, method=OnPageChanged.Method.PAGE_SCROLL_STATE_CHANGED)
3.9.2. How to use
The method should be this way (the method and variable names can be changed):
public void onPageScrolled(int position)
public void onPageScrolled(int position, float offset, int pixelOffset)
public void onPageSelected(int position)
public void onPageScrollStateChanged(int state)
3.9.3. Example
@OnPageChanged(value=R.id.list, method=OnPageChanged.Method.PAGE_SELECTED)
public void onPageSelected(int position) {
Toast.makeText(this, "Current page: $position", Toast.LENGTH_SHORT).show();
}
3.10. @OnTextChanged
This annotation will trigger an event when an EditText’s text is changed.
3.10.1. Annotation arguments
@OnTextChanged uses an extra "method" argument to specify the listener method which will call this method.
The annotation must be:
@OnTextChanged(value=R.id.edit_text, method=OnTextChanged.Method.BEFORE_TEXT_CHANGED)
// or
@OnTextChanged(value=R.id.edit_text, method=OnTextChanged.Method.ON_TEXT_CHANGED)
// or
@OnTextChanged(value=R.id.edit_text, method=OnTextChanged.Method.AFTER_TEXT_CHANGED)
3.10.2. How to use
The method should be this way (the method and variable names can be changed):
BeforeTextChanged
public void beforeTextChanged(CharSequence sequence)
public void beforeTextChanged(TextView textView, CharSequence sequence)
public void beforeTextChanged(CharSequence sequence, int start, int count, int after)
public void beforeTextChanged(TextView textView, CharSequence sequence, int start, int count, int after)
OnTextChanged
public void onTextChanged(CharSequence sequence)
public void onTextChanged(TextView textView, CharSequence sequence)
public void onTextChanged(CharSequence sequence, int start, int before, int count)
public void onTextChanged(TextView textView, CharSequence sequence, int start, int before, int count)
AfterTextChanged
public void afterTextChanged(Editable editable)
3.10.3. Example
@OnTextChanged(value=R.id.edit_text, method=OnTextChanged.Method.ON_TEXT_CHANGED)
public void onTextChanged(CharSequence sequence) {
Toast.makeText(this, "Sentence written: $sequence", Toast.LENGTH_SHORT).show();
}
3.11. @OnEditorAction
This annotation will trigger an event when a TextView receives an action such as pressing ENTER key or a custom action on the keyboard.
3.11.1. How to use
The method should be this way:
public boolean onEditorAction(KeyEvent event)
public boolean onEditorAction(TextView textView, KeyEvent event)
3.11.2. Example
@OnEditorAction(R.id.edit_text)
public boolean onEditorAction(KeyEvent event) {
Toast.makeText(this, "Editor action received", Toast.LENGTH_SHORT).show();
return false;
}
More information TextView.setOnEditorActionListener |
3.12. @OnUIThread
This annotation will execute the code inside the method on the UI Thread using a Handler.
3.12.1. Example
@OnUIThread()
public void getResult(Context context, boolean value){
if(value == true) {
Toast.makeText(context, "Result was: $value", Toast.LENGTH_SHORT).show()
}
}
3.13. @OnBackground
This annotation will execute the code inside the method on a background Thread.
3.13.1. Example
@OnBackground()
public boolean getResult(){...}
3.14. @SaveInstance
This annotation will help you to reduce your persistance code. It has two sepparated behaviours: When it’s applied to Views, and when it’s not.
Instead of having to override the onSaveInstanceState method, add everything to the Bundle, check if the savedInstanceState was null at the onCreate… you can simplify it by only annotating a variable with @SaveState.
3.14.1. How to use
In order to work propperly, you must ensure that:
Your variable can be written to a Bundle object (check Bundle documentation) You must make a call to SwissKnife.restoreState(this, savedInstanceState) in order for the state to be restored.
Keep calm. Don’t worry if you manually override the onSaveInstanceState method with some code, it doesn’t matter, SwissKnife will append any necessary statements to the already existing code |
3.14.2. Example
@SaveInstance
public int myInt
// You can also set a custom tag to your variable
@SaveInstance("MYSTRING")
public String myString
@Override
public void onCreate(Bundle savedInstanceState){
// Your previous code
SwissKnife.restoreState(this, savedInstanceState)
}
In case your variable is not one of the types that can be written to a Bundle, the compilation will fail and show which class is giving the error.
Of course, it supports Parcelable objects (also arrays and ArrayLists).
Make sure you check the sample for more examples.
3.14.3. Views
@SaveInstance can also be used to save the state of views. Though Android usually will do that automatically if the view has an id, you can still use this annotation to force that restoration or use it on views without id.
@SaveInstance
TextView myTextView;
@Override
public void onCreate(Bundle savedInstanceState){
// Your previous code
if(savedInstanceState == null) {
// If the Activity recreates, myTextView will retain the "Hey!" text
myTextView.setText("Hey!")
}
SwissKnife.restoreState(this, savedInstanceState)
}
How is this done? Every view has two methods, onSaveInstanceState() and onRestoreInstanceState(Parcelable) in which they will store their state when called. Usually those methods are called from their containing Activities and Fragments as they are destroyed and recreated when their homonymous methods are called. SwissKnife just forces those calls when needed.
3.15. @Parcelable
@Parcelable
lets you automatically turn any class into a Parcelable object.
It will pick every parcelable or serializable attribute of the annotated class and put it into a Parcel
object, just like you would have to do implementing the Parcelable interface.
public void writeToParcel(Parcel parcel, int flags)
public int describeContents()
public static Parcelable.Creator CREATOR
// Constructor to create a MyClass instance from a Parcel object
public MyClass(Parcel source)
Also, it will create a MyClass.Creator
class which will implement Parcelable.Creator
for you.
3.15.1. How to use
Just add @Parcelable
annotation to the class you want to use as a Parcelable object.
What about circular dependencies? Even having only a few classes, you can end up having circular dependencies, where having two parcelable classes A and B, A contains a B instance and B contains an A instance. If you used @Parcelable with them both your app would go crazy while trying to save them as it will end on a never-ending cycle. What to do here? Use exclude parameter on the annotation. |
3.15.2. Exclude some properties
If there is some attribute that you don’t want parceled, you can explicitly do that using the exclude
parameter on the annotation like this:
@Parcelable(exclude={anotherObject;andThisToo})
class ParcelableClass {
...
ParcelableClass anotherObject
SomeOtherClass andThisToo
3.15.3. Example
@Parcelable
class ParcelableClass {
int id
String name
}
Will generate:
class ParcelableClass implements Parcelable {
...
public ParcelableClass(Parcel source) {
this.id = source.readInt()
this.name = source.readString()
}
public void writeToParcel(Parcel out) {
out.writeInt(id)
out.writeString(name)
}
...
What classes can be parceled?
As previously said, @Parcelable
will only parcel what can be put into a Parcel object. This classes are:
-
String
-
String[]
-
List
-
Map
-
SparseArray
-
Parcelable
-
Parcelable[]
-
Bundle
-
CharSequence
-
Serializable
Of course, any other primitive variable (int, long, float, char, etc.) can be parceled too , as well as arrays (int[], double[]…).
Also, please take in mind that static
variables won’t be parceled as they don’t belong to a single instance.
3.16. @Extra
This annotation will generate hidden code for automatically parsing Intent extras.
3.16.1. How to use
You would put extras on an Intent as you usually do:
Intent goToActivityIntent = new Intent(this, SomeActivity.class)
goToActivityIntent.putExtra("some_key", "my api key")
startActivity(goToActivityIntent)
To parse the "some_key"
extra, you would add this field:
@Extra("some_key")
String myKey
// or
@Extra
String some_key
And then, to load them, you would need to call:
public void onCreate(Bundle savedInstanceState) {
...
SwissKnife.loadExtras(this)
println myKey // It's usable!
}
3.16.2. What can be retrieved as an extra?
Almost everything that can be put into a Bundle, just like with @SaveInstance
:
-
Primitive types (int, double, long, char…).
-
Classes that implement
Parcelable
orSerializable
. -
Arrays an Lists all above.
3.17. @Res annotations
Some annotations have been added to inject resources directly on your groovy classes:
-
@StringRes
: String. -
@IntegerRes
: int, Integer. -
@BooleanRes
: boolean, Boolean. -
@DimenRes
: float, Float. -
@ColorRes
: int, Integer. -
@StringArrayRes
: String[]. -
@IntegerArrayRes
: int[], Integer[]. -
@DrawableRes
: Drawable. -
@AnimationRes
: Animation. -
@ColorStateListRes
: ColorStateList.
These will be linked to their resources when SwissKnife.inject
method is called.
Also, at its current state, resource injection will only work with your project’s R resources, it won’t work with any android.R resources.
3.17.1. Example:
Here’s a quick example:
@AnimationRes(R.anim.fade_in)
Animation fadeInAnimation
@StringArrayRes
String[] menuOptions // will look for R.array.menuOptions
4. DSL Methods - aka method extensions
What we call 'DSL Methods' are actually 'Extension Methods'.
Still no clue of what those are?
Imagine you have GPS coordinates in (lat, lon) format, with lat and lon being doubles. If you want to show that on screen, you’d have to format them as 'String' with a desired number of decimal positions.
So you could write in Java:
public class DoubleUtils {
public static String doubleWithDecimals(double number, int decimals) {
String parseDef = "#."+"0"*decimals
DecimalFormat formatter = new DecimalFormat(parseDef)
return formatter.format(number)
}
}
But why do you have to add a whole class just for that - or even worse, mix it with some random code?
Wouldn’t this method fit perfectly inside the 'Double' class?
Well, in Groovy that can be done and is quite simple. You could write an extension method and just use:
// Actually, Groovy does have ".trunc(int)" extension method for that
String coordStr = coordinate.withDecimals(4)
So if that can be done to data classes, why shouldn’t we use it on Android?
Short answer is: we do.
4.1. Types of DSL methods
The DSL methods written so far can be classified on these big groups:
-
View methods.
-
Context methods.
-
Fragment methods.
-
Bundle methods.
-
Event methods.
-
Misc methods.
4.2. View methods
4.2.1. (View|Fragment|Activity).view(int id, (Optional)Closure modifier)
A short for findViewById(int)
, can be used from Activity, View and Fragment instances.
The optional closure is a way to modify the retrieved item.
Example:
def myTextView = activity.view(R.id.text_view) {
def textView = it as TextView
textView.setText("Hi there!")
}
4.2.2. (View|Fragment|Activity).text(int id, (Optional)Closure modifier)
Retrieves a View, but it is downcasted to TextView
before it’s returned and configured using the optional closure. Can be used from Activity, View and Fragment instances.
Example:
def myTextView = activity.text(R.id.text_view) {
it.setText("Hi there!")
}
4.2.3. (View|Fragment|Activity).editText(int id, (Optional)Closure modifier)
Retrieves a View, but it is downcasted to EditText
before it’s returned and configured using the optional closure. Can be used from Activity, View and Fragment instances.
Example:
def myEditText = activity.editText(R.id.edittext) {
it.setHint("Type your name")
}
4.2.4. (View|Fragment|Activity).button(int id, (Optional)Closure modifier)
Retrieves a View, but it is downcasted to Button
before it’s returned and configured using the optional closure. Can be used from Activity, View and Fragment instances.
Example:
def myButton = activity.button(R.id.button) {
it.setBackgroundColor(color)
}
4.2.5. (View|Fragment|Activity).image(int id, (Optional)Closure modifier)
Retrieves a View, but it is downcasted to ImageView
before it’s returned and configured using the optional closure. Can be used from Activity, View and Fragment instances.
Example:
def myImageVIew = activity.image(R.id.image_view) {
it.setDrawable(R.drawable.photo)
}
4.2.6. (View|Fragment|Activity).asListView(int id, ListAdapter adapter)
Retrieves a ListView
, applying the provided adapter to it. Can be used from Activity, View and Fragment instances.
Example:
def listView = activity.asListView(R.id.list_view, adapter)
4.2.7. (Iterable).asListView(Context context, int id, int rowLayoutId, (Optional) Closure closure)
Retrieves a ListView
and configures it. Can be used from an Iterable instance, such as List, Set, etc. The internal adapter is a GArrayAdapter
which executes the closure
param to configure the rows.
Example:
myListOfStrings.asListView(context, R.id.list_view, R.layout.list_row) { String item, View view ->
view.text(R.id.title) {
it.setText(item)
}
}
4.2.8. (View|Fragment|Activity).asListView(int id, int rowLayoutId, Iterable items, (Optional) Closure closure)
Retrieves a ListView
and configures it. Can be used from Acitvity, Fragment or View instances. The internal adapter is a GArrayAdapter
which executes the closure
param to configure the rows.
Example:
containerView.asListView(R.id.list_view, R.layout.list_row, items) { String item, View view ->
view.text(R.id.title) {
it.setText(item)
}
}
4.2.9. (ListView).onItem(int rowLayoutId, Iterable items, (Optional) Closure closure)
Creates a GArrayAdapter
which will execute the closure to configure the rows. Can be used from a ListView instance.
Example:
listView.onItem(R.layout.list_row, items) { String item, View view ->
view.text(R.id.title) {
it.setText(item)
}
}
4.2.10. (View).show()
Makes view’s visibility VISIBLE
. Can be called from a View instance.
4.2.11. (View).hide()
Makes view’s visibility GONE
. Can be called from a View instance.
4.2.12. (View).visible(boolean visible, (Optional) closure)
Makes view’s visibility VISIBLE
if visible
is true, GONE
if it’s false and applies the provided closure to it. Can be called from a View instance.
4.2.13. (Activity).getRootView()
Gets an Activity's root view. Can be called from an Activity instance.
4.3. Context methods
4.3.1. (Context).log(String message, (Optional)Throwable throwable)
Sends a DEBUG message to logcat including the provided message and optional throwable exception. Can be called from any Object.
4.3.2. (Context).getCompatNotificationManager()
Get a CompatNotificationManager
instance for the current Context
. Can be called from a Context instance.
4.3.3. (Context).showNotification(int notificationId, Notification notification)
Sends the provided notification with its notificationId. Can be called from a Context instance.
4.3.4. (Context).showNotification(int notificationId, Closure notificationSpec)
Sends the a notification with the provided notificationId, the notification will be created on notificationSpec
with `NotificationCompat.Builder’s methods. Can be called from a Context instance.
Example:
context.showNotification(1234) {
contentTitle = "New message!"
ongoing = false
}
4.3.5. (Context).notification(Closure notificationSpec)
Creates a notification from notificationSpec
with `NotificationCompat.Builder’s methods. Can be called from a Context instance.
Example:
def notification = context.notification {
contentTitle = "New message!"
ongoing = false
}
4.3.6. (Context).bigTextStyle(Closure notificationSpec)
Creates a BigTextStyle
notification from notificationSpec
with `NotificationCompat.Builder’s methods. Can be called from a Context instance.
Example:
def notification = context.bigTextStyle {
contentTitle = "New message!"
ongoing = false
}
4.3.7. (Context).pendingActivityIntent(int requestCode, Intent intent, int flags)
Creates a PendingIntent
to an Activity
. Can be called from a Context instance.
4.3.8. (Context).intent(Class class)
Creates an explicit Intent
to the provided class. Can be called from a Context instance.
Example:
def intent = context.intent(OtherActivity)
context.startActivity(intent)
4.3.9. (Context).startActivity(Class<Activity> activityClass, Closure closure)
Creates an explicit Intent
to an Activity
of the provided class, the Intent can be modified using the closure. Can be called from a Context instance.
Example:
context.startActivity(AnotherActivity) {
putExtra("key", value)
}
4.4. Fragment methods
4.4.1. Class<Fragment>.withArgs(Context context, Map<String, ?> args)
Creates a Fragment
of the selected type with the args
pairs as arguments. Can be called from any class that inherits Fragment
.
Example:
def fragment = MyFragment.withArgs(context, ["page": 0])
4.4.2. Class<Fragment>.withArgs(Context context, Bundle args)
Creates a Fragment
of the selected type with the args
Bundle
as arguments. Can be called from any class that inherits Fragment
.
Example:
def fragment = MyFragment.withArgs(context, bundle)
4.4.3. (FragmentActivity).addFragment(Closure transactionSpec)
Commits an add FragmentTransaction
with the transactionSpec
values.
Example:
fragmentActivity.addFragment {
replacedViewId = R.id.container
fragment = myFragment
// These 2 are not required
addToBackStack = false
customAnimations = null
}
4.4.4. (FragmentActivity).replaceFragment(Closure transactionSpec)
Commits a replace FragmentTransaction
with the transactionSpec
values.
Example:
fragmentActivity.replaceFragment {
replacedViewId = R.id.container
fragment = myFragment
// These 2 are not required
addToBackStack = false
customAnimations = null
}
4.5. Bundle methods
4.5.1. Bundle.fromMap(Map<String, ?> argsMap)
Turns a Map
object with String
keys into a Bundle
. It will only add values that can be put into a Bundle
object. Can be called from Bundle
class.
Example:
def myMap = ["id": 1, "value": parcelableEntity]
def bundle = Bundle.fromMap(myMap)
4.5.2. (Map).asBundle()
Turns a Map
object with String
keys into a Bundle
. It will only add values that can be put into a Bundle
object. Can be called from a Map<String, ?>
instance.
Example:
def myMap = ["id": 1, "value": parcelableEntity]
def bundle = myMap.asBundle()
4.5.3. (Bundle).putFromMap(Map<String, ?> argsMap)
Adds a Map
object with String
keys to a Bundle
. It will only add values that can be put into a Bundle
object. Can be called from a Bundle
instance.
Example:
def myMap = ["id": 1, "value": parcelableEntity]
def myBundle = new Bundle()
myBundle.putFromMap(myMap)
4.6. Event methods
4.6.1. (View).onClick(Closure closure)
Sets a View
's OnClickListener
and executes the closure when it’s called.
Example:
myButton.onClick {
callSomeMethod()
}
4.6.2. (View).onLongClick(Closure closure)
Sets a View
's OnLongClickListener
and executes the closure when it’s called.
The closure must return a boolean value.
Example:
myButton.onClick {
callSomeMethod()
return true
}
4.6.3. (ListView).onClick(Closure closure)
Sets a ListView
's OnItemClickListener
and executes the closure when it’s called.
Valid closure parameters:
-
ItemClass item
-
ItemClass item, View v
-
ItemClass item, View v, int position
Example:
myList.onClick { Object item, View v ->
callSomeMethod(item)
}
4.6.4. (View).onLongClick(Closure closure)
Sets a ListView
's OnItemLongClickListener
and executes the closure when it’s called.
Valid closure parameters:
-
ItemClass item
-
ItemClass item, View v
-
ItemClass item, View v, int position
Must return a boolean value.
Example:
myList.onLongClick { Object item, View v ->
callSomeMethod()
return true
}
4.7. Misc methods
4.7.1. (Object).async(Closure closure)
AsyncTasks
using closures.
Example:
async { context, GAsyncTask task ->
task.error { e ->
// here we can catch error situation, param "e" is usually Exception
}
task.after {
// here we can catch after task situation
}
// and here we should execute long running task
this.tempApartments = DatabaseService.instance.getApartments(this.offset)
}
4.7.2. (String).asImage()
Download and decode a Bitmap
from a String
url.
Example:
async { ctx, task ->
task.error { e ->
}
this.imageView.imageBitmap = 'http://someurlwithimage.com/'.asImage()
}
5. Using Proguard
First of all, thanks to @marcoRS for his base proguard config file, in which I based the one I’m going to show you.
5.1. Reflection and ProGuard issues
If you try to use ProGuard on a project that uses SwissKnife, you will see how most injection methods are not on the final classes. Why does this happen?
ProGuard shrinks all unused methods by default from your code. As most methods are called using Reflection, ProGuard just assumes that they are never called at all and shrinks them automatically, making you app not work anymore. To fix this, you must put this code on your proguard-rules.pro:
# Groovy stuff so the compiler doesn't complain
-dontwarn org.codehaus.groovy.**
-dontwarn groovy**
-dontwarn com.vividsolutions.**
-dontwarn com.squareup.**
-dontwarn okio.**
-keep class org.codehaus.groovy.vmplugin.**
-keep class org.codehaus.groovy.runtime.**
-keepclassmembers class org.codehaus.groovy.runtime.dgm* {*;}
-keepclassmembers class ** implements org.codehaus.groovy.runtime.GeneratedClosure {*;}
-keepclassmembers class org.codehaus.groovy.reflection.GroovyClassValue* {*;}
# Don't shrink SwissKnife methods
-keep class com.arasthel.swissknife** { *; }
# Add this for any classes that will have SK injections
-keep class * extends android.app.Activity
-keepclassmembers class * extends android.app.Activity {*;}
6. Intellij Plugin
You can find it on JetBrains Plugin Repo.
Using the plugin is really simple. You just have to install it, go to a Groovy class and open Generate menu. There, you will find this:
Then, you select the desired annotation and a list of compatible methods will be shown:
After you select one, the desired method will be appended to your class:
The names of the methods and variables can be changed to whatever you want if you keep the argument list structure.