Is BindingAdapter really saving our time - android

I am working on new project and just started using Databinding. I heard people talking abaout code reduction.I have a RecyclerView in fragment. I have a simple ConstraintLayout in which I have 3 TextViews. This is in my Adapter for RecyclerView.
That aditional +" nazv" is just a sample and I could use String Extension.
fun bind(item: Mkdo) {
binding.txtMkdoNaziv.text = item.nazv+" nazv"
binding.txtMkdoPost.text = item.post
binding.txtmkdoPostNaziv.text = item.postNaziv
}
ConstraintLayout for single RecyclerView row (deleted positioning properties)
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintRowMkdo"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/txtMkdoNaziv"/>
<TextView
android:id="#+id/txtMkdoPost"/>
<TextView
android:id="#+id/txtmkdoPostNaziv"/>
</androidx.constraintlayout.widget.ConstraintLayout>
This works just fine.
If I want to convert it to Databinding I have to add:
fun bind(item: Mkdo) {
binding.mkdo=item
binding.executePendingBindings()
}
And BindingAdapter
#BindingAdapter("setNazv")
fun TextView.setNazv(item:Mkdo?){
item?.let {
text="${item.nazv} nazv"
}
}
ConstraintLayout for single RecyclerView row (deleted positioning properties) with Databinding
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintRowMkdo"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/txtMkdoNaziv"
app:setNazv="#{mkdo}""/>
<TextView
android:id="#+id/txtMkdoPost"
android:text="#{mkdo.post}"/>
<TextView
android:id="#+id/txtmkdoPostNaziv"
android:text="#{mkdo.postNaziv}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
This makes constraintRowMkdo nonreusable and in my opinion adds more work than without it.
Also if I have some string formatting I need to have it on two places string Extension and BindingAdapter. I can reuse Extension but it is still on who places.
What are your toughts on this?
Am I missing something?
Are there any aditional benefits of using it?
Thanks

Related

Dynamically add fragments (or appropriate component) to a list view in Android App (Kotlin)

I am unsure on the theory on Kotlin and Android development (Still a novice). I have a fragment with a simple score keeping element to it. It displays a score and there is a + & - button to effect the score. Below is this fragments code:
ScoreKeepingBasicFragment.kt
class ScoreKeepingBasicFragment : Fragment() {
companion object {
fun newInstance() =
ScoreKeepingBasicFragment()
}
private lateinit var binding: ScoreKeepingBasicFragmentBinding
private val viewModel by viewModels<ScoreKeepingBasicViewModel> {
ScoreKeepingBasicViewModelFactory()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.score_keeping_basic_fragment,
container,
false
)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
}
ScoreKeepingBasicViewModel.kt
class ScoreKeepingBasicViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor().newInstance()
}
}
class ScoreKeepingBasicViewModel : ViewModel() {
var scoreText = MutableLiveData<String>("0")
private var scoreInt: Int = 0
fun addScore(){
println("ADD")
scoreInt++
scoreText.value = "$scoreInt"
}
fun minusScore(){
println("Minus")
scoreInt--
scoreText.value = "$scoreInt"
}
}
score_keeping_basic_fragment.xml
<layout>
<data>
<variable
name="viewModel"
type="com.michaelcodesthings.scorekeeper.viewmodel.ScoreKeepingBasicViewModel" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:orientation="horizontal">
<LinearLayout
android:id="#+id/minus_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
android:onClick="#{()->viewModel.minusScore()}">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:background="#drawable/left_rectangle"
android:src="#drawable/ic_remove"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="horizontal">
<TextView
android:id="#+id/score_list_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginVertical="8dp"
android:background="#drawable/center_rectangle"
android:gravity="center"
android:text="#{viewModel.scoreText}"
android:textAlignment="center"
android:textSize="30sp" />
</LinearLayout>
<LinearLayout
android:id="#+id/add_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
android:onClick="#{()->viewModel.addScore()}">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/right_rectangle"
android:src="#drawable/ic_add"
android:layout_margin="8dp"/>
</LinearLayout>
</LinearLayout>
</layout>
Now this fragment works exactly how I want it to, adding and taking away from a score. And works fine if I were to use the "include" tag in another fragment or activity.
But now this is where I'm lost on how Kotlin/Android Dev works. I want to have a list of these fragments on a screen, the user is able to press an "add score" button and another one of these gets added to the list, so would be dynamically adding scores to the list view. Each item in the list needs its own state and to work independently to the rest. Below is a diagram on roughly how I want it to look.
I've done some searching but have been reading you can't add fragments to list views, but haven't seem to find a solution to my problem. Any ideas on how to solve this?
Any help on how the logic works for this would be appreciated greatly. Thanks in advance for any help.
I've done some searching but have been reading you can't add fragments to list views, but haven't seem to find a solution to my problem. Any ideas on how to solve this?
Technically it is possible to add fragments to a list view with some workarounds. However, you should not be adding fragments to a list view.
That is not what list views are for. Also fragment are managed by the Activity through the FragmentManager not the list view. You should look into making custom views and use the adapters getViewTypeCount and getView to get different list item behavior.
Here is some documentation on now to make custom views.
https://developer.android.com/guide/topics/ui/custom-components
After you have a custom view, that should be what you add dynamically to your list view.

