Data Binding - Cannot call function from a layout file - android

I'm trying to call a function from my Data Binding layout, but I'm always receiving some error. I'm trying to set the text on my textView using MyUtilClass's function which I have created. here's my code:
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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.Companion.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MyUtilClass
class MyUtilClass {
companion object {
#JvmStatic
fun changeText(text: String): String {
return text
}
}
}
User
data class User(
val firstName: String,
val lastName: String,
val age: Int,
val loggedIn: Boolean
)
MainActivity.java
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val myUser = User("John", "Doe", 25, true)
binding.user = myUser
}
}
Error:
C:\Users\Stefan\AndroidStudioProjects\TestApp\app\build\generated\source\kapt\debug\com\example\testapp\DataBinderMapperImpl.java:9:
error: cannot find symbol import
com.example.testapp.databinding.ActivityMainBindingImpl;
^ symbol: class ActivityMainBindingImpl location: package
com.example.testapp.databinding
cannot find method changeText(java.lang.String) in class
com.example.testapp.MyUtilClass.Companion Open File

Adding JvmStatic to the changeText() method in MyUtilClass automatically makes it static.
Therefore, you can access it like this in your layout file:
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
You can check this link to find out more:
Kotlin DataBinding pass static function into layout xml

This is the change I made, and it worked. I basically removed class keyword and added object instead.
object MyUtilClass {
#JvmStatic
fun changeText(text: String): String {
return text
}
}

Related

Two Way Binding on Double Android

I read a few posts to convert from String to Integer and tried to do the same to convert string to double using two-way binding but was unable to do it.
SampleViewModel.kt
class SampleViewModel: ViewModel() {
val weight = MutableLiveData<Double>()
fun validateFields() {
Log.i("SAMPLE_VIEW_MODEL", "validateFields: ${weight.value}")
}
}
TypeConverters.kt
object TypeConverters {
#InverseMethod("stringToDouble")
#JvmStatic
fun doubleToString(value: Double?): String {
return value?.toString() ?: ""
}
#JvmStatic
fun stringToDouble(value: String): Double? {
if (TextUtils.isEmpty(value)) {
return null
}
return value.toDoubleOrNull()
}
}
fragment_sample.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.example.sampleapp.SampleViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/til_weight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/_10sdp"
android:hint="#string/hint_weight"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#+id/tilName">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_weight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={TypeConverters.doubleToString(viewModel.weight)}"/>
</com.google.android.material.textfield.TextInputLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The example above does not let me enter the proper value on UI like "56.78". I tried to follow this post but doesn't work for me. Another way is to take string value and then convert it into double and vice versa accordingly. I would like to know which is the correct way.
In your TextInputEditText, update this line
android:text="#={String.valueOf(viewModel.weight)}"
See Results
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_weight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={String.valueOf(viewModel.weight)}"/>

Android binding adapter doesn't change after property changed

I write a code for a simple dice roll app but there is a problem in updating the image of an image view that bound with a binding adapter
I can't extend BaseObservable class because I already extended the ViewModel class. I need that for keeping data after the status changed. Also, I can't observe the live data inside my binding adapter because the function is static
do you know any other way?
This is my xml code
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.diceroller.MainActivityViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/imgDice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:imageRes="#{viewModel.drawableResource}" />
<Button
android:id="#+id/btnRoll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="Roll the dice"
android:onClick="#{() -> viewModel.onRollButtonClicked()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
and this is my kotlin code
class MainActivityViewModel : ViewModel() , Observable {
#Bindable
var drawableResource : MutableLiveData<Int> = MutableLiveData<Int>()
init {
drawableResource.value = R.drawable.dice_1
}
fun onRollButtonClicked(){
drawableResource.value = when(Random.nextInt(1,7)){
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
}
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
override fun removeOnPropertyChangedCallback(callback:
Observable.OnPropertyChangedCallback?) {
}
}
#BindingAdapter("android:imageRes")
fun loadImage(view : View , imageRes : Int){
(view as ImageView).setImageResource(imageRes)
}

Why can I bind either String or LiveData<string> varaiable to android:text in Android Studio?

I'm learning data binding, the following code is from the project.
The android:text in plain_activity_solution_3.xml bind to SimpleViewModel with name which is String.
The android:text in solution.xml bind to SimpleViewModelSolution with name which is LiveData<String>.
Why can either String or LiveData<string> be bind to android:text? In my mind , only one is allowed to be bind to android:text.
SimpleViewModel.kt
class SimpleViewModel : ViewModel() {
val name = "Grace"
val lastName = "Hopper"
var likes = 0
private set // This is to prevent external modification of the variable.
...
}
plain_activity_solution_3.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/plain_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="128dp"
android:text="#{viewmodel.name}"
...
}
SimpleViewModelSolution.kt
class SimpleViewModelSolution : ViewModel() {
private val _name = MutableLiveData("Ada")
private val _lastName = MutableLiveData("Lovelace")
private val _likes = MutableLiveData(0)
val name: LiveData<String> = _name
val lastName: LiveData<String> = _lastName
val likes: LiveData<Int> = _likes
...
}
solution.xml
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.example.android.databinding.basicsample.data.SimpleViewModelSolution"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- A simple binding between a TextView and a string observable in the ViewModel -->
<TextView
android:id="#+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="128dp"
android:text="#{viewmodel.name}"
...
}
As the document says:
Any plain-old object can be used for data binding, but modifying the object doesn't automatically cause the UI to update. Data binding can be used to give your data objects the ability to notify other objects, known as listeners, when its data changes.
LiveData<string> is observable, too.
This is by design, and discussed in documentation. The binder will accept either the native data type, or something that's "Observable", like LiveData. If an observable object is provided, the binder will subscribe to the object's changes and bind those to the view, saving you lines of code.

BindingAdpater is not working at recyclerview

I set up BindingAdpater at recyclerview items. But when I get from data, it doesn't not work.
as you see below, myItem is working. But not working at items that is in parameter. I don't understand why items(in parameter) is not working.
BindingAdapter
#BindingAdapter("app:items")
fun setItems(view: RecyclerView, items: List<LaunchListQuery.Launch>?) {
var myItem = arrayListOf<LaunchListQuery.Launch?>()
myItem.add(LaunchListQuery.Launch("","Id : "+1,"Site : "+1,null))
myItem.add(LaunchListQuery.Launch("","Id : "+2,"Site : "+2,null))
myItem.add(LaunchListQuery.Launch("","Id : "+3,"Site : "+3,null))
(view.adapter as MainAdapter).submitList(items)
}
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"
xmlns:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewmodel"
type="com.haii.graphqldemo.main.MainViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/main_layout">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:items="#{viewmodel.items}"/>
</LinearLayout>
</layout>
ViewModel
class MainViewModel #Inject constructor(
private val repository: DefaultRepository
) : ViewModel() {
private var _items :MutableLiveData<List<LaunchListQuery.Launch?>> = MutableLiveData()
val items : LiveData<List<LaunchListQuery.Launch?>> = _items
fun getItems(){
viewModelScope.launch {
val items = repository.getLaunchList()
_items.value = items.launches
}
}
}
I solved this by adding
onCreate at Activity
binding.lifecycleOwner = this
onActivityCreated at Fragment
binding.lifecycleOwner = this.viewLifecycleOwner

How to access a button in included layout using data binding

I am using databinding in my application but button is not working. where am i making mistake in this code. i tried many solution but no luck. but if make button in activity_login.xml then button click works. i think i am not able to pass the view model to the included view.
Here is my code
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name ="loginViewModel"
type="com.innowi.checoutrestaurantdashboard.view.main.MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/login_constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/login_background_burgerstack"
android:isScrollContainer="false"
android:paddingEnd="32dp"
android:paddingStart="32dp"
tools:context=".view.main.LoginActivity">
<include
android:id="#+id/login_layout"
layout="#layout/layout_login"
android:layout_width="0dp"
android:layout_height="0dp"
bind:loginViewModel="#{loginViewModel}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
</layout>
layout_login.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 ="loginViewModel"
type="com.innowi.checoutrestaurantdashboard.view.main.MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
style="#style/Login"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:isScrollContainer="false"
android:padding="32dp">
<Button
android:id="#+id/login_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="#drawable/button_login_drawable"
android:enabled="false"
android:gravity="center"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:text="#string/button_sign_in"
android:textColor="#color/colorWhite"
android:textSize="24sp"
android:onClick="#{loginViewModel::onLoginButtonClick}"
app:layout_constraintLeft_toLeftOf="#+id/login_username"
app:layout_constraintRight_toRightOf="#+id/login_username"
app:layout_constraintTop_toBottomOf="#+id/login_password" />
.
.
.
.
</android.support.constraint.ConstraintLayout>
</layout>
MainViewModel.kt
class MainViewModel #Inject constructor(
private val loginRepository: LoginRepository
) : BaseViewModel() {
fun onLoginButtonClick(view : View){
performLogin()
}
.
.
.
}
LoginActivity.kt
class LoginActivity : AppBaseActivity() {
private lateinit var loginViewBinding: ActivityLoginBinding
private lateinit var viewModel: MainViewModel
private val TAG = LoginActivity::class.simpleName
override fun initViewModel(viewModelProvider: ViewModelProvider): BaseViewModel? {
viewModel = viewModelProvider.get(MainViewModel::class.java)
return viewModel
}
override fun render(state: ViewState) {
when (state) {
is LoadingState -> {
// loading
val loading = state.loading
Log.d(TAG,"Loading State")
}
is DefaultState -> {
// render Data
val data = state.data
Log.d(TAG,"Data State")
}
is ErrorState -> {
// show error
val error = state.error
Log.d(TAG,"Loading State")
}
}
}
override fun setContentView() {
loginViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginViewBinding.loginViewModel = viewModel
loginViewBinding.executePendingBindings()
}
}

Categories

Resources