Skip to content

h87kg/holdr

 
 

Repository files navigation

Holdr

What is Holdr?

Holdr generates classes based on your layouts to help you interact with them in a type-safe way. It removes the boilerplate of doing TextView myTextView = findViewById(R.id.my_text_view) all the time.

Doesn't Butter Knife/AndroidAnnotaions/RoboGuice already do that?

This is a different approach to solving the same problem, the important difference is your layout dictates what is generated instead of annotations on your classes. This means that it's much less likely for your code and layouts to get out of sync.

This approach also means zero reflection (and no proguard issues) and works equally as well in library projects.

Usage

Simply apply the gradle plugin and your done!

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.12.2'
        classpath 'me.tatarka.holdr:gradle-plugin:1.1.0'
    }
}

repositories {
    mavenCentral()
}

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.holdr'

Say you have a layout file hand.xml.

<!-- hand.xml -->
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="Hello, Holdr!"/>
</LinearLayout>

Holdr will create a class for you named your.application.id.holdr.Holdr_Hand. This class is basically a view holder that you can instantiate anywhere you have a view.

In an Activity

public class MyActivity extends Activity {
    private Holdr_Hand holdr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.hand);
        holdr = new Holdr_Hand(findViewById(android.R.id.content));
        holdr.text.setText("Hello, Holdr!");
    }
}

In a Fragment

public class MyFragment extends Fragment {
    private Holdr_Hand holdr;
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.hand, container, false);
    }
    
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        holdr = new Holdr_Hand(view);
        holdr.text.setText("Hello, Holdr!");
    }
}

In an Adapter

public class MyAdapter extends BaseAdapter {
    // other methods
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Holdr_Hand holdr;
        if (convertView == null) {
            holdr = new Holdr_Hand(inflater.inflate(R.layout.hand, parent, false));
            holdr.getView().setTag(holdr);
        } else {
            holdr = (Holdr_Hand) convertView.getTag();
        }
        holdr.text.setText(getItem(position));
        return holdr.getView();
    }
}

In a Custom View

public class MyCustomView extends LinearLayout {
    Holdr_Hand holdr;
    
    // other methods
    
    private void init() {
        holdr = new Holdr_Hand(inflate(getContext(), R.layout.hand, this));
        holdr.text.setText("Hello, Holdr!");
    }
}

Callback Listeners

You can also specify listeners for your Activity/Fragment/Whatever to handle to make working with callbacks a bit nicer. For example, if you had the layout file hand.xml,

<!-- hand.xml -->
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical" 
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
      android:id="@+id/my_button"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Hello, Holdr!"
      app:holdr_onClick="true"/>
</LinearLayout>

The generated Holdr_Hand class will also have a listener interface for you to implement.

public class MyActivity extends Activity implements Holdr_Hand.Listener {
  private Holdr_Hand holdr;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.hand);
      holdr = new Holdr_Hand(findViewById(android.R.id.content));
      holdr.setListener(this);
  }
  
  @Override
  public void onMyButtonClick(Button myButton) {
      // Handle button click.
  }
}

Here is a list of all the listeners you can handle:

  • holdr_onTouch
  • holdr_onClick
  • holdr_onLongClick
  • holdr_onFocusChange
  • holdr_onCheckedChanged
  • holdr_onEditorAction
  • holdr_onItemClick
  • holdr_onItemLongClick

You can also specify a custom method name by doing app:holdr_onClick="myCustomMethodName" instead.

Custom Superclass

Want to use a Holdr in a place where you need a specific subclass? (RecyclerView.ViewHolder for example). Just use the attribute app:holdr_superclass="com.example.MySuperclass and it will subclass that instead of Holdr. The only requirement is that the superclass must contain a constructor that takes a View.

Controlling What's Generated

If you don't like the idea of a whole bunch of code being generated for all your layouts (It's really not much, I promise!), you can add holdr.defaultInclude false to your build.gradle and then you can manually opt-in for each of your layouts.

The easiest way to opt-in is to add app:holdr_include="all" to the root view of that layout.

By default, every view with an id gets added to the generated class. You can use the attributes holdr_include and holdr_ignore to get more granular control. Both take either the value "view" to act on just the view it's used on or "all" to act on that view and all it's children. For example,

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:id="@+id/container"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 app:holdr_ignore="all">

 <TextView
     android:id="@+id/text1"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     tools:text="Hello, Holdr!"
     app:holdr_include="view"/>
 `   
 <TextView
     android:id="@+id/text2"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     tools:text="Hello, Holdr!"/>
</LinearLayout>

would include only text1 in the generated class.

Note: The current implementation only allows you to nest these attributes 2 levels deep (ignore inside include inside ignore won't work). I don't think there is a use case complex enough to warrant this, but it may be fixed in a later version if there is a need.

Finally, if you don't like the field name generated for a specific id, you can set it yourself by using app:holdr_field_name="myBetterFieldName" on a view.

About

Because typing findViewById() in Android is such a pain.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published