two-way data-binding for edit-text, and LiveData ViewModel Class - android

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 ;)

Related

How to Intialize the Binding Properties in the Fragment to Make Two Way Data Binding Work

So I'm doing a project and I'm completely lost. I have seen how to do data binding with TextViews but I am being asked to do it with EditText Views with Two Way Data Binding. I got up to here so far with it.
The XML 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>
<variable
name="myShoe"
type="com.udacity.shoestore.product.Shoe" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimary">
<TextView
android:id="#+id/title_detail_view"
style="#style/title_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="#string/add_shoe_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/shoe_name"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ems="10"
android:hint="#string/shoe_name_string"
android:inputType="text"
android:textSize="30sp"
android:text="#={myShoe.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_detail_view" />
<EditText
android:id="#+id/shoe_size"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/size_string"
android:inputType="number|numberDecimal"
android:textSize="15sp"
android:text="#={myShoe.size}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shoe_name" />
<EditText
android:id="#+id/company_name"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/company_string"
android:inputType="text"
android:textSize="15sp"
android:text="#={myShoe.company}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shoe_size" />
<EditText
android:id="#+id/shoe_description"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/description_string"
android:inputType="text"
android:textSize="15sp"
android:text="#={myShoe.description}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/company_name" />
<Button
android:id="#+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#color/colorPrimaryDark"
android:text="#string/cancel_string"
android:textColor="#android:color/white"
app:layout_constraintBaseline_toBaselineOf="#+id/savee_button"
app:layout_constraintEnd_toStartOf="#+id/savee_button"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/savee_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="88dp"
android:backgroundTint="#color/colorPrimaryDark"
android:text="#string/save_string"
android:textColor="#android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shoe_description" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I was told to implement it to a fragment and it should work. But I'm not sure how. Here's the fragment
class ShoeDetailsFragment : Fragment() {
private val viewModel: ActivityViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentShoeDetailsBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_shoe_details,
container, false
)
//initializing the button and clearing the views once canceled
binding.cancelButton.setOnClickListener { v: View ->
v.findNavController().navigateUp()
binding.shoeName.text.clear()
binding.shoeSize.text.clear()
binding.companyName.text.clear()
binding.shoeDescription.text.clear()
}
//initializing the button and saving the info to transfer to the shoeList
binding.saveeButton.setOnClickListener { v: View ->
v.findNavController().navigateUp()
val name = shoe_name.text.toString()
val size = shoe_size.text.toString()
val brand = company_name.text.toString()
val details = shoe_description.text.toString()
viewModel.addShoe(name, size, brand, details)
}
return binding.root
}
}
I am open to any ideas to initialize binding properties so i can use it in both the layout and the fragment. Or am I looking at this the wrong way?
P.S. The XML File is being represented in this fragment
I also did this project for my NanoDegree.
Inside my ViewModel I created 3 variables for each EditText:
MutableLiveData - to update the value inside the viewModel
LiveData to expose value outside viewModel e.g. in a fragment (you won't really need this)
Public Variable to monitor value of MutableLiveData and expose this to your xml thus achieving the 2-Way Binding.
Then I and would create a Shared ViewModel to share data between ShoeDetailsFragment and ShoeListingFragment .
Inside the SharedViewModel
I created 3 variables for each EditText (this is just the first 2 Edittexts):
class MySharedViewModel : ViewModel() {
private val _name = MutableLiveData<String>()
val name: LiveData<String>
get() = _name
var edShoeName = ""
private val _size = MutableLiveData<Double>()
val size: LiveData<Double>
get() = _size
var edSize = ""
......}
For the xml I did exactly what you have done but used the 3rd variable for the 2-Way Data Binding:
<EditText
android:id="#+id/shoe_name"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ems="10"
android:hint="#string/shoe_name_string"
android:inputType="text"
android:textSize="30sp"
android:text="#={mySharedViewModel.edShoeName}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_detail_view" />
<EditText
android:id="#+id/shoe_size"
style="#style/login_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/size_string"
android:inputType="number|numberDecimal"
android:textSize="15sp"
android:text="#={mySharedViewModel.edCompany}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shoe_name" />
I have seen you have included this line of code on your ShoeDetailFragment code:
binding.saveeButton.setOnClickListener { v: View -> ....}
In my case I did it inside SharedViewModel instead:
fun onSaveButtonClick() {
//check value entered for size and set it to 0 if blank
if (edSize == "") {
edSize = "0"
}
//update MutableLiveData with values read live from the EditText
_name.value = edShoeName
_size.value = edSize.toDouble()
//save shoeObject to the _shoeList MutableLiveData
_shoeList.value?.add(Shoe(edShoeName, edSize.toDouble(), edCompany, edDescription))
}
Using DataBinding I moved the onClick bit to xml:
<Button
android:id="#+id/buttonSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="75dp"
android:layout_marginBottom="75dp"
android:background="#drawable/custom_button_background"
android:onClick="#{()->sharedViewModel.onSaveButtonClick()}"
You can also refer to my project.

BindingImpl class for fragment not generated when using data binding onClick expression

In my Fragments layout I have a button which is using the XML onClick expression to call a method in the corresponding ViewModel.
The problem is that upon building the project, the compiler throws following error:
error: cannot find symbol
import com.aresid.myapp.databinding.FragmentLoginBindingImpl;
^
symbol: class FragmentLoginBindingImpl
location: package com.aresid.myapp.databinding
The thing is that, when I remove the parameters from the functions signature and the functions call in the XML, the app builds without problems.
My expectation is that, when I click the login button, the method in the ViewModel would be called, logging the emailField.text and passwordField.text.
The 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>
<variable
name="loginViewModel"
type="com.aresid.myapp.login.LoginViewModel"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/fragmentPaddingMedium"
tools:context=".login.LoginFragment"
>
<ImageView
android:id="#+id/app_logo"
android:layout_width="#dimen/imageViewAppLogoSize"
android:layout_height="#dimen/imageViewAppLogoSize"
android:layout_marginTop="4dp"
android:contentDescription="#string/my_app_logo"
android:src="#drawable/my_app_logo_144"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/email_field_layout"
style="#style/Widget.MyApp.TextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
app:layout_constraintTop_toBottomOf="#+id/app_logo"
>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/email_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/password_field_layout"
style="#style/Widget.MyApp.TextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
app:layout_constraintTop_toBottomOf="#+id/email_field_layout"
app:passwordToggleEnabled="true"
>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/password_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/password"
android:imeActionId="6"
android:imeActionLabel="#string/common_signin_button_text"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="#+id/login_button"
style="#style/Widget.MyApp.ContainedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/log_in"
android:onClick="#{() -> loginViewModel.onLoginButtonClicked(emailField.text, passwordField.text)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/password_field_layout"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="13dp"
android:text="#string/sign_up_using_these_colon"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="#+id/signup_button_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<LinearLayout
android:id="#+id/signup_button_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="13dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/email_signup_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6.5dp"
android:background="#drawable/round_button_background"
android:src="#drawable/ic_email_24dp"
/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/google_signup_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6.5dp"
android:background="#drawable/round_button_background"
android:src="#drawable/ic_google_favicon_24dp"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The corresponding ViewModel:
package com.aresid.myapp.login
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import timber.log.Timber
/**
* Created on: 27/04/2020
* For Project: MyApp
* Author: René Spies
* Copyright: © 2020 ARES ID
*/
class LoginViewModel: ViewModel() {
// LiveData for the login email
private val _email = MutableLiveData<String>()
val email: LiveData<String>
get() = _email
// LiveData for the login password
private val _password = MutableLiveData<String>()
val password: LiveData<String>
get() = _password
// LiveData for the login button
private val _wantLogin = MutableLiveData<Boolean>()
val wantLogin: LiveData<Boolean>
get() = _wantLogin
init {
Timber.d("init: called")
// TODO: init: init objects here
// Init email LiveData
_email.value = ""
// Init password LiveData
_password.value = ""
// Init wantLogin LiveData
_wantLogin.value = false
}
fun onLoginButtonClicked(email: String, password: String) {
Timber.d("onLoginButtonClicked: called")
Timber.d("email = $email\npassword = $password")
// TODO: onLoginButtonClicked: log in user
}
}
And the Fragments onCreateView():
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Timber.d("onCreateView: called")
// Define LoginViewModel
loginViewModel = ViewModelProvider(this).get(LoginViewModel::class.java)
// Define FragmentLoginBinding and inflate the layout
binding = FragmentLoginBinding.inflate(inflater, container, false)
// TODO: onCreateView: code goes here
// Let the data binder know about the LoginViewModel
binding.loginViewModel = loginViewModel
// Return the inflated layout
return binding.root
}
Try this:
Use Two-way Databinding for your e-mail and password. By this your ViewModel fields will change automatically after user changes them in UI:
android:text="#={viewmodel.password}"
Your function "onLoginButtonClicked" can now be declared without parameters (these values are hold now as fields of ViewModel so you can get them freely inside function).
In your xml change the onClick as well:
android:onClick="#{() -> loginViewModel.onLoginButtonClicked()}"

