View Binding does everything holdr does but is a first-party solution. I will still be maintaing this project and fixing any bugs, but I will not be adding any new features.
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.
Simply apply the gradle plugin and your done!
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
classpath 'me.tatarka.holdr:gradle-plugin:1.5.2'
}
}
repositories {
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.holdr'
alternativly, you can use the new gradle 2.1+ syntax
plugins {
id "me.tatarka.holdr" version "1.5.2"
}
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.
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!");
}
}
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!");
}
@Override
public void onDestroyView() {
super.onDestroyView();
holdr = null;
}
}
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();
}
}
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!");
}
}
You may have multiple instances of a layout (in layout
and layout-land
for
example). In that case Holdr will merge the id's across them. If an id appears
in one and not the other, a @Nullable
annotation will be generated to warn you
of this.
If the type of the view doesn't match, Holdr will take the most
conservative route and use type View
. If instead, they share a common
superclass and you want to use that, you can use the app:holdr_class
to
override the view type so that they match.
<!-- layout/hand.xml -->
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Hello, Holdr!"/>
<!-- layout-land/hand.xml -->
<com.example.MyCustomTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Hello, Holdr!"
app:holdr_class="TextView"/>
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. You can also specify the same
name on multiple views and they will share a listener (provided the listeners
are of the same type).
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
.
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.
Tired of having to build your project after every layout change? With the intellij plugin the Holdr classes will be auto-generated as soon as you save!
Go to Settings -> Plugins -> Browse Repositories...
and search for "Holdr".
The plugin will also allow you to do a refactor-rename on holdr fields and use goto-source (Ctrl-click or Ctrl-B) to go directly to the view in the layout.
(Requires Android Studio 0.6.0+
or Intellij 14)
If instead you feel like living on the edge, you can build install the plugin manually.
- Clone the repo
- Change
studio.path
ingradle.properties
to point to your Android Studio/Intellij instalation directory - Run
./gradlew intellij-plugin:build --configure-on-demand
- Go to
Settings -> Plugins -> Install plugin from disk...
and install the jar in./intellij-plugin/build/libs/