problems using databinding: val vs var and the use invalidateAll()

This is actually 2 questions.
I noticed that databinding doesn't work if in the Person data class I set the name parameter to be val instead of var. The code will break with the following error:
error: cannot find symbol
import com.example.android.aboutme.databinding.ActivityMainBindingImpl;
^
symbol: class ActivityMainBindingImpl
location: package com.example.android.aboutme.databinding
Why does it happen?
Why do I need to call invalidateAll() in doneClick()? The documentation says that it "Invalidates all binding expressions and requests a new rebind to refresh UI". Isn't the purpose of databinding to connect data and views in such a way that an update to the data immediately updates the views?
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val person = Person("Bob")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.person = person
binding.apply {
btnDone.setOnClickListener { doneClick(it) }
}
}
private fun doneClick(view: View) {
binding.apply {
person?.nickname = etNickname.text.toString()
invalidateAll()
etNickname.visibility = View.GONE
tvNickname.visibility = View.VISIBLE
btnDone.visibility = View.GONE
}
hideKeybord(view)
}
private fun hideKeybord(view: View) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}
Person:
class Person(var name: String, var nickname: String? = null)
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="person"
type="com.example.android.aboutme.Person" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="#dimen/padding"
android:paddingEnd="#dimen/padding">
<TextView
android:id="#+id/tv_name"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.name}"
android:textAlignment="center" />
<EditText
android:id="#+id/et_nickname"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/what_is_your_nickname"
android:inputType="textPersonName"
android:textAlignment="center" />
<Button
android:id="#+id/btn_done"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="#dimen/layout_margin"
android:fontFamily="#font/roboto"
android:text="#string/done" />
<TextView
android:id="#+id/tv_nickname"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.nickname}"
android:textAlignment="center"
android:visibility="gone" />
<ImageView
android:id="#+id/star_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/layout_margin"
android:contentDescription="#string/yellow_star"
app:srcCompat="#android:drawable/btn_star_big_on" />
<ScrollView
android:id="#+id/bio_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/layout_margin">
<TextView
android:id="#+id/bio_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="#dimen/line_spacing_multiplier"
android:text="#string/bio"
android:textAppearance="#style/NameStyle" />
</ScrollView>
</LinearLayout>
</layout>
Qustion 1:
I noticed that databinding doesn't work if in the Person data class I set the name parameter to be val instead of var.
Why does it happen?
Because you're using two-way databinding.
In your layout you have this:
<TextView
android:id="#+id/tv_name"
style="#style/NameStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.name}"
android:textAlignment="center" />
The #= in android:text="#={person.name}", specifically, tells databinding "I want to set the TextView's text to the person's name value and I want to update the person's name when the TextView text changes".
When you use the #= databinding will look for a setter for the attribute you're assingning. In this case, it's looking for a setter for the name attribute on the Person class. In Kotlin, this means having a property named name that is a var.
If you do not intend to update the person's name attribute when the TextView changes (which I assume you don't, you'd generally do that with an EditText), then change that line to just # (android:text="#{person.name}"). Then you can make name a val because you're only reading from it for databinding.
Question 2:
Why do I need to call invalidateAll() in doneClick()?
You actually don't ...
The documentation says that it "Invalidates all binding expressions and requests a new rebind to refresh UI". Isn't the purpose of databinding to connect data and views in such a way that an update to the data immediately updates the views?
Yes, but: databinding is not magic. If the UI is to update it must be told to do so and changing your data does not magically tell databinding that it has to update. Something has to tell databinding that a) it's time to update and b) what it needs to update.
So what you have right now with invalidateAll() is the shotgun approach. You updated the one nickname field and then you yelled at databinding "hey, update everything!", so it rebinds all views based on the current state of Person which of course includes "nickname" so that view gets updated.
What you want to do is update only the fields that are bound to nickname because that is the one thing that changed and, preferably, you want to do it automatically when nickname changes. For that, you need to observe the state of the nickname field and react to it changing.
You can do this in a few ways:
Use LiveData
In this approach you have the fields of the model you want to bind be LiveData objects (val nickname = MutableLiveData<String>()) and you add a LifeCycleOwner to the binding so it can observe the LiveData objects.
Databinding is set up to use LiveData so your xml does not need to change. But now the properties are observable and when you update the name on Person (person?.nickname?.value = "New Nickname") databinding will be notified automatically and will update the state of the associated view.
You will not have to call invalidateAll().
Use Observable Fields
This is conceptually the same as #1 but this came before LiveData was introduced. Nowadays you can consider this deprecated and use the LiveData approach, but I'll mention it for completeness.
Again, instead of having a regular property of type String you wrap that property in an observable data structure (val nickname = ObservableString()) that will notify databinding when the value has changed. Again, databinding is set up to work with this so you don't have to change your XML.
Use Observable Objects
With this option, you make your Person class (or preferably a ViewModel) extend Observable and manage notifying databinding yourself as the fields change. You would go this route if you have special logic that has to happen when updating some fields and a simple "set and notify" is not enough. This option is far more complicated and I'll leave it as an exercise to the reader to read the docs to see how this option works. For the vast majority of cases you should be able to do what you need with option #1.
Parting thought on this line:
person?.nickname = etNickname.text.toString()
If you set up databinding correctly, this should not be necessary. :)
If you set up etNickname to use two-way binding and make person.nickname properly observable, the person.nickname attribute will automatically update to the text value in etNickname when it changes!
That is the beauty of databinding.
Hope that helps!
Val = Inmutable
Var = mutable
Full answer
Val and Var in Kotlin
It's because the properties have no built-in mechanisms to notify the UI that they've changed. So you have to invoke it manually. A solution for this problem is using LiveData or MutableLiveData.

