Since Kotlin 1.4.20-M2 JetBrains no longer uses the Kotlin Android Extensions compiler plugin.
Actually this has been expected for a long time, in this commit , you can see in Replaced kotlinx synthetic with findViewById:
kotlinx.android.synthetic is no longer a recommended practice. Removing in favorite of explicit findViewById.
Reason?
There are some big problems with kotlinx synthetic:
- Revealing a global namespace consisting of ids that are not related to the layout is actually inflated without checking (for example, when having the same view ID in different layouts, when using it, it is possible to import the wrong view of another layout. error NullPointerException).
- Can only be used in Kotlin.
- Not really null safety when the layout is only available in some configurations (layout landscape, portrait …).
- Also, kotlinx synthetic may not work on many modules, there was an open issue from January 2018.
- All of these issues cause the API to increase the number of crashes for Android apps.
Alternatives?
- View Binding is recommended for interacting with View, but it does have a few more nice things when compared to Android Kotlin Extensions. Compared to Android Kotlin Extensions, it adds compile-time checkups for view lookups and type safety.
- the old traditional findViewById works for both Kotlin and Java.
JetBrains doesn’t use Kotlin Android Extensions in favor of View Binding, so we’ll explore how to switch to View Binding in this article.
View Binding
View Binding works with Java and Kotlin.
View Binding is a feature that allows you to write code to interact with the view more easily.
When the View Binding Inter view feature is enabled in a module, it creates a binding class for each XML layout file contained in that module. An instance of the binding class contains direct references to all the View IDs in the respective layout.
View Binding is not Null-safe for the layouts specified in multiple configurations. View binding will detect if a view is only available in some configurations and create a @Nullable
property.
Note: Don’t confuse View Binding with Data Binding
How to enable View binding?
You don’t need to include any additional libraries to enable View binding. It is integrated into the Gradle Android Plugin starting with versions provided in Android Studio 3.6. To enable this in the modules that will use it, add the following to your build.gradle
file.
1 2 3 4 5 6 7 | android <span class="token punctuation">{</span> <span class="token operator">..</span> <span class="token operator">..</span> buildFeatures <span class="token punctuation">{</span> viewBinding <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
If you want the layout file to be omitted when creating binding classes , add the tools: viewBindingIgnore = "true"
attribute tools: viewBindingIgnore = "true"
to the root view of that layout:
1 2 3 4 5 6 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> LinearLayout</span> <span class="token attr-name">...</span> <span class="token attr-name"><span class="token namespace">tools:</span> viewBindingIgnore</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> true <span class="token punctuation">"</span></span> <span class="token punctuation">></span></span> ... <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> LinearLayout</span> <span class="token punctuation">></span></span> |
How to use View Binding?
If view binding is enabled for a module, a binding class will be generated for each XML layout file that the module contains. Each binding class contains references to the root view and all views with IDs.
The name of the binding class is created by converting the name of the XML file to Pascal case and adding the word Binding
at the end, for example, the file named result_profile.xml
, the binding class will be named ResultProfileBinding
Use View Binding in Activities
1 2 3 4 5 6 7 8 9 | <span class="token keyword">private</span> <span class="token keyword">lateinit</span> <span class="token keyword">var</span> binding <span class="token operator">:</span> ResultProfileBinding <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreate</span> <span class="token punctuation">(</span> savedInstanceState <span class="token operator">:</span> Bundle <span class="token operator">?</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">onCreate</span> <span class="token punctuation">(</span> savedInstanceState <span class="token punctuation">)</span> binding <span class="token operator">=</span> ResultProfileBinding <span class="token punctuation">.</span> <span class="token function">inflate</span> <span class="token punctuation">(</span> layoutInflater <span class="token punctuation">)</span> <span class="token keyword">val</span> view <span class="token operator">=</span> binding <span class="token punctuation">.</span> root <span class="token function">setContentView</span> <span class="token punctuation">(</span> view <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
You can then access the Views using the binding
object
1 2 | bind <span class="token punctuation">.</span> name <span class="token punctuation">.</span> text <span class="token operator">=</span> <span class="token string">"String gì đó ..."</span> |
Use View Binding in Fragments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">private</span> <span class="token keyword">var</span> _binding <span class="token operator">:</span> ResultProfileBinding <span class="token operator">?</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token comment">// This property is only valid between onCreateView and</span> <span class="token comment">// onDestroyView.</span> <span class="token keyword">private</span> <span class="token keyword">val</span> binding <span class="token keyword">get</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=</span> _binding <span class="token operator">!!</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreateView</span> <span class="token punctuation">(</span> inflater <span class="token operator">:</span> LayoutInflater <span class="token punctuation">,</span> container <span class="token operator">:</span> ViewGroup <span class="token operator">?</span> <span class="token punctuation">,</span> savedInstanceState <span class="token operator">:</span> Bundle <span class="token operator">?</span> <span class="token punctuation">)</span> <span class="token operator">:</span> View <span class="token operator">?</span> <span class="token punctuation">{</span> _binding <span class="token operator">=</span> ResultProfileBinding <span class="token punctuation">.</span> <span class="token function">inflate</span> <span class="token punctuation">(</span> inflater <span class="token punctuation">,</span> container <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token keyword">val</span> view <span class="token operator">=</span> binding <span class="token punctuation">.</span> root <span class="token keyword">return</span> view <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onDestroyView</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">onDestroyView</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> _binding <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">}</span> |
You can then access views using binding
same way as Activity.
1 2 3 | binding <span class="token punctuation">.</span> name <span class="token punctuation">.</span> text <span class="token operator">=</span> viewModel <span class="token punctuation">.</span> name binding <span class="token punctuation">.</span> button <span class="token punctuation">.</span> <span class="token function">setOnClickListener</span> <span class="token punctuation">{</span> viewModel <span class="token punctuation">.</span> <span class="token function">userClicked</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Note:
- The inflate () method needs to pass an inflater layout. If the layout has been inflated, you can use the static bind () method of the binding class .
- Fragments last longer than its view. Make sure you remove any references to the
binding class instance
in Fragment’s onDestroyView () method.
About Parcelize feature in Kotlin Android Extensions?
The Parcelize feature in Kotlin is part of the kotlin-android-extensions
plugin compiler, so deleting the plugin will prevent all of your Parcelable classes from compiling if they depend on the Parcelize annotation
.
JetBrains extracted Parcelize from Kotlin Android Extensions into a new plugin, kotlin-parcelize
First, you’ll need to add the kotlin-parcelize
plugin to your modules.
1 2 3 4 5 | plugins <span class="token punctuation">{</span> <span class="token operator">..</span> <span class="token operator">..</span> id <span class="token string">'kotlin-parcelize'</span> <span class="token punctuation">}</span> |
Then, change the old import statement from
1 2 | <span class="token keyword">import</span> kotlinx <span class="token punctuation">.</span> android <span class="token punctuation">.</span> parcel <span class="token punctuation">.</span> Parcelize |
Fort
1 2 | <span class="token keyword">import</span> kotlinx <span class="token punctuation">.</span> parcelize <span class="token punctuation">.</span> Parcelize |
You can find this IDE error
1 2 | Class 'abc..' is not abstract and does not implement abstract member public abstract fun describeContents(): Int defined in android.os.Parcelable |
Forget it, the app is still building normally, this is a bug that has been reported on youtrack and has been marked as fixed.
Summary
Here’s what you should do to move from the kotlin-android-extensions
plugin to ViewBinding
and the kotlin-parcelize
plugin:
- Remove
kotlin-android-extensions
plugin from yourbuild.gradle
file. - Delete all kotlin synthetic imports from your activities and fragments.
- Enable
viewBinding
inbuild.gradle
in your module. - Add the binding object in your activities and fragments and use it to set the content view and access the views from your xml file.
- If you are using
Parcelize
annotation, add pluginskotlin-parcelize
to filebuild.gradle
in your module and change the import statement as specified above.