Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Demystifying Butterknife snippets
BindAnim
BindArray
BindBitmap
BindBool
BindColor
BindDimen
BindDrawable
BindFloat
BindFont
BindInt
BindString
BindView
BindViews
OnCheckedChanged
OnClick
OnEditorAction
OnFocusChange
OnItemClick
OnItemLongClick
OnItemSelected
OnLongClick
OnPageChange
OnTextChanged
OnTouch
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
...
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
...
try {
return constructor.newInstance(target, source);
}
...
}
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor;
... // Removed cache code
String clsName = cls.getName();
...
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
...
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
...
}
...
return bindingCtor;
}
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
...
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
...
} else {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames)
throws Exception {
...
ExecutableElement executableElement = (ExecutableElement) element;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Assemble information on the method.
Annotation annotation = element.getAnnotation(annotationClass);
Method annotationValue = annotationClass.getDeclaredMethod("value");
...
int[] ids = (int[]) annotationValue.invoke(annotation);
String name = executableElement.getSimpleName().toString();
boolean required = isListenerRequired(executableElement);
...
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
...
ListenerMethod method;
ListenerMethod[] methods = listener.method();
if (methods.length > 1) {
...
} else if (methods.length == 1) {
...
method = methods[0];
} else {
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
...
}
// Verify that the method has equal to or less than the number of parameters as the listener.
List<? extends VariableElement> methodParameters = executableElement.getParameters();
...
Parameter[] parameters = Parameter.NONE;
if (!methodParameters.isEmpty()) {
parameters = new Parameter[methodParameters.size()];
BitSet methodParameterUsed = new BitSet(methodParameters.size());
String[] parameterTypes = method.parameters();
for (int i = 0; i < methodParameters.size(); i++) {
VariableElement methodParameter = methodParameters.get(i);
TypeMirror methodParameterType = methodParameter.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
for (int j = 0; j < parameterTypes.length; j++) {
if (methodParameterUsed.get(j)) {
continue;
}
if ((isSubtypeOfType(methodParameterType, parameterTypes[j])
&& isSubtypeOfType(methodParameterType, VIEW_TYPE))
|| isTypeEqual(methodParameterType, parameterTypes[j])
|| isInterface(methodParameterType)) {
parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
methodParameterUsed.set(j);
break;
}
}
...
}
}
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
QualifiedId qualifiedId = elementToQualifiedId(element, id);
builder.addMethod(getId(qualifiedId), listener, method, binding)
...
}
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
public final class FinalRClassBuilder {
...
private static final String[] SUPPORTED_TYPES = {
"anim", "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "layout", "menu", "plurals",
"string", "style", "styleable"
};
...
public static void brewJava(File rFile, File outputDir, String packageName, String className)
throws Exception {
CompilationUnit compilationUnit = JavaParser.parse(rFile);
TypeDeclaration resourceClass = compilationUnit.getTypes().get(0);
TypeSpec.Builder result =
TypeSpec.classBuilder(className).addModifiers(PUBLIC).addModifiers(FINAL);
for (Node node : resourceClass.getChildNodes()) {
if (node instanceof ClassOrInterfaceDeclaration) {
addResourceType(Arrays.asList(SUPPORTED_TYPES), result, (ClassOrInterfaceDeclaration) node);
}
}
JavaFile finalR = JavaFile.builder(packageName, result.build())
...
.build();
finalR.writeTo(outputDir);
}
private static void addResourceType(List<String> supportedTypes, TypeSpec.Builder result,
ClassOrInterfaceDeclaration node) {
...
String type = node.getNameAsString();
TypeSpec.Builder resourceType = TypeSpec.classBuilder(type).addModifiers(PUBLIC, STATIC, FINAL);
for (BodyDeclaration field : node.getMembers()) {
if (field instanceof FieldDeclaration) {
FieldDeclaration declaration = (FieldDeclaration) field;
// Check that the field is an Int because styleable also contains Int arrays which can't be
// used in annotations.
if (isInt(declaration)) {
addResourceField(resourceType, declaration.getVariables().get(0),
getSupportAnnotationClass(type));
}
}
}
result.addType(resourceType.build());
}
...
private static void addResourceField(TypeSpec.Builder resourceType, VariableDeclarator variable,
ClassName annotation) {
String fieldName = variable.getNameAsString();
String fieldValue = variable.getInitializer().map(Node::toString).orElse(null);
FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(int.class, fieldName)
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer(fieldValue);
...
resourceType.addField(fieldSpecBuilder.build());
}
...
}
...
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.myTextView = Utils.findRequiredViewAsType(source, R.id.my_text_view, "field 'myTextView'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
...
this.target = null;
target.myTextView = null;
}
}
public class MyActivity extends Activity {
@BindView(R.id.my_text_view) TextView myTextView;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// instead of reflection using
unbinder = Butterknife.bind(this);
// you can write
unbinder = new MyActivity_ViewBinding(this);
myTextView.setText("Ta-da!");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment