Findviewbyid to viewbinding
Skill almasumdev/awesome-java-android-agent-skills/.github/skills/migration/findviewbyid-to-viewbinding
Mechanical, safety-oriented migration from findViewById to Jetpack ViewBinding in a Java Android codebase. Use this when modernizing an older project that still uses findViewById throughout Activities, Fragments, and adapters.From its SKILL.md
npx -y skills add almasumdev/awesome-java-android-agent-skills --skill findviewbyid-to-viewbindingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.4 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
Migrating findViewById to ViewBinding
Instructions
findViewById returns View (pre-API 26 it was unchecked) and relies on string-like IDs resolved at runtime. Every rename, deletion, or typo becomes an NPE or ClassCastException. ViewBinding replaces this with a generated, null-safe class per layout.
1. Enable ViewBinding
app/build.gradle:
android {
buildFeatures {
viewBinding true
}
}
Do not enable DataBinding yet — it has a different migration surface and a heavier annotation processor.
2. Layout Preparation
ViewBinding uses the layout filename to generate the class (activity_main.xml → ActivityMainBinding). Before migrating code:
- Rename layouts so they match their screen (
main.xml→activity_main.xml,row.xml→item_article.xml). IDE's "Refactor → Rename" keeps resource references in sync. - Remove unused
<include>tags; every remaining<include>will need a unique id. - Convert nested
<merge>usages only after the parent layout has been migrated; a<merge>root is bound through the parent.
3. Activity Migration
Before:
public class MainActivity extends AppCompatActivity {
private Toolbar toolbar;
private RecyclerView list;
@Override
protected void onCreate(Bundle s) {
super.onCreate(s);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
list = findViewById(R.id.list);
toolbar.setTitle(R.string.app_name);
}
}
After:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle s) {
super.onCreate(s);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.toolbar.setTitle(R.string.app_name);
}
}
Activity bindings live for the Activity's full lifetime — no nullification needed.
4. Fragment Migration (most error-prone)
Fragments keep the binding only while their view is alive. The Fragment instance itself can outlive multiple onCreateView / onDestroyView cycles.
public class ArticleListFragment extends Fragment {
private FragmentArticleListBinding binding;
@Override
public View onCreateView(LayoutInflater i, ViewGroup p, Bundle s) {
binding = FragmentArticleListBinding.inflate(i, p, false);
return binding.getRoot();
}
@Override
public void onViewCreated(View v, Bundle s) {
binding.list.setAdapter(adapter);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // mandatory
}
}
If you forget binding = null in onDestroyView, the Fragment holds a reference to its destroyed view hierarchy.
5. RecyclerView Adapter Migration
Before:
@Override
public void onBindViewHolder(VH h, int pos) {
Article a = items.get(pos);
((TextView) h.itemView.findViewById(R.id.title)).setText(a.title());
((TextView) h.itemView.findViewById(R.id.body)).setText(a.body());
}
After:
static class VH extends RecyclerView.ViewHolder {
final ItemArticleBinding b;
VH(ItemArticleBinding b) { super(b.getRoot()); this.b = b; }
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int type) {
return new VH(ItemArticleBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder(VH h, int pos) {
Article a = items.get(pos);
h.b.title.setText(a.title());
h.b.body.setText(a.body());
}
Binding in onBindViewHolder repeatedly is the most common pre-migration hot path. Binding once in the ViewHolder constructor fixes it.
6. Custom Views
For a compound view, inflate in the constructor:
public class RatingBar extends ConstraintLayout {
private final ViewRatingBarBinding b;
public RatingBar(Context ctx, AttributeSet a) {
super(ctx, a);
b = ViewRatingBarBinding.inflate(LayoutInflater.from(ctx), this);
}
}
Note: inflating into this uses a <merge> root in the layout to avoid an extra level.
7. Migration Strategy (large codebase)
- Enable ViewBinding.
- Turn on the lint check
UseCompoundDrawables/ add a custom script to list files still usingfindViewById. - Migrate leaf files first (adapters), then Fragments, then Activities.
- After each file, run the app's smoke tests; ViewBinding catches renames at compile time.
- Remove
syntheticimports (should be none in a Java project; present only if Kotlin was mixed in). - Add a lint rule or Spotless custom step that rejects new
findViewByIdcalls insrc/main.
Checklist
-
viewBinding trueis set in every module that has UI. - Every Activity assigns
bindinginonCreatebeforesetContentView. - Every Fragment nulls its binding in
onDestroyView. - RecyclerView ViewHolders hold the binding, not loose view references.
- No
findViewByIdcalls remain insrc/main(verified bygrep -rn 'findViewById' src/main/java). - App launches and all screens render identically after the migration.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.