Kotlin view binding fragment

View binding Part of Android Jetpack.

View binding is a feature that makes it easier to write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout.

In most cases, view binding replaces findViewById .

Setup

View binding is enabled on a module-by-module basis. To enable view binding in a module, set the viewBinding build option to true in the module-level build.gradle file, as shown in the following example:

Groovy

Kotlin

If you want a layout file to be ignored while generating binding classes, add the tools:viewBindingIgnore=»true» attribute to the root view of that layout file:

Usage

If view binding is enabled for a module, a binding class is generated for each XML layout file that the module contains. Each binding class contains references to the root view and all views that have an ID. The name of the binding class is generated by converting the name of the XML file to Pascal case and adding the word «Binding» to the end.

Читайте также:  Constant expression contains invalid operation php

For example, consider a layout file called result_profile.xml that contains the following:

The generated binding class is called ResultProfileBinding . This class has two fields: a TextView called name and a Button called button . The ImageView in the layout has no ID, so there is no reference to it in the binding class.

Every binding class also includes a getRoot() method, providing a direct reference for the root view of the corresponding layout file. In this example, the getRoot() method in the ResultProfileBinding class returns the LinearLayout root view.

The following sections demonstrate the use of generated binding classes in activities and fragments.

Use view binding in activities

To set up an instance of the binding class for use with an activity, perform the following steps in the activity’s onCreate() method:

  1. Call the static inflate() method included in the generated binding class. This creates an instance of the binding class for the activity to use.
  2. Get a reference to the root view by either calling the getRoot() method or using Kotlin property syntax.
  3. Pass the root view to setContentView() to make it the active view on the screen.

These steps are shown in the following example:

Kotlin

private lateinit var binding: ResultProfileBinding override fun onCreate(savedInstanceState: Bundle?)

Java

private ResultProfileBinding binding; @Override protected void onCreate(Bundle savedInstanceState)

You can now use the instance of the binding class to reference any of the views:

Kotlin

binding.name.text = viewModel.name binding.button.setOnClickListener

Java

binding.getName().setText(viewModel.getName()); binding.getButton().setOnClickListener(new View.OnClickListener() < viewModel.userClicked() >);

Use view binding in fragments

To set up an instance of the binding class for use with a fragment, perform the following steps in the fragment’s onCreateView() method:

  1. Call the static inflate() method included in the generated binding class. This creates an instance of the binding class for the fragment to use.
  2. Get a reference to the root view by either calling the getRoot() method or using Kotlin property syntax.
  3. Return the root view from the onCreateView() method to make it the active view on the screen.

Kotlin

private var _binding: ResultProfileBinding? = null // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? < _binding = ResultProfileBinding.inflate(inflater, container, false) val view = binding.root return view >override fun onDestroyView()

Java

private ResultProfileBinding binding; @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) < binding = ResultProfileBinding.inflate(inflater, container, false); View view = binding.getRoot(); return view; >@Override public void onDestroyView()

You can now use the instance of the binding class to reference any of the views:

Kotlin

binding.name.text = viewModel.name binding.button.setOnClickListener

Java

binding.getName().setText(viewModel.getName()); binding.button.setOnClickListener(new View.OnClickListener() < viewModel.userClicked() >);

Note: Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment’s onDestroyView() method.

Provide hints for different configurations

When you declare views across multiple configurations, it occasionally makes sense to use a different view type depending on the particular layout. The following code snippet shows an example of this:

# in res/layout/example.xml # in res/layout-land/example.xml 

In this case, you might expect the generated class to expose a field userBio of type TextView , because TextView is the common base class. Due to technical limitations, the view binding code generator can’t determine this and generates a View field instead. This requires casting the field later with binding.userBio as TextView .

To work around this limitation, view binding supports a tools:viewBindingType attribute, letting you tell the compiler what type to use in the generated code. In the previous example, you can use this attribute to make the compiler generate the field as a TextView :

# in res/layout/example.xml (unchanged) # in res/layout-land/example.xml 

In another example, suppose you have two layouts, one that contains a BottomNavigationView and another that contains a NavigationRailView . Both classes extend NavigationBarView , which contains most of the implementation details. If your code doesn’t need to know exactly which subclass is present in the current layout, you can use tools:viewBindingType to set the generated type to NavigationBarView in both layouts:

# in res/layout/navigation_example.xml # in res/layout-w720/navigation_example.xml 

View binding can’t validate the value of this attribute when generating code. To avoid compile-time and runtime errors, the value must meet the following conditions:

  • The value must be a class that inherits from android.view.View .
  • The value must be a superclass of the tag it is placed on. For example, the following values don’t work:

Differences from findViewById

View binding has important advantages over using findViewById :

  • Null safety: since view binding creates direct references to views, there’s no risk of a null pointer exception due to an invalid view ID. Additionally, when a view is only present in some configurations of a layout, the field containing its reference in the binding class is marked with @Nullable .
  • Type safety: the fields in each binding class have types matching the views they reference in the XML file. This means there’s no risk of a class cast exception.

These differences mean incompatibilities between your layout and your code result in your build failing at compile time rather than at runtime.

Comparison with data binding

View binding and data binding both generate binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding:

  • Faster compilation: view binding requires no annotation processing, so compile times are faster.
  • Ease of use: view binding doesn’t require specially tagged XML layout files, so it’s faster to adopt in your apps. Once you enable view binding in a module, it applies to all of that module’s layouts automatically.