2-way databinding not work by liveData

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>

Kotlin android. binding not update data

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

MVVM notify View about loading state

I’m using LiveData by Google now and they recomend to use a MVVM patter design. For some of my requests I use RxJava2, and listen for responses in SubscribeWith(…).
For example, when I press a button to send some data to the remote data source, I’m showing some loading animation and want to hide it onComplete() event (inside subscribeWith(…)). The problem is that I don’t have an access to the View from ModelView. How it’s possible to let the View know that loading animation should be hidden?
My current idea is to create in interface inside ViewModel and implement it in View. But it ruins the concept of View and ViewModel separation.
Well you can use liveData for this :D
At your ViewModel class you can create a live data object like this
MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
and for example make a function called downloadFinished and call it in the onComplete
for your remote code
private void downloadFinished() {
isLoading.setValue(true);
}
At your activity that use the view model you observe the value of the loading and hide the progress or what ever you want
TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
viewModel.isLoading.observe(this, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean isLoading) {
if (isLoading != null) {
if (isLoading) {
// hide your progress bar
}
}
}
});
you can use DataBinding for that too
make a separate layout to reuse it everywhere
laoding_state_xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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="match_parent">
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="#+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
then include it inside your desired layout
<?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="android.view.View" />
<variable
name="viewModel"
type="com.blogspot.soyamr.notforgotagain.view.signin.SignInViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.signin.SignInFragment">
<include
android:id="#+id/include"
layout="#layout/toolbar_application"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/signInButtonView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="68dp"
android:layout_marginEnd="16dp"
android:onClick="#{() -> viewModel.logIn()}"
android:text="#string/sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/passwordTextInputLayout" />
<TextView
android:id="#+id/noAccountTextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="4dp"
android:text="#string/no_account"
android:textColor="#android:color/black"
app:layout_constraintEnd_toStartOf="#+id/createAccountTextView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/signInButtonView" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/emailTextInputLayout"
style="#style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="80dp"
android:layout_marginEnd="16dp"
app:errorEnabled="true"
app:errorText="#{viewModel.emailErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/include">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/email"
android:inputType="textEmailAddress"
android:text="#={viewModel.emailText}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/passwordTextInputLayout"
style="#style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
app:errorText="#{viewModel.passwordErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/emailTextInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/password"
android:inputType="textPassword"
android:text="#={viewModel.passwordText}" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="#+id/createAccountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:text="#string/create_one"
android:textColor="#color/textBlue"
app:layout_constraintBottom_toBottomOf="#+id/noAccountTextview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/noAccountTextview"
app:layout_constraintTop_toTopOf="#+id/noAccountTextview" />
<!-- **here is the important include**-->
<include
android:id="#+id/here_must_be_id_or_no_databinding"
android:visibility="#{viewModel.isLoading ? View.VISIBLE : View.GONE}"
layout="#layout/loading_state" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
i included the whole XML for clarification.
then in your view model add this
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SignInViewModelFactory(private val repository: NoteRepository) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SignInViewModel(repository) as T
}
class SignInViewModel(val repository: NoteRepository) : ViewModel() {
private val _isLoading = MutableLiveData(true)
val emailText = MutableLiveData("")
val passwordText = MutableLiveData("")
val isLoading: LiveData<Boolean> = _isLoading
fun logIn() {
//start loading, this will make the view start loading directly
_isLoading.value = true
if (isValidInput()) {
val res = repository.logIn(LoginUser(emailText.value!!, passwordText.value!!))
}//remove loading view
_isLoading.value = false
}
//code ..
}
notice that you are observing isLoading variable inside the XML so whenever its value is changed the view will observe the change and start act on it.

Categories

Resources