I want use 2-way dataBinding by LiveData. but change editText value not update textView that show user.name.
what is wrong by my code? I use android studio 3.3 canary 3 and enable data binding v2 in gradle.properties by this code:
android.databinding.enableV2=true
model data class:
data class User(var name: String)
viewModel class:
class MainViewModel : ViewModel() {
val user = MutableLiveData<User>()
init {
user.value = User("ali")
}
}
mainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
activityMainBinding.setLifecycleOwner(this)
activityMainBinding.viewModel = viewModel
}
}
<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.hoseinkelidari.databindingsample.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="#+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="8dp"
android:text="#={viewModel.user.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="#{viewModel.user.name}"
app:layout_constraintBottom_toTopOf="#+id/editText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Update:
I found solution at this link:
LiveData update on object field change
I set this change and it work:
class User : BaseObservable() {
#get:Bindable
var firstName: String? = null
set(name) {
field = firstName
notifyPropertyChanged(BR.firstName)
}
}
class MainViewModel : ViewModel() {
var user = CustomMutableLiveData<User>()
init {
user.value = User()
}
}
and add new
class CustomMutableLiveData<T : BaseObservable> : MutableLiveData<T>() {
internal var callback: Observable.OnPropertyChangedCallback = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
//Trigger LiveData observer on change of any property in object
value = value
}
}
override fun setValue(value: T?) {
super.setValue(value)
//listen to property changes
value!!.addOnPropertyChangedCallback(callback)
}
}
I think the problem is that you only change the value of user's name, not the value of LiveData, so textview is not changing according to it.
Take a look at some change in your code, and 2-way dataBinding is worked in this case.
Change the ViewModel
class MainViewModel : ViewModel() {
val user = MutableLiveData<String>()
init {
user.value = "Ali"
}
}
and change the layout
<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.hoseinkelidari.databindingsample.MainViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="#+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="8dp"
android:text="#={viewModel.user}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="#{viewModel.user}"
app:layout_constraintBottom_toTopOf="#+id/editText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
Related
I have a question about two-way data binidng implemented by MutableLiveData in ViewModel Class for and EditText.
If I define a LoginViewModel Class for a user, which is consisted of User, email and password as follows:
class LoginViewModel : ViewModel() {
val user = MutableLiveData<User>()
}
and
data class User(var email: String, var password: String)
when I rotate the phone (configuration changes occurs), the data entered will be gone.
<?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="loginViewModel"
type="com.udacity.shoestore.screens.login.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/fragment_horizontal_margin"
android:paddingTop="#dimen/fragment_vertical_margin"
android:paddingRight="#dimen/fragment_horizontal_margin"
android:paddingBottom="#dimen/fragment_vertical_margin"
tools:context=".screens.login.LoginFragment">
<TextView
android:id="#+id/email_text"
style="#style/title_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/layout_margin"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/str_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/email_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:autofillHints=""
android:hint="#string/str_email_hint"
android:inputType="textEmailAddress"
android:text="#={loginViewModel.user.email}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/email_text" />
<TextView
android:id="#+id/password_text"
android:layout_width="0dp"
style="#style/title_style"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/str_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/email_edit" />
<EditText
android:id="#+id/password_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:autofillHints=""
android:hint="#string/str_password_hint"
android:inputType="textPassword"
android:text="#={loginViewModel.user.password}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/password_text" />
<Button
android:id="#+id/sign_up_button"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginEnd="#dimen/small_margin"
android:text="#string/str_sign_up"
app:layout_constraintBaseline_toBaselineOf="#+id/sign_in_button"
app:layout_constraintEnd_toStartOf="#+id/sign_in_button"
app:layout_constraintStart_toStartOf="#+id/password_edit" />
<Button
android:id="#+id/sign_in_button"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="#dimen/small_margin"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginBottom="#dimen/medium_margin"
android:text="#string/str_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/password_edit"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/sign_up_button"
app:layout_constraintTop_toBottomOf="#+id/password_edit"
app:layout_constraintVertical_bias="0.524" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I wonder if there is something wrong about defining the User data class or using it in ViewModel or something else, but it doesn't work.
On the other hand, if I define the elemnts of user seperately in ViewModel, it works:
class LoginViewModel : ViewModel() {
val email = MutableLiveData<String>()
val password = MutableLiveData<String>()
}
and of course some changes in xml:
android:text="#={loginViewModel.email}"
and
android:text="#={loginViewModel.password}"
any idea?
My ShoeStore project in GitHub
I use this exact same implementation in my app.
Did you set binding lifecycleOwner in your activity or fragment ?
When using viewmodels, this is actually how I do it.
class LoginViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
}
In my fragment onCreateView
override
fun onCreateView(...){
val binding = FragmentBinding.inflate(...)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
...
}
I'll check your project on GitHub and let you know if anything...
Sorry for the late reply. I was busy playing with Jetpack Compose ;)
In my app I'm using custom view containing a TextInputLayout with the following code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout"
style="#style/UbiInput2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="-"
android:orientation="horizontal"
app:boxBackgroundColor="#color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
And the kotlin
class TextInputView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
var errorMessage: String? = ""
init {
inflate(context, R.layout.text_input_view, this)
val attributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputView)
val inputLayout = findViewById<TextInputLayout>(R.id.input_layout)
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputLayout.hint = attributes.getString(R.styleable.TextInputView_hint)
inputValue.hint = attributes.getString(R.styleable.TextInputView_placeholderText)
inputLayout.isExpandedHintEnabled =
attributes.getBoolean(R.styleable.TextInputView_expandedHintEnabled, true)
errorMessage = attributes.getString(R.styleable.TextInputView_errorMessage)
inputLayout.isHelperTextEnabled = false
inputValue.inputType =
attributes.getInt(
R.styleable.TextInputView_android_inputType,
InputType.TYPE_CLASS_TEXT
)
if (attributes.hasValue(R.styleable.TextInputView_android_maxLength)) {
inputValue.filters += InputFilter.LengthFilter(
attributes.getInt(
R.styleable.TextInputView_android_maxLength,
100
)
)
}
inputValue.gravity =
attributes.getInt(R.styleable.TextInputView_android_gravity, Gravity.START)
if (attributes.getBoolean(R.styleable.TextInputView_android_gravity, false)) {
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
if (attributes.getBoolean(R.styleable.TextInputView_helperTextEnabled, false)) {
inputLayout.isHelperTextEnabled = true
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
inputLayout.startIconDrawable =
attributes.getDrawable(R.styleable.TextInputView_startIconDrawable)
attributes.recycle()
}
}
#BindingAdapter("textValue")
fun TextInputView.setTextValue(value: String?) {
value?.let {
setValue(value)
}
}
#InverseBindingAdapter(attribute = "textValue")
fun TextInputView.getTextValue(): String {
return value()
}
#BindingAdapter("textValueAttrChanged")
fun TextInputView.setListener(textAttrChanged: InverseBindingListener) {
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputValue.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
textAttrChanged.onChange()
}
})
}
I have the following screen which go to the next fragment on button click. Using Navigation component.
But from the next screen, when I'm pressing back button to come back to the search form, the TextInputLayout values for hint, and helperText are all the same.
The only place I set those is inside the custom view. And from debugging I can see that all the correct values are set at that time.
I'm a bit out of ideas about what's going on there. Any hints would be appreciated.
Material library version used: 1.3.0-alpha04
All codebase has been migrated to view binding according the latest changes
The View is used as followed:
<?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="viewModel"
type="com.ubigo.features.v2.products.taxi.SearchViewModel" />
<variable
name="dateFormat"
type="com.ubigo.ubicore.date.UbiDate" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/rental_search_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background">
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_form"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView10">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/pickup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:hint="#string/product_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.pickupDateTime)}" />
<View
android:id="#+id/divider6"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/pickup" />
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/dropoff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:hint="#string/product_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider6"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.destinationDatetime)}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.ubigo.ubicore.ui.LoaderButton
android:id="#+id/rental_search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:text="#string/product_rental_show_car"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_location" />
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_location"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_form">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/user_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:helperText="#string/product_rental_location"
app:helperTextEnabled="true"
app:hint="#string/product_pickup_location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_location_on_black_24dp"
app:textValue="#{viewModel.depAddress}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="#+id/textView10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#string/product_rental_weekend"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And the initialization of the Fragment:
class RentalSearchFragment : Fragment() {
val viewModel: SearchViewModel by viewModel()
private val binding get() = _binding!!
private var _binding: FragmentRentalSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (_binding == null) {
_binding = FragmentRentalSearchBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.dateFormat = UbiDate()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I was able to solve this exact problem by implementing onSaveInstanceState, onRestoreInstanceState, dispatchSaveInstanceState, dispatchRestoreInstanceState, and setValues as described here
http://www.devexchanges.info/2016/03/custom-compound-view-in-android.html
The linked above goes into all the necessary detail about how to implement these functions. There's also undoubtedly countless other sources you can find about how to implement this android paradigm.
In my android app:
dataBinding {
enabled = true
}
in my activity:
class RegistrationActivity : RootActivity() {
private lateinit var viewBinding: RegistrationActivityBinding // use "registration_activity.xml"
private lateinit var registrationViewModel: RegistrationViewModel
private var wallet = Wallet()
companion object {
private val TAG = RegistrationActivity::class.java.name
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Debug.d(TAG, "onCreate: savedInstanceState = $savedInstanceState")
viewBinding = RegistrationActivityBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
viewBinding.setVariable(BR.model, wallet)
viewBinding.executePendingBindings()
init()
}
private fun init() {
viewBinding.buttonRegistration.setOnClickListener {
Debug.d(TAG, "initLogic: click_button, wallet = $wallet")
}
}
}
here my model:
class Wallet() : Serializable {
var email: String? = null
var password: String? = null
override fun toString(): String {
return "\nWallet(email = $email, password = $password)"
}
}
in xml:
<data>
<variable
name="model"
type="com.myproject.model.Wallet" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/common_color_bg">
<include
android:id="#+id/registrationToolbar"
layout="#layout/tool_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="#{#string/registration}" />
<EditText
android:id="#+id/editTextEmail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/default_margin"
android:hint="#string/email"
android:importantForAutofill="no"
android:inputType="textEmailAddress"
android:text="#{model.email}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/registrationToolbar" />
<EditText
android:id="#+id/editTextPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/default_margin"
android:hint="#string/password"
android:importantForAutofill="no"
android:inputType="textPassword"
android:text="#{model.password}"
app:layout_constraintEnd_toEndOf="#+id/editTextEmail"
app:layout_constraintStart_toStartOf="#+id/editTextEmail"
app:layout_constraintTop_toBottomOf="#+id/editTextEmail" />
<EditText
android:id="#+id/editTextPasswordRetype"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/default_margin"
android:hint="#string/retypePassword"
android:importantForAutofill="no"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="#+id/editTextEmail"
app:layout_constraintStart_toStartOf="#+id/editTextEmail"
app:layout_constraintTop_toBottomOf="#+id/editTextPassword" />
<com.google.android.material.button.MaterialButton
android:id="#+id/buttonRegistration"
android:layout_width="0dp"
android:layout_height="#dimen/button_height"
android:layout_margin="#dimen/default_margin"
android:layout_marginBottom="#dimen/default_margin"
android:text="#string/registration"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/editTextPasswordRetype" />
<include
android:id="#+id/progressBarLayout"
layout="#layout/progress_bar_layout"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
I input some email in editTextEmail and click button. As result call method:
viewBinding.buttonRegistration.setOnClickListener
Nice.
But here log:
click_button, wallet = Wallet(email = null, password = null)
As you can see the email is null
But I need pass email from xml to activity.
I use viewbinding. Why email not pass to activity via this:
android:text="#{model.email}"
?
You have to use two way binding to back data from View to ViewModel. Use #= to enable two way data binding in your xml like:
android:text="#={model.email}"
I have a question about how the visibility of an object in the ViewModel will change.
I'll tell you my case: I have a login interface that has two Edittexts and two buttons, a button and an Edittext are invisible by default, and I want the button that is visible to make the first Edittext and the button that I clicked invisible and make the second button and the second Edittext visible. And here's the problem, I could do all this in Activity, but I need to do it in ViewModel and I have no idea how to access the xml components from there.
I know all this is messy so I'll send the classes and if someone could tell me how to do this I'd appreciate it. Thank you. Thank you.
Login 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.quobis.sippo.ecco.viewmodel.LoginViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="LoginUser">
<ImageView
android:layout_width="250dp"
android:layout_height="200dp"
android:src="#drawable/logom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"
/>
<EditText
android:id="#+id/usr"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_user"
android:textSize="18sp"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
app:addTextChangedListener="#{viewModel.emailTextWatcher}"
/>
<Button
android:id="#+id/btn_usr"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:onClick="#{viewModel::onUserClicked}"
/>
<EditText
android:id="#+id/pass"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_pass"
android:textSize="18sp"
android:shape="rectangle"
android:inputType="textPassword"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
android:onClick="#{viewModel::onLoginClicked}"
/>
<Button
android:id="#+id/btn_pass"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
/>
<Spinner
android:id="#+id/spinner_usr"
android:layout_width="120dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.85"
/>
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel:
class LoginViewModel(private val listener: LoginResultCallbacks) :
ViewModel() {
private val user: UserModel
var userp ="jorge"
init {
this.user= UserModel(email = "")
}
fun emailTextWatcher(): TextWatcher {
return object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
user.setEmail(s.toString())
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
}
}
}
fun onUserClicked(v: View) {
}
fun onLoginClicked(v:View) {
if (user.getEmail() == userp)
listener.onSucces("Correcto")
else
listener.onError("Fallo")
}
}
Note: the method to change the visibility would be the onUserClicked.
Main activity:
class LoginUser : AppCompatActivity(), LoginResultCallbacks {
lateinit var binding: ActivityLoginUserBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login_user)
binding.viewModel = ViewModelProviders.of(this, LoginViewModelFactory(this)).get(LoginViewModel::class.java)
var languages = arrayOf("English", "EspaƱol", "Galego")
val spinner = binding.spinnerUsr
if (spinner != null) {
val arrayAdapter = ArrayAdapter(this, R.layout.spinner_item, languages)
spinner.adapter = arrayAdapter
}
}
override fun onSucces(message: String) {
Toast.makeText(this,"Login bueno", Toast.LENGTH_SHORT).show()
}
#SuppressLint("ResourceAsColor")
override fun onError(message: String) {
binding.btnUsr
btn_usr.setBackgroundColor(R.color.colorFailLogin)
}
}
You can use Android databinding. I will provide a rough example.
You need to create ObservableFields that your views in XML can bind to.
For example
val isVisible : ObservableField<Boolean> = ObservableField();
Then create a binding adapter
#BindingAdapter("customVisibility")
fun setVisibility(view : View, visible : Boolean) {
view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
Then bind in your XML
app:customVisibility="#{viewModel.isVisible}"
Then you can change the visibility of the views just by modifying the isVisible property of your view model.
I have wrote a primer in Android Databinding and Functional MVVM. You can check it for further details (written in Kotlin)
https://medium.com/tompee/android-data-binding-and-functional-mvvm-b311e4c98d
Good evening! I try to use binding in kotlin, but data aren't update. All compile and work, but when user change text and click bsave -> i try to return data from textEdit, and data is not updated. TextEdit contains old data. In java it is working, but in kotlin i have problem. Can you help me?
It is my method onCreate, here i create model and binding its.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_fblogin)
auth = FirebaseAuth.getInstance()
setContentView(R.layout.activity_fblogin)
user = User("Test02", "123456")
binding?.setVariable(BR.user, user)
binding?.executePendingBindings()
loginButton.setOnClickListener{_ -> loginToSystem()}
signIn.setOnClickListener{_ -> showSignInActivity()}
}
private fun loginToSystem() {
binding?.executePendingBindings()
//showProgressDialog()
val email = binding?.userName?.text.toString().trim()
val password = binding?.userPass?.text.toString()
val email2 = user?.login
val password2 = user?.password
if (!checkValidateForm(email, password)) {
return
}
It is main part of my 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>
<variable
name="user"
type="com.example.darkt.makeyouself.models.User"
/>
</data>
<android.support.constraint.ConstraintLayout
android:descendantFocusability="beforeDescendants"
android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
tools:context="com.example.darkt.makeyouself.activities.FBLogin"
tools:ignore="MissingConstraints">
<EditText
android:id="#+id/userName"
android:layout_width="220dp"
android:layout_height="43dp"
android:ems="10"
android:inputType="textPersonName"
android:text="#{user.login}"
app:layout_constraintBottom_toBottomOf="#+id/author"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Login"/>
<EditText
android:id="#+id/userPass"
android:layout_width="220dp"
android:layout_height="45dp"
android:layout_marginTop="25dp"
android:ems="10"
android:inputType="textPassword"
android:text="#{user.password}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/userName"
tools:layout_editor_absoluteX="74dp"
tools:layout_editor_absoluteY="245dp"
tools:text="Password"/>
<Button
android:id="#+id/signIn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="#color/green_main"
android:text="SignIn"
android:textColor="#color/white"
app:layout_constraintStart_toStartOf="#+id/userPass"
app:layout_constraintTop_toBottomOf="#+id/userPass" />
</android.support.constraint.ConstraintLayout>
</layout>
And my short model:
data class User (val login: String, val password: String)
How i can refresh data in binding after user changes?
I was find error! My mistake is duplicating setContentView:
binding = DataBindingUtil.setContentView(this, R.layout.activity_fblogin)
....
setContentView(R.layout.activity_fblogin)
When i was delete the second setContentView, i fixed my problem