Xtendroid - Android特定语言
软件简介
Xtendroid是一款Android的领域特定语言,它大大降低样板代码,同时提供巨大的工具支持。Xtendroid利用Xtend
transpiler实现,它的特点是能够在Java代码编辑或编译期间具有拓展方法和活动注释(实时编辑代码生成器)功能。活动注释,他特别能够让Xtend比Kotlin或者Groovy语言更加适合DSL的创建。Xtendroid支持的Eclipse和IntelliJ/
Android Studio,其中包括代码完成,调试等。
举例特点
Anonymous inner classes (lambdas)
Android code:
// get button widget, set onclick handler to toast a message
Button myButton = (Button) findViewById(R.id.my_button);
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(this, "Hello, world!", Toast.LENGTH_LONG).show();
}
});
Xtendroid code:
import static extension org.xtendroid.utils.AlertUtils.* // for toast(), etc.
// myButton references getMyButton(), a lazy-getter generated by @AndroidActivity
myButton.onClickListener = [View v| // Lambda - params are optional
toast("Hello, world!")
]
Note: Semi-colons optional, compact and differentiating lambda syntax,
getters/setters as properties.
Type Inference
Android code:
// Store JSONObject results into an array of HashMaps
ArrayList<HashMap<String,JSONObject>> results = new ArrayList<HashMap<String,JSONObject>>();
HashMap<String,JsonObject> result1 = new HashMap<String,JSONObject>();
result1.put("result1", new JSONObject());
result2.put("result2", new JSONObject());
results.put(result1);
results.put(result2);
Xtendroid (Xtend) code:
var results = #[
#{ "result1" -> new JSONObject },
#{ "result2" -> new JSONObject }
]
Note: compact notation for Lists and Maps, method call brackets are optional
Multi-threading
Blink a button 3 times (equivalent Android code is too verbose to include
here):
import static extension org.xtendroid.utils.AsyncBuilder.*
// Blink button 3 times using AsyncTask
async [
// this is the doInBackground() code, runs in the background
for (i : 1..3) { // number ranges, nice!
runOnUiThread [ myButton.pressed = true ]
Thread.sleep(250) // look ma! no try/catch!
runOnUiThread [ myButton.pressed = false ]
Thread.sleep(250)
}
return true
].then [result|
// This is the onPostExecute() code, runs on UI thread
if (result) {
toast("Done!")
}
].onError [error|
toast("Oops! " + error.message)
].start()
Note: sneaky throws, smoother error handling. See documentation for the many
other benefits to using the AsyncBuilder.
Android boilerplate removal
Creating a Parcelable in Android:
public class Student implements Parcelable {
private String id;
private String name;
private String grade;
// Constructor
public Student(){
}
// Getter and setter methods
// ... ommitted for brevity!
// Parcelling part
public Student(Parcel in){
String[] data = new String[3];
in.readStringArray(data);
this.id = data[0];
this.name = data[1];
this.grade = data[2];
}
@Оverride
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(new String[] {this.id,
this.name,
this.grade});
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Student createFromParcel(Parcel in) {
return new Student(in);
}
public Student[] newArray(int size) {
return new Student[size];
}
};
}
Xtendroid:
// @Accessors creates getters/setters, @AndroidParcelable makes it parcelable!
@Accessors @AndroidParcelable class Student {
String id
String name
String grade
}
Note: the above Xtendroid code essentially generates the same Android code
above, into the build/generated folder, which gets compiled normally. Full bi-
directional interoperability with existing Java classes.
Functional programming
@Accessors class User {
String username
long salary
int age
}
var List<User> users = getAllUsers() // from somewhere...
var result = users.filter[ age >= 40 ].maxBy[ salary ]
toast('''Top over 40 is «result.username» earning «result.salary»'''
Note: String templating, many built-in list comprehension functions, lambdas
taking a single object parameter implicitly puts in scope.
Builder pattern
// Sample Builder class to create UI widgets, like Kotlin's Anko
class UiBuilder {
def static LinearLayout linearLayout(Context it, (LinearLayout)=>void initializer) {
new LinearLayout(it) => initializer
}
def static Button button(Context it, (Button)=>void initializer) {
new Button(it) => initializer
}
}
// Now let's use it!
import static extension org.xtendroid.utils.AlertUtils.*
import static extension UiBuilder.*
contentView = linearLayout [
gravity = Gravity.CENTER
addView( button [
text = "Say Hello!"
onClickListener = [
toast("Hello Android from Xtendroid!")
]
])
]
Note: You can create your own project-specific DSL!
Utilities
import static extension org.xtendroid.utils.AlertUtils.*
import static extension org.xtendroid.utils.TimeUtils.*
var Date yesterday = 24.hours.ago
var Date tomorrow = 24.hours.fromNow
var Date futureDate = now + 48.days + 20.hours + 2.seconds
if (futureDate - now < 24.hours) {
confirm("Less than a day to go! Do it now?") [
// user wants to do it now
doIt()
]
}
Note: using all of the above makes writing unit tests and instrumentation
tests very easy, and fun!