From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Code completion can improve your productivity by reducing how much you have to type, but there are situations when a more powerful tool is needed. Thanks to Android Studio and IntelliJ, live templates make it much easier to focus on just the things you care about.
Live Templates are code snippets that I can insert into my code by typing their abbreviation and pressing tab
. They live in the Editor section of the Preferences.
A template contains hard-coded text and placeholder tokens (or variables). The parts that are marked by the $
character on either end are variables and normally would be the things that I’d be expected to type in.
For example, one of the built-in templates looks like this:
for(int $INDEX$ = 0; $INDEX$ < $LIMIT$; $INDEX$++) {
$END$
}
Here, we have three variables $INDEX$
, $LIMIT$
and $END$
.
$END$
is a special predefined variable that controls where your cursor will be placed once you’re done filling out the template. We will have to fill out the values of the other two.fori
in the Java file and press tab
. Android Studio will expand the template and put the cursor on the first variable that needs to be replaced. In this case, it will put the cursor where the template has the $INDEX$
token.$INDEX$
appears will copy what I’m typing, live!tab
or return
and the cursor will move to the next variable that needs to be defined, which is $LIMIT$
.$LIMIT$
variable, pressing tab
or return
will place the cursor where the $END$
variable is and will stop the template fill-out session.Let’s look at a more complex example.
In our Android Programming Guide, we use the newIntent
pattern for Activities and the newInstance
pattern for Fragments. Typically, creating a new Activity/Fragment pair involves the following steps:
newIntent
method in the activity.Intent
.getFragment
method that reads the Intent
extras and passes them on to the fragment’s newInstance
method.newInstance
method in the fragment.onCreate
method.In this video, I demonstrate how live templates make it much easier to focus on just the things I care about, rather than the boilerplate.
In the first part of the video, I’m using my “Activity New Intent with Arguments” template. I type ania
, the abbreviation I assigned to it, then hit <tab>
so Android Studio expands the template, and then I type these characters: String<tab><tab>scannerId<tab>
. I end up with this code:
public class ScannerActivity extends SingleFragmentActivity {
private static final String SCANNER_ID = "ScannerActivity.SCANNER_ID";
public static Intent newIntent(Context context, String scannerId) {
Intent intent = new Intent(context, ScannerActivity.class);
intent.putExtra(SCANNER_ID, scannerId);
return intent;
}
@Override
protected Fragment getFragment() {
String scannerId = getIntent().getStringExtra(SCANNER_ID);
return ScannerFragment.newInstance(scannerId);
}
}
I have another template, “Fragment New Instance with Arguments.” I type the abbreviation I assigned to the template, fnia
, then <tab>
, and get the expanded template. Then I type the same sequence of characters as for the ania
template: String<tab><tab>scannerId<tab>
. This is the result:
public class ScannerFragment extends Fragment {
private static final String SCANNER_ID = "ScannerFragment.SCANNER_ID";
private String mScannerId;
public static ScannerFragment newInstance(String scannerId) {
ScannerFragment fragment = new ScannerFragment();
Bundle args = new Bundle();
args.putString(SCANNER_ID, scannerId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mScannerId = getArguments().getString(SCANNER_ID);
}
}
Not too bad for just under 30 keystrokes.
There are two parts to live templates. One is the template itself and the other is the definition of the variables. This is my ania
live template:
private static final String $EXTRA_PARAM$ = "$CLASS_NAME$.$EXTRA_PARAM$";
public static Intent newIntent(Context context, $EXTRA_CLASS$ $EXTRA_VAR$) {
Intent intent = new Intent(context, $CLASS_NAME$.class);
intent.putExtra($EXTRA_PARAM$, $EXTRA_VAR$);$END$
return intent;
}
@Override
protected Fragment getFragment() {
$EXTRA_CLASS$ $EXTRA_VAR$ = getIntent().get$EXTRA_CLASS$Extra($EXTRA_PARAM$);
return $FRAGMENT_CLASS$.newInstance($EXTRA_VAR$);
}
Note that variables are deliniated by the $
symbol. Normally, each variable is what you need to type in. If a variable appears in multiple places, all of them are updated simultaneously as you type. It’s possible to customize these variables and even to automatically set their values based on other variables.
For example, $CLASS_NAME$
is defined as expression className
, which evaluates to the name of the current class. Here’s the full list of definitions:
Name | Expression | Default Value | Skip if Defined |
---|---|---|---|
EXTRA_CLASS |
typeOfVariable(VAR) |
[ ] | |
EXTRA_VAR |
suggestVariableName |
[ ] | |
CLASS_NAME |
className |
[x] | |
EXTRA_PARAM |
capitalizeAndUnderscore(EXTRA_VAR) |
[x] | |
FRAGMENT_CLASS |
groovyScript("_1.replaceAll('Activity','Fragment')", CLASS_NAME) |
[x] |
Three of the variables are marked “Skip if defined,” so I don’t need to type them; their names are derived from what I have already typed. I can even use groovyScript
to evaluate expressions beyond the fairly rich predefined set.
As I noted earlier, $END$
controls where your cursor will be once you’re done filling out the template. In this example, I want to put it inside the newIntent
method just before the return
statement, so that I can customize the Intent
object further. For example, I could add flags or more extras.
The fnia
template is very similar:
private static final String $ARG_PARAM$ = "$CLASS_NAME$.$ARG_PARAM$";
private $ARG_CLASS_DITTO$ m$INST_VAR$;
public static $CLASS_NAME$ newInstance($ARG_CLASS$ $ARG_VAR$) {
$CLASS_NAME$ fragment = new $CLASS_NAME$();
Bundle args = new Bundle();
args.put$ARG_CLASS$($ARG_PARAM$, $ARG_VAR$);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
m$INST_VAR$ = getArguments().get$ARG_CLASS$($ARG_PARAM$);
}
I had to use a little trick in this template. I created a special variable, $ARG_CLASS_DITTO$
. that’s a copy of the $ARG_CLASS$
variable. The reason I duplicated them is to force the cursor to start at the type of the parameter of the newInstance
method. If I didn’t do this, the cursor would first jump to the type of the instance variable, then to the name of the parameter.
Thanks to live templates, I’ve reduced the amount of typing I have to do when creating new Activities and Fragments. Of course, there are many other situations where Live Templates would come in handy as well. I’m sure lots of you have your own productivity tips and examples of Live Templates, so please feel free to share with your fellow developers in the comments!
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...