The famous ButterKnife library is familiar to many Android developers. The number of stars on Github is nearly 15K, and it is used by many well-known apps.

ButterKnife simplifies code like findViewById and setOnClickListener, freeing developers from tedious details and focusing more on business code development.

Why create another wheel when the ButterKnife is powerful enough? You say good code, I say you are not brave enough to build wheels. In fact, this library is more lightweight with only a few of the most commonly used annotations, and it is completely developed based on Kotlin.

Download and install:

Add it in build.gradle in the root directory

 buildscript {
     repositories {
         jcenter()
     }
     dependencies {
         classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'}}Copy the code

Add it to build.gradle in the app module directory

apply plugin: 'com.neenbedankt.android-apt'. dependencies { compile'com. Safframework. Injectview: saf - injectview: 1.0.0'
    apt 'com. Safframework. Injectview: saf - injectview - compiler: 1.0.0'. }Copy the code

The demo presentation





Injectview Demo. GIF

Demo and library addresses: github.com/fengzhizi71…

Design of the entire library:

The entire library consists of three modules:

  • Injectview: Android library, including the Injector class and ViewBinder interface. Use to enter injection in Activity, Fragment, View, Dialog.
  • Annotations: Java Library, used to store individual annotations, such as @InjectView.
  • Injectview-compiler: Java Library, uses APT to generate code




PNG Project structure diagram





The module diagram of the whole project. PNG

1. injectview module

import android.app.Activity
import android.app.Dialog
import android.support.v4.app.Fragment
import android.view.View
import java.lang.reflect.Field

/** * Created by Tony Shen on 2017/1/24. */

object Injector {

    enum class Finder {

        DIALOG {
            override fun findById(source: Any, id: Int): View {
                return (source as Dialog).findViewById(id)
            }
        },
        ACTIVITY {
            override fun findById(source: Any, id: Int): View {
                return (source as Activity).findViewById(id)
            }

            override fun getExtra(source: Any, key: String, fieldName: String): Any? {

                val intent = (source as Activity).intent

                if(intent ! =null) {
                    val extras = intent.extras

                    varvalue: Any? = extras? .get(key)

                    var field: Field? = null
                    try {
                        field = source.javaClass.getDeclaredField(fieldName)
                    } catch (e: NoSuchFieldException) {
                        e.printStackTrace()
                    }

                    if (field == null) return null;

                    if (value == null) {
                        when {
                            field.type.name == Int: :class.java.name || field.type.name == "int" -> value = 0
                            field.type.name == Boolean: :class.java.name || field.type.name == "boolean" -> value = false
                            field.type.name == java.lang.String::class.java.name -> value = ""
                            field.type.name == Long: :class.java.name || field.type.name == "long" -> value = 0L
                            field.type.name == Double: :class.java.name || field.type.name == "double" -> value = 0.0}}if(value ! =null) {
                        try {
                            field.isAccessible = true
                            field.set(source, value)
                            return field.get(source)
                        } catch (e: IllegalAccessException) {
                            e.printStackTrace()
                        }
                    }
                }

                return null
            }
        },
        FRAGMENT {
            override fun findById(source: Any, id: Int): View {
                return (source as View).findViewById(id)
            }
        },
        VIEW {
            override fun findById(source: Any, id: Int): View {
                return (source as View).findViewById(id)
            }
        };

        abstract fun findById(source: Any, id: Int): View

        open fun getExtra(source: Any, key: String, fieldName: String): Any? {
            return null}}/** * Use annotations * in the Activity@param activity
     */
    @JvmStatic fun injectInto(activity: Activity) {
        inject(activity, activity, Finder.ACTIVITY)
    }

    /** * Use annotations in fragments *@param fragment
     * @param v
     *
     * @return* /
    @JvmStatic fun injectInto(fragment: Fragment, v: View) {
        inject(fragment, v, Finder.FRAGMENT)
    }

    /** * Use annotation * in dialog@param dialog
     *
     * @return* /
    @JvmStatic fun injectInto(dialog: Dialog) {
        inject(dialog, dialog, Finder.DIALOG)
    }

    /** * Use annotation * in view@param obj
     * @param v
     *
     * @return* /
    @JvmStatic fun injectInto(obj: Object, v: View) {
        inject(obj, v, Finder.VIEW)
    }

    private fun inject(host: Any, source: Any,finder: Finder) {
        val className = host.javaClass.name
        try {
            val finderClass = Class.forName(className+"\$\$ViewBinder")

            val viewBinder = finderClass.newInstance() as ViewBinder<Any>
            viewBinder.inject(host, source, finder)
        } catch (e: Exception) {
            // throw new RuntimeException("Unable to inject for " + className, e);
            println("Unable to inject for " + className)
        }

    }
}Copy the code

The method getExtra() in the enumerator Finder is final by default and needs to be marked open, and Kotlin requires that members be overridden explicitly using open.

Since the ViewBinder interface contains generics, we need to write Any in the Inject method

val viewBinder = finderClass.newInstance() as ViewBinder<Any>
viewBinder.inject(host, source, finder)Copy the code

Otherwise it will fail to compile.

2. injectview-annotations module

Kotlin can simplify the Annotation class, such as @InjectView in the Java version

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** * Created by Tony Shen on 2016/12/6. */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
    int value(a)default 0;
}Copy the code

Kotlin’s version looks like this

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy

/** * Created by Tony Shen on 2017/1/24. */
@Target(AnnotationTarget.FIELD)
@Retention(RetentionPolicy.CLASS)
annotation class InjectView(val value: Int = 0)Copy the code

It looks like a Kotlin rewrite would be more intuitive and easier.

3. injectview-compiler module

All annotations are compile-time annotation types, such as those used in activities that generate the same class name +? ViewBinder class.





PNG class generated by apt

TestViewActivity based on APT? ViewBinder class

import com.safframework.app.ui.TitleView;
import com.safframework.injectview.Injector.Finder;
import com.safframework.injectview.ViewBinder;
import java.lang.Object;
import java.lang.Override;

public class TestViewActivity?ViewBinder implements ViewBinder<TestViewActivity> {
  @Override
  public void inject(final TestViewActivity host, Object source, Finder finder) {
    host.titleView = (TitleView)(finder.findById(source, 2131427422)); }}Copy the code

How to use the entire library:

1. @InjectView

@InjectView makes it easy to find and register components, both android native and custom. So before we use @InjectView, we’re going to code like this

          public class MainActivity extends Activity {

                ImageView imageView;

                @Override
                protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);

                  setContentView(R.layout.activity_main);
                  imageView = (ImageView) findViewById(R.id.imageview); }}Copy the code

