Skip to content

Commit

Permalink
Add AndroidX support. (#1289)
Browse files Browse the repository at this point in the history
  • Loading branch information
naturalwarren authored and JakeWharton committed Jun 14, 2018
1 parent d120736 commit f0b7351
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,20 @@ final class BindingSet {
private static final ClassName RESOURCES = ClassName.get("android.content.res", "Resources");
private static final ClassName UI_THREAD =
ClassName.get("android.support.annotation", "UiThread");
private static final ClassName UI_THREAD_ANDROIDX =
ClassName.get("androidx.annotation", "UiThread");
private static final ClassName CALL_SUPER =
ClassName.get("android.support.annotation", "CallSuper");
private static final ClassName CALL_SUPER_ANDROIDX =
ClassName.get("androidx.annotation", "CallSuper");
private static final ClassName SUPPRESS_LINT =
ClassName.get("android.annotation", "SuppressLint");
private static final ClassName UNBINDER = ClassName.get("butterknife", "Unbinder");
static final ClassName BITMAP_FACTORY = ClassName.get("android.graphics", "BitmapFactory");
static final ClassName CONTEXT_COMPAT =
ClassName.get("android.support.v4.content", "ContextCompat");
static final ClassName CONTEXT_COMPAT_ANDROIDX =
ClassName.get("androidx.core.content", "ContextCompat");
static final ClassName ANIMATION_UTILS =
ClassName.get("android.view.animation", "AnimationUtils");

Expand Down Expand Up @@ -82,13 +88,14 @@ private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean
this.parentBinding = parentBinding;
}

JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) {
TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}

private TypeSpec createType(int sdk, boolean debuggable) {
private TypeSpec createType(int sdk, boolean debuggable, boolean useAndroidX) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
Expand All @@ -106,42 +113,42 @@ private TypeSpec createType(int sdk, boolean debuggable) {
}

if (isView) {
result.addMethod(createBindingConstructorForView());
result.addMethod(createBindingConstructorForView(useAndroidX));
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
result.addMethod(createBindingConstructorForActivity(useAndroidX));
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
result.addMethod(createBindingConstructorForDialog(useAndroidX));
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
result.addMethod(createBindingViewDelegateConstructor(useAndroidX));
}
result.addMethod(createBindingConstructor(sdk, debuggable));
result.addMethod(createBindingConstructor(sdk, debuggable, useAndroidX));

if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
result.addMethod(createBindingUnbindMethod(result, useAndroidX));
}

return result.build();
}

private MethodSpec createBindingViewDelegateConstructor() {
private MethodSpec createBindingViewDelegateConstructor(boolean useAndroidX) {
return MethodSpec.constructorBuilder()
.addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n "
+ "Only present for runtime invocation through {@code ButterKnife.bind()}.\n",
bindingClassName, targetTypeName, CONTEXT)
.addAnnotation(Deprecated.class)
.addAnnotation(UI_THREAD)
.addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target")
.addParameter(VIEW, "source")
.addStatement(("this(target, source.getContext())"))
.build();
}

