A Swiss army Knife is like a Dagger, but although it may be less sharp, it can be more handy

intro swissknife logo

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.

intro intellij plugin

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:

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.

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;
}

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 or Serializable.

  • 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:

DoubleUtils.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:

intellij 1
intellij 2

Then, you select the desired annotation and a list of compatible methods will be shown:

intellij 3

After you select one, the desired method will be appended to your class:

intellij 4

The names of the methods and variables can be changed to whatever you want if you keep the argument list structure.