After using @InjectView, you’ll write the code like this

          public class MainActivity extends Activity {

                @InjectView(R.id.imageView)
                ImageView imageView;

                @Override
                protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);

                   setContentView(R.layout.activity_main);
                   Injector.injectInto(this); }}Copy the code

Currently, @InjectViews can be used in activities, dialogs, and fragments. The use of Activity and Dialog is similar, but the use of Fragment is a bit different.

          public class DemoFragment extends Fragment {

                   @InjectView(R.id.title)
                   TextView titleView;

                   @InjectView(R.id.imageview)
                   ImageView imageView;

                   @Override
                   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                          View v = inflater.inflate(R.layout.fragment_demo, container, false);

                          Injector.injectInto(this,v); // The difference with Activity is here

                          initViews();
                          initData();

                          returnv; }... }Copy the code

2. @InjectViews

          public class MainActivity extends Activity {

                @InjectViews(ids={R.id.imageView1,R.id.imageView2})
                ImageView[] imageviews;

                @Override
                protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);

                   setContentView(R.layout.activity_main);
                   Injector.injectInto(this); }}Copy the code

3. @InjectExtra

         / * * * MainActivity transfer data to the SecondActivity * Intent I = new Intent (MainActivity. This, SecondActivity. Class); * i.putExtra("test", "saf"); * i.putExtra("test_object", hello); * startActivity(i); * In SecondActivity you can use @Injectextra annotations * * @author Tony Shen * */
         public class SecondActivity extends Activity{

               @InjectExtra(key="test")
               String testStr;

               @InjectExtra(key="test_object")
               Hello hello;

               protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);

                   Injector.injectInto(this);
                   Log.i("+ + + + + + + + + + + +"."testStr="+testStr);
                   Log.i("+ + + + + + + + + + + +"."hello="+SAFUtil.printObject(hello)); // This method is used to print objects}}Copy the code

4. @OnClick

@onclick can be used in activities, fragments, dialogs, and views, as well as multiple components bound to the same method.

     public class AddCommentFragment extends BaseFragment {

         @Override
         public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

             View v = inflater.inflate(R.layout.fragment_add_comment, container, false);

             Injector.injectInto(this, v);

             initView();

             return v;
        }

        @OnClick(id={R.id.left_menu,R.id.btn_comment_cancel})
        void clickLeftMenu() {
            popBackStack();
        }

        @OnClick(id=R.id.btn_comment_send)
        void clickCommentSend() {
            if (StringHelper.isBlank(commentEdit.getText().toString())) {
               ToastUtil.showShort(mContext, R.string.the_comment_need_more_character);
            } else {
               AsyncTaskExecutor.executeAsyncTask(new AddCommentTask(showDialog(mContext))); }}... }Copy the code

Conclusion:

It implements only a few of ButterKnife’s annotations, but these are some of the most commonly used. Unfortunately, there are still some issues that need to be resolved in ListView and RecyclerView. There are many areas where this library can be improved in the future.