private MethodSpec createBindingConstructorForView() {
private MethodSpec createBindingConstructorForView(boolean useAndroidX) {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
Expand All @@ -152,9 +159,9 @@ private MethodSpec createBindingConstructorForView() {
return builder.build();
}

private MethodSpec createBindingConstructorForActivity() {
private MethodSpec createBindingConstructorForActivity(boolean useAndroidX) {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
Expand All @@ -165,9 +172,9 @@ private MethodSpec createBindingConstructorForActivity() {
return builder.build();
}

private MethodSpec createBindingConstructorForDialog() {
private MethodSpec createBindingConstructorForDialog(boolean useAndroidX) {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
if (constructorNeedsView()) {
Expand All @@ -178,9 +185,9 @@ private MethodSpec createBindingConstructorForDialog() {
return builder.build();
}

private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
private MethodSpec createBindingConstructor(int sdk, boolean debuggable, boolean useAndroidX) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD)
.addModifiers(PUBLIC);

if (hasMethodBindings()) {
Expand Down Expand Up @@ -255,12 +262,12 @@ private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
return constructor.build();
}

private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass, boolean useAndroidX) {
MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
if (!isFinal && parentBinding == null) {
result.addAnnotation(CALL_SUPER);
result.addAnnotation(useAndroidX ? CALL_SUPER_ANDROIDX : CALL_SUPER);
}

if (hasTargetField()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;

Expand Down Expand Up @@ -120,6 +121,7 @@ public final class ButterKnifeProcessor extends AbstractProcessor {

private int sdk = 1;
private boolean debuggable = true;
private boolean useAndroidX = false;

private final RScanner rScanner = new RScanner();

Expand All @@ -139,6 +141,7 @@ public final class ButterKnifeProcessor extends AbstractProcessor {
}

debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
useAndroidX = hasAndroidX(env.getElementUtils());

typeUtils = env.getTypeUtils();
filer = env.getFiler();
Expand Down Expand Up @@ -188,7 +191,7 @@ private Set<Class<? extends Annotation>> getSupportedAnnotations() {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();

JavaFile javaFile = binding.brewJava(sdk, debuggable);
JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
Expand Down Expand Up @@ -666,9 +669,17 @@ private void parseResourceColor(Element element,
int id = element.getAnnotation(BindColor.class).value();
Id resourceId = elementToId(element, BindColor.class, id);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
builder.addResource(new FieldResourceBinding(resourceId, name,
isColorStateList ? FieldResourceBinding.Type.COLOR_STATE_LIST
: FieldResourceBinding.Type.COLOR));

FieldResourceBinding.Type colorStateList = useAndroidX
? FieldResourceBinding.Type.COLOR_STATE_LIST_ANDROIDX
: FieldResourceBinding.Type.COLOR_STATE_LIST;
FieldResourceBinding.Type color = useAndroidX
? FieldResourceBinding.Type.COLOR_ANDROIDX
: FieldResourceBinding.Type.COLOR;
builder.addResource(new FieldResourceBinding(
resourceId,
name,
isColorStateList ? colorStateList : color));

erasedTargetNames.add(enclosingElement);
}
Expand Down Expand Up @@ -769,7 +780,8 @@ private void parseResourceDrawable(Element element,
Map<Integer, Id> resourceIds = elementToIds(element, BindDrawable.class, new int[] {id, tint});

BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
builder.addResource(new FieldDrawableBinding(resourceIds.get(id), name, resourceIds.get(tint)));
builder.addResource(new FieldDrawableBinding(resourceIds.get(id), name, resourceIds.get(tint),
useAndroidX));

erasedTargetNames.add(enclosingElement);
}
Expand Down Expand Up @@ -841,7 +853,7 @@ private void parseResourceFont(Element element,

BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
Id resourceId = elementToId(element, BindFont.class, bindFont.value());
builder.addResource(new FieldTypefaceBinding(resourceId, name, style));
builder.addResource(new FieldTypefaceBinding(resourceId, name, style, useAndroidX));

erasedTargetNames.add(enclosingElement);
}
Expand Down Expand Up @@ -1348,6 +1360,19 @@ private static AnnotationMirror getMirror(Element element,
return null;
}

/**
* Perform two lookups to see if the androidx annotation and core libraries are on the application
* classpath. If both aren't present butterknife will leverage support annotations and
* compat libraries instead.
*/
private static boolean hasAndroidX(Elements elementUtils) {
boolean annotationsPresent
= elementUtils.getTypeElement("androidx.annotation.NonNull") != null;
boolean corePresent
= elementUtils.getTypeElement("androidx.core.content.ContextCompat") != null;
return annotationsPresent && corePresent;
}

private static class RScanner extends TreeScanner {
Map<Integer, Id> resourceIds = new LinkedHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
import com.squareup.javapoet.CodeBlock;

import static butterknife.compiler.BindingSet.CONTEXT_COMPAT;
import static butterknife.compiler.BindingSet.CONTEXT_COMPAT_ANDROIDX;
import static butterknife.compiler.BindingSet.UTILS;
import static butterknife.internal.Constants.NO_RES_ID;

final class FieldDrawableBinding implements ResourceBinding {
private final Id id;
private final String name;
private final Id tintAttributeId;
private final boolean androidX;

FieldDrawableBinding(Id id, String name, Id tintAttributeId) {
FieldDrawableBinding(Id id, String name, Id tintAttributeId, boolean useAndroidX) {
this.id = id;
this.name = name;
this.tintAttributeId = tintAttributeId;
this.androidX = useAndroidX;
}

@Override public Id id() {
Expand All @@ -33,6 +36,7 @@ final class FieldDrawableBinding implements ResourceBinding {
if (sdk >= 21) {
return CodeBlock.of("target.$L = context.getDrawable($L)", name, id.code);
}
return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, CONTEXT_COMPAT, id.code);
return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name,
androidX ? CONTEXT_COMPAT_ANDROIDX : CONTEXT_COMPAT, id.code);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ enum Type {
BOOL("getBoolean"),
COLOR(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColor", false, 1),
new ResourceMethod(null, "getColor", false, 23)),
COLOR_ANDROIDX(new ResourceMethod(BindingSet.CONTEXT_COMPAT_ANDROIDX, "getColor", false, 1),
new ResourceMethod(null, "getColor", false, 23)),
COLOR_STATE_LIST(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColorStateList", false, 1),
new ResourceMethod(null, "getColorStateList", false, 23)),
COLOR_STATE_LIST_ANDROIDX(new ResourceMethod(BindingSet.CONTEXT_COMPAT_ANDROIDX,
"getColorStateList", false, 1),
new ResourceMethod(null, "getColorStateList", false, 23)),
DIMEN_AS_INT("getDimensionPixelSize"),
DIMEN_AS_FLOAT("getDimension"),
FLOAT(new ResourceMethod(BindingSet.UTILS, "getFloat", false, 1)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
final class FieldTypefaceBinding implements ResourceBinding {
private static final ClassName RESOURCES_COMPAT =
ClassName.get("android.support.v4.content.res", "ResourcesCompat");
private static final ClassName RESOURCES_COMPAT_ANDROIDX =
ClassName.get("androidx.core.content.res", "ResourcesCompat");
private static final ClassName TYPEFACE = ClassName.get("android.graphics", "Typeface");

/** Keep in sync with {@link android.graphics.Typeface} constants. */
Expand Down Expand Up @@ -35,11 +37,13 @@ enum TypefaceStyles {
private final Id id;
private final String name;
private final TypefaceStyles style;
private final boolean useAndroidX;

FieldTypefaceBinding(Id id, String name, TypefaceStyles style) {
FieldTypefaceBinding(Id id, String name, TypefaceStyles style, boolean useAndroidX) {
this.id = id;
this.name = name;
this.style = style;
this.useAndroidX = useAndroidX;
}

@Override public Id id() {
Expand All @@ -53,7 +57,8 @@ enum TypefaceStyles {
@Override public CodeBlock render(int sdk) {
CodeBlock typeface = sdk >= 26
? CodeBlock.of("res.getFont($L)", id.code)
: CodeBlock.of("$T.getFont(context, $L)", RESOURCES_COMPAT, id.code);
: CodeBlock.of("$T.getFont(context, $L)",
useAndroidX ? RESOURCES_COMPAT_ANDROIDX : RESOURCES_COMPAT, id.code);
if (style != TypefaceStyles.NORMAL) {
typeface = CodeBlock.of("$1T.create($2L, $1T.$3L)", TYPEFACE, typeface, style);
}
Expand Down

0 comments on commit f0b7351

Please sign in to comment.