Passing LiveData as <layout> tag argument

I have quick question about my code. I'm writing an app using Android MVVM with LiveData. I want to create loading layout which is will be included in many views. Main goal is to have ability of passing live data representing if layout should be visible and what text info should be displayed with progress bar.
So far I created loading indicator layout, and definded two variables "indicatorVisibility" and "progressText". In attached code one of values is commented out. I created also BindingAdapters to set visibility and text on controls.
This is my layout with progress bar
<data>
<variable
name="indicatorVisibility"
type="android.arch.lifecycle.LiveData"/>
<!--<variable-->
<!--name="progressText"-->
<!--type="android.arch.lifecycle.LiveData"/>-->
</data>
<android.support.constraint.ConstraintLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/layout_loading_background"
>
<ProgressBar
android:id="#+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="#dimen/layout_loading_progress_bar_size"
android:layout_height="#dimen/layout_loading_progress_bar_size"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/textView7"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--android:text="#{progressText}"-->
<TextView
android:id="#+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="TextView"
android:textColor="#color/layout_loading_text"
android:textSize="#dimen/layout_loading_text_size"
app:layout_constraintBottom_toBottomOf="#+id/progressBar2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/progressBar2"
app:layout_constraintTop_toTopOf="#+id/progressBar2" />
</android.support.constraint.ConstraintLayout>
This is how i include it in fragment layout
<include layout="#layout/layout_loading_info"
app:indicatorVisibility="#{viewModel.isBusy}"
/>
And those are my bind adapters:
#BindingAdapter("android:visibility")
fun getVisibility(view: View, liveData: LiveData<Boolean>){
liveData.observe(view.getLifecycleOwner(), Observer {
view.visibility = if(it == true) View.VISIBLE else View.INVISIBLE
})
}
#BindingAdapter("app:text")
fun getText(view: TextView, liveData : LiveData<Int>)
{
liveData.observe(view.getLifecycleOwner(), Observer {
it?.let{
view.text = view.context.resources.getString(it)
}
})
}
So far I tried passing simple types like Integer and it works. The problem lays in LiveData. Even when I don't use variables inside included layout I get error (error message tells nothing).
I saw similar stack task [here] : Applying databinding adapter to include tag but they passed the whole viewModel, which is not a flexible enough solution for me.
I think you use the wrong name-space; for data-binding that should be bind:
<include
layout="#layout/layout_loading_info"
bind:indicatorVisibility="#{viewModel.isBusy}"/>
The data-type is LiveData<Boolean>; therefore you'd need to import LiveData and Boolean, in order to use them in a variable definition. The data-binding should look about like this:
<data class=".databinding.LiveDataBinding">
<import type="android.arch.lifecycle.LiveData"/>
<import type="java.lang.Boolean"/>
<variable name="indicatorVisibility" type="LiveData<Boolean>"/>
</data>
bind:viewModel="#{viewModel}" might in general be better than binding single values.

