Add View Binding to Replace findViewById
View Binding initializes every UI element which has an id in the layout files and exposes them to the developers through a generated class.
If you think writing
Does the code inside the activity or fragment start like this?
If you think writing
findViewById
in every activity or fragment should not be a developer's job then you are reading the right article.Does the code inside the activity or fragment start like this?
Manual UI elements initialization
// Initialize the UI elements
final ConstraintLayout constraintLayout = findViewById(R.id.coordinatorLayout);
final TextView responseTextView = findViewById(R.id.responseTextView);
final BottomAppBar bottomAppBar = findViewById(R.id.bottomAppBar);
final FloatingActionButton fab = findViewById(R.id.fab);
final RecyclerView productRecyclerView = findViewById(R.id.productRecyclerView);
final Button filterButton = findViewById(R.id.filterButton);
final Button sortButton = findViewById(R.id.sortButton);
Butter Knife UI elements initialization
// UI elements
@BindView(R.id.coordinatorLayout) CoordinatorLayout coordinatorLayout;
@BindView(R.id.responseTextView) TextView responseTextView;
@BindView(R.id.bottomAppBar) BottomAppBar bottomAppBar;
@BindView(R.id.fab) FloatingActionButton fab;
@BindView(R.id.productRecyclerView) RecyclerView productRecyclerView;
@BindView(R.id.filterButton) Button filterButton;
@BindView(R.id.sortButton) Button sortButton;
Doesn't this code look ugly? Even the article looks ugly by adding it to the top of the page. Without additional dependencies, Android now provides in-built support to replace
Enable View Binding for the module by adding the following lines to the
findViewById
in both Java and Kotlin.
Enable View Binding for the module by adding the following lines to the
android
block inside the module level build.gradle
file.Between Android Studio versions 3.6 and 4.0
viewBinding {
enabled = true
}
From Android Studio version 4.0
buildFeatures {
viewBinding true
}
Now, for an XML file with a name
The generated
Inside the
activity_main
, a corresponding view binding class with the name ActivityMainBinding
file would be generated.The generated
ActivityMainBinding
class implements the ViewBinding
interface to override getRoot
method. The getRoot
method returns the root element of the layout.
Inside the
MainActivity
or the activity class that is setting its content using the layout named activity_main
, Our typical,// Set the content view
setContentView(R.layout.activity_main);
changes to
// Inflate the layout
final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
// Set the content view
setContentView(binding.getRoot());
Calling
inflate
method inflates the activity_main
layout and internally calls bind
method where it initializes the views available in the layout.
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.activityContent;
View activityContent = rootView.findViewById(id);
if (activityContent == null) {
break missingId;
}
ActivityContentBinding activityContentBinding = ActivityContentBinding.bind(activityContent);
id = R.id.bottomAppBar;
BottomAppBar bottomAppBar = rootView.findViewById(id);
if (bottomAppBar == null) {
break missingId;
}
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) rootView;
id = R.id.fab;
FloatingActionButton fab = rootView.findViewById(id);
if (fab == null) {
break missingId;
}
return new ActivityMainBinding((CoordinatorLayout) rootView, activityContentBinding,
bottomAppBar, coordinatorLayout, fab);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
If you observe the generated class,
inflate
method is overloaded twice. One takes LayoutInflater
and the other one takes additional parameters ViewGroup
, attachToParent
.Does the second overloaded method look familiar? It is similar to inflating layout inside a fragment. This is the method, we use to perform View Binding inside a Fragment. Currently inside
onCreateView
,return inflater.inflate(R.layout.main_fragment, container, false)
changes to
binding = MainFragmentBinding.inflate(inflater, container, false);
return binding.getRoot();
The only pain point with View Binding in fragments is that we need to set the
binding
variable to null when the fragment is destroyed.@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
The
binding
variable holds references to all the views in the layout. The generated ViewBinding class exposes the variables by setting them to public.@NonNull
private final CoordinatorLayout rootView;
@NonNull
public final ActivityContentBinding activityContent;
@NonNull
public final BottomAppBar bottomAppBar;
@NonNull
public final CoordinatorLayout coordinatorLayout;
@NonNull
public final FloatingActionButton fab;
For example, we can set listeners to the fab button which has an id
fab
as shown below.Without View Binding:
// Initialize the UI element
final FloatingActionButton fab = findViewById(R.id.fab);
// Set a click listener
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Show a snack message
showSnack(getString(R.string.clicked_button_fab));
}
});
With View Binding:
// Set a click listener
binding.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Show a snack message
showSnack(getString(R.string.clicked_button_fab));
}
});
View Binding provides null safety and type safety. The
fab
button returned by the binding
variable is of type FloatingActionButton
. As soon as an id is removed, the compiler shows an error that the symbol cannot be resolved and there is no need to build the project to see which keys are removed.
In case, we have include tags that add nested layouts then simply add an id to the include tag to reference the nested elements in the layout.
<include android:id="@+id/activityContent"
layout="@layout/activity_content"/>
This gives us access to the views available inside
activity_content
like responseTextView
, productRecyclerView
, filterButton
, sortButton
and we can update the text view like below.binding.activityContent.responseTextView.setText(response);
Unlike other layout files, if we want to skip generating ViewBinding classes for some, we can always add
The code becomes cleaner by removing the manual or butter knife initialization of UI elements in the activity or fragment. Kotin Android Extensions provide similar feature but the solution is limited to pure Kotlin Android projects.
View Binding is similar to Data Binding but doesn't provide any advanced features like binding views in the layout or two-way binding.
tools:viewBindingIgnore="true"
to the top view group.The code becomes cleaner by removing the manual or butter knife initialization of UI elements in the activity or fragment. Kotin Android Extensions provide similar feature but the solution is limited to pure Kotlin Android projects.
View Binding is similar to Data Binding but doesn't provide any advanced features like binding views in the layout or two-way binding.