On the other hand, view binding has the following limitations compared to data binding:

  • View binding doesn’t support layout variables or layout expressions, so it can’t be used to declare dynamic UI content straight from XML layout files.
  • View binding doesn’t support two-way data binding.

Because of these considerations, in some cases it’s best to use both view binding and data binding in a project. You can use data binding in layouts that require advanced features and use view binding in layouts that don’t.

Additional resources

To learn more about view binding, see the following additional resources:

Samples

Blogs

Videos

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2023-07-12 UTC.

Источник

Делаем Android View Binding удобным c Kotlin

Привет! Меня зовут Кирилл Розов. Я автор Telegram канала Android Broadcast. Очень люблю Kotlin и мне нравится с помощью его возможностей упрощать разработку. С такой задачей я недавно столкнулся, когда на новом Android проекте начали использовать View Binding.

image

Эта возможность появилась в Android Studio 3.6, но на самом деле она не совсем новая, а облегченный вариант Android Data Binding. Зачем столько усложнений? Проблема была в скорости — множество разработчиков использовали Android Data Binding только для генерации кода со ссылками на View и игнорировали другие возможности библиотеки. Чтобы ускорить генерацию кода, создали View Binding . Однако стандартный способ работы с ней — это дублирование кода от которого хочется избавиться.

Стандартный способ работы с View Binding

Разберем работу View Binding на примере Fragment. У нас есть layout ресурс с именем profile.xml (содержимое его неважно). Если мы хотим использовать ViewBinding, тогда в стандартном варианте это будет выглядеть так:

class ProfileFragment : Fragment(R.layout.profile) < private var viewBinding: ProfileBinding? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) < super.onViewCreated(view, savedInstanceState) viewBinding = ProfileBinding.bind(view) // Используем созданный viewBinding >override fun onDestroyView() < super.onDestroyView() viewBinding = null >>
  • Много лишнего кода
  • Копи паста: каждый Fragment будет иметь аналогичный кусок кода
  • Property viewBinding получается nullable и модифицируемым.

Давайте пробовать избавляться от этого с помощью Cилы Kotlin

Kotlin Delegated Property в бой

С помощью делегирования работы с property в Kotlin можно круто повторно использовать код и упростить некоторые задачи. Например, я применил это в случае с ViewBinding . Для этого я сделал свой делегат, который оборачивает создание ViewBinding и очистку его в нужный момент жизненного цикла:

class FragmentViewBindingProperty( private val viewBinder: ViewBinder ) : ReadOnlyProperty < internal var viewBinding: T? = null private val lifecycleObserver = BindingLifecycleObserver() @MainThread override fun getValue(thisRef: Fragment, property: KProperty): T < checkIsMainThread() this.viewBinding?.let < return it >val view = thisRef.requireView() thisRef.viewLifecycleOwner.lifecycle.addObserver(lifecycleObserver) return viewBinder.bind(view).also < vb ->this.viewBinding = vb > > private inner class BindingLifecycleObserver : DefaultLifecycleObserver < @MainThread override fun onDestroy(owner: LifecycleOwner) < owner.lifecycle.removeObserver(this) viewBinding = null >> >

и конечно же функцию-фабрику, чтобы не видеть как делегат создается:

inline fun Fragment.viewBinding(): ReadOnlyProperty

После небольшого рефакторинга с новыми возможностями я получил следующее:

class ProfileFragment() : Fragment(R.layout.profile) < private val viewBinding: ProfileBinding by viewBinding() override fun onViewCreated(view: View, savedInstanceState: Bundle?) < super.onViewCreated(view, savedInstanceState) // Используем созданный viewBinding >>

Вроде задача, которая ставилась, была достигнута. Что же могло пойти не так?

Момент, когда что-то пошло не так.

В какой-то момент возникла необходимость чистить View следующим образом:

class ProfileFragment() : Fragment(R.layout.profile) < private val viewBinding: ProfileBinding by viewBinding() override fun onDestroyView() < super.onDestroyView() // Сбрасываем View из viewBinding >>

Но в итоге я получил состояние, что моя ссылка на ViewBinding внутри делегируемого property уже была почищена. Попытка перенести очистку кода до вызова super.onDestroyView() не принесла успеха и я начал копаться в причинах. Виновником стала реализация вызова методов жизненного цикла у Fragment.viewLifecycleOwner .

Событие ON_DESTROY в Fragment.viewLifecycleOwner происходит до вызова Fragment.onDestroyView() , поэтому FragmentViewBindingProperty очищался раньше, чем я того ожидал. Решением стало отложить вызов операции очистки. Все вызовы жизненного цикла вызываются последовательно и на главном потоке, поэтому весь фикс свелся к откладыванию очистки с помощью Handler :

class FragmentViewBindingProperty(. ) : ReadOnlyProperty  < internal var viewBinding: T? = null private inner class BindingLifecycleObserver : DefaultLifecycleObserver < private val mainHandler = Handler(Looper.getMainLooper()) @MainThread override fun onDestroy(owner: LifecycleOwner) < owner.lifecycle.removeObserver(this) mainHandler.post < viewBinding = null >> > >

Полный код можно найти здесь и использовать его на своих проектах.

Источник

Оцените статью