Make certain part of text bold using DataBinding

I want to make certain part of my text to bold whose value is set using DataBinding with ViewModel.
For e.g
If you are selected, you will pay $160 for your pair.
I am using strings resources
<string name="product_price">If you are selected, you will have to pay $%d for your pair.</string>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/spacing_xlarge"
android:layout_marginStart="#dimen/spacing_xlarge"
android:layout_marginBottom="#dimen/spacing_small"
android:text="#{#string/product_price(productPrice)}"
android:textColor="#color/button_tertiary"
android:visibility="#{productPrice > 0}"
style="#style/Body.Small"
/>
Currently passing product price using ViewModel with Binding by setting binding.setProductPrice(Object.getPrice())
I know the following solutions : But want to try using DataBinding
Using Html Text - But don't want to use it in code.
Using Different TextView in Horizontal Style. Setting styles as bold for that Product Price. - Really Bad Practice
Using SpannableString - But don't want to use it in code.
But all of the above solutions are workaround.
Question ::
Want to try DataBinding feature which can be used to style certain part of string. Just like SpannableString
Manipulate String in the Layout file using DataBinding
You have to create a BindingAdapter and SpannableStringBuilder .
Binding Adapter
object Util {
#BindingAdapter("main","secondText")
#JvmStatic
fun setBoldString(view: AppCompatTextView, maintext: String,sequence: String) {
view.text = Util.getBoldText(maintext, sequence)
}
#JvmStatic
fun getBoldText(text: String, name: String): SpannableStringBuilder {
val str = SpannableStringBuilder(text)
val textPosition = text.indexOf(name)
str.setSpan(android.text.style.StyleSpan(Typeface.BOLD),
textPosition, textPosition + name.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return str
}
}
XML
<android.support.v7.widget.AppCompatTextView
android:id="#+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:main="#{`you will pay $160 for your pair`}"
app:secondText="#{`$160`}"
android:textColor="#color/black"
android:textSize="22sp" />
May be it helps you.
As per #CommonsWare,
Tried by adding basic Html tag <string name="product_price">If you are selected, you will have to pay <![CDATA[<b>$%d</b>]]> for your pair.</string>
Layout File : Imported Html
<?xml version="1.0" encoding="utf-8"?>
<layout
<data>
<import type="android.text.Html"/>
<data>
<LinearLayout>
<android.support.design.widget.CoordinatorLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/spacing_xlarge"
android:layout_marginStart="#dimen/spacing_xlarge"
android:layout_marginBottom="#dimen/spacing_small"
android:text="#{Html.fromHtml(#string/product_price(productPrice))}"
android:textColor="#color/button_tertiary"
android:visibility="#{productPrice > 0}"
style="#style/Body.Small"
/>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
</layout>
You can use a binding adapter coupled with SpannableString. Once you define the binding adapter, you can reuse it in all your layout files.
#BindingAdapter({"mainText", "priceToFormat"})
public static void format(TextView textView, String mainText, float
productPrice){
//Use spannable string to format your text accordingly
textView.setText(formattedText);
}
You can pass these params in your layout file like this:
<TextView
.
.
app:mainText = "#{ priceText }"
app:priceToFormat = "#{ price }"/>
Good luck.
use BindingAdapter as mentioned below
#BindingAdapter("setBold")
#JvmStatic
public static void setBold(TextView view, boolean isBold) {
if (isBold) {
view.setTypeface(null, Typeface.BOLD);
} else {
view.setTypeface(null, Typeface.NORMAL);
}
}
And use it in xml as below :
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.value}"
app:setBold="#{model.isBold}"
/>
As Html.fromHtml signagure changed at API level 24 (Android N), the Android team introduced HtmlCompat for using the same signature within any api level.
So, you should use the HtmlCompat class:
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY);
To use it, you may include AndroidX core in your project's build.gradle:
implementation 'androidx.core:core:1.3.1'
Layout XML file:
<?xml version="1.0" encoding="utf-8"?>
<layout
<data>
<import type="androidx.core.text.HtmlCompat"/>
<data>
<LinearLayout>
<android.support.design.widget.CoordinatorLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/spacing_xlarge"
android:layout_marginStart="#dimen/spacing_xlarge"
android:layout_marginBottom="#dimen/spacing_small"
android:text="#{HtmlCompat.fromHtml(#string/product_price(productPrice),HtmlCompat.FROM_HTML_MODE_LEGACY)}"
android:textColor="#color/button_tertiary"
android:visibility="#{productPrice > 0}"
style="#style/Body.Small"
/>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
</layout>
// put this method in your model class where name is string variable which is set as per given api response
public Spanned getHtmlText(){
return Html.fromHtml("<b>" + name + "</b>");
}
// in xml use this where userList is variable name of model class.
android:text="#{userList.htmlText}"

Programmatically inflated layout with Kotlin Android Extensions

I have a following layout:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#android:color/white"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:id="#+id/tvErrorTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="#android:color/background_dark"
android:textSize="18sp"
/>
<TextView
android:id="#+id/tvErrorDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textColor="#android:color/darker_gray"
android:textSize="16sp"
/>
<TextView
android:id="#+id/tvAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginBottom="10dp"
android:layout_gravity="end"
android:padding="5dp"
android:textSize="15sp"
android:textStyle="bold"
android:textAllCaps="true"
android:textColor="#android:color/holo_purple"
/>
</LinearLayout>
When I want to use [kotlin android extensions][1] outside of activity like below, it doesn't work. I ended up doing findViewById.
...
...
import kotlinx.android.synthetic.main.dialog_error.*
...
...
val view = LayoutInflater.from(context).inflate(R.layout.dialog_error, null, false)
val tvErrorTitle = view.findViewById(R.id.tvErrorTitle) as TextView
val tvErrorDesc = view.findViewById(R.id.tvErrorDesc) as TextView
val tvErrorAction = view.findViewById(R.id.tvAction) as TextView
It doesn't pull the views directly from xml. How to use it in programmatically inflated layout and avoid findViewById?
Note : This question strictly belongs to [Kotlin Android Extensions][1], not the language itself.
Edit
I have imported both :
import kotlinx.android.synthetic.main.dialog_error.view.*
import kotlinx.android.synthetic.main.dialog_error.*
But Android Studio still tries to import from R.id and doesn't recognize those two imports. Is there anything missing?
[1]: https://kotlinlang.org/docs/tutorials/android-plugin.html
From the docs you linked:
If we want to call the synthetic properties on View (useful in adapter classes), we should also import
kotlinx.android.synthetic.main.activity_main.view.*.
That is, import kotlinx.android.synthetic.main.layout.view.* as well to load the View extension properties.
Then:
val view = LayoutInflater.from(context).inflate(...)
view.tvErrorTitle.text = "test"
It returns a view inflated:
layoutInflater.inflate(R.layout.your_layout, null)
See, you can replace this LayoutInflater.from(context) with this layoutInflater when your class extend from a Context superclass
In kotlin you can try this to inflate layout inside linear layout using databinding
val inflater: LayoutInflater = LayoutInflater.from(activity).context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val mBindingDeno: LayoutDenominationBinding =
DataBindingUtil.inflate(
inflater, R.layout.layout_denomination, null, false
)
layout.addView(mBindingDeno.root)
Here layout is your LinearLayout
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:nestedScrollingEnabled="true"
android:visibility="gone">
<LinearLayout
android:id="#+id/linear_denomination"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</ScrollView>
Migrate from Kotlin synthetics to Jetpack view binding:
Kotlin Android Extensions is deprecated, which means that using Kotlin synthetics for view binding is no longer supported. If your app uses Kotlin synthetics for view binding, use this guide to migrate to Jetpack view binding.
If your app does not already use Kotlin synthetics for view binding, see View binding for basic usage information.
Like Android Extensions, Jetpack view binding is enabled on a module by module basis. For each module that uses view binding, set the viewBinding build option to true in the module-level build.gradle file:
android {
...
buildFeatures {
viewBinding true
}
}
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:
Call the static inflate() method included in the generated binding class. This creates an instance of the binding class for the activity to use.
Get a reference to the root view by either calling the getRoot() method or using Kotlin property syntax.
Pass the root view to setContentView() to make it the active view on the screen.
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
You can now use the instance of the binding class to reference any of the views:
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
For more information: https://developer.android.com/topic/libraries/view-binding#activities

Categories

Resources