Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 564 Vote(s) - 3.5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to implement validation using ViewModel and Databinding?

#1
What is the best approach to validate form data using ViewModel and Databinding?

I have a simple Sign-Up activity that links binding layout and ViewModel

class StartActivity : AppCompatActivity() {

private lateinit var binding: StartActivityBinding
private lateinit var viewModel: SignUpViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

viewModel = ViewModelProviders.of(this, SignUpViewModelFactory(AuthFirebase()))
.get(SignUpViewModel::class.java);

binding = DataBindingUtil.setContentView(this, R.layout.start_activity)
binding.viewModel = viewModel;

signUpButton.setOnClickListener {

}
}
}

`ViewModel` with 4 `ObservableFields` and `signUp()` method that should validate data before submitting data to the server.

class SignUpViewModel(val auth: Auth) : ViewModel() {
val name: MutableLiveData<String> = MutableLiveData()
val email: MutableLiveData<String> = MutableLiveData()
val password: MutableLiveData<String> = MutableLiveData()
val passwordConfirm: MutableLiveData<String> = MutableLiveData()

fun signUp() {

auth.createUser(email.value!!, password.value!!)
}
}

I guess we can add four boolean ObservableFields for each input, and in `signUp()` we can check inputs and change state of boolean ObservableField that will produce an appearing error on layout

val isNameError: ObservableField<Boolean> = ObservableField()


fun signUp() {

if (name.value == null || name.value!!.length < 2 ) {
isNameError.set(true)
}

auth.createUser(email.value!!, password.value!!)
}

But I am not sure that `ViewModel` should be responsible for validation and showing an error for a user and we will have boilerplate code

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

<import type="android.view.View" />

<variable
name="viewModel"
type="com.maximdrobonoh.fitnessx.SignUpViewModel" />
</data>

<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorGreyDark"
android:orientation="vertical"
android:padding="24dp">

<TextView
android:id="@+id/appTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/app_title"
android:textColor="@color/colorWhite"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<LinearLayout
android:id="@+id/screenTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appTitle">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:text="@string/sign"
android:textColor="@color/colorWhite"
android:textSize="26sp"
android:textStyle="bold" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/up"
android:textColor="@color/colorWhite"
android:textSize="26sp" />
</LinearLayout>

<LinearLayout
android:id="@+id/form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/screenTitle">

<android.support.v7.widget.AppCompatEditText
style="@style/SignUp.InputBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/sign_up_name"
android:inputType="textPersonName"
android:text="@={viewModel.name}" />

<android.support.v7.widget.AppCompatEditText
style="@style/SignUp.InputBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/sign_up_email"
android:inputType="textEmailAddress"
android:text="@={viewModel.email}"
/>

<android.support.v7.widget.AppCompatEditText
style="@style/SignUp.InputBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/sign_up_password"
android:inputType="textPassword"
android:text="@={viewModel.password}" />

<android.support.v7.widget.AppCompatEditText
style="@style/SignUp.InputBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/sign_up_confirm_password"
android:inputType="textPassword"
android:text="@={viewModel.passwordConfirm}" />

<Button
android:id="@+id/signUpButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/button_gradient"
android:text="@string/sign_up_next_btn"
android:textAllCaps="true"
android:textColor="@color/colorBlack" />

</LinearLayout>

</android.support.constraint.ConstraintLayout>
</layout>
Reply

#2
What you have in mind is right, actually. The viewmodel should not know anything about the android system and will only work with pure java/kotlin. Thus, doing what you are thinking of is right. ViewModel's shouldn't know about the android system as all view interactions should be handled on the View. But, their properties can be bounded to the view.

# TL;DR
This will work

fun signUp() {

if (name.value == null || name.value!!.length < 2 ) {
isNameError.set(true)
}

auth.createUser(email.value!!, password.value!!)
}


<br/>
### *Suggestion*
I would suggest, if you would like to dig in deeper, you could use Custom Binding Adapters. This way you:

- won't need additional variables to your view model
- have a cleaner view model since the error handling is on the custom binding adapter
- would easier read on your XML views as you could define there the validations you need

I'll let your imagination fly on how you could make the custom binding adapter only have the validations. For now, it's better to understand the basics of custom binding adapters.<br/><br/>
**Cheers**
Reply

#3
Yes, you can use your validation logic from **`ViewModel`**, because you're having your observable variables from **`ViewModel`** & your xml is also deriving data from **`ViewModel`** class also.

So, Solution :

- You can create **`@BindingAdapter`** in ViewModel and bind your
button click with it. Check your validation there and do some other
stuffs also.

- You can create **`Listener`**, and implement it on **`ViewModel`** to receive clicks from button and bind that listener to `xml`.

- You can use [Two-Way data binding][1] also *(Be aware of infinite loops though)*.


//Let's say it's your binding adapter from ViewModel
fun signUp() {
if (check validation logic) {
// Produce errors
}
// Further successful stuffs
}

[1]:

[To see links please register here]

Reply

#4
There can be many ways to implement this. I am telling you two solutions, both works well, you can use which you find suitable for you.

I use `extends BaseObservable` because I find that easy than converting all fields to `Observers`. You can use `ObservableFields` too.

##Solution 1 (Using custom `BindingAdapter`)

**In xml**

<variable
name="model"
type="sample.data.Model"/>

<EditText
passwordValidator="@{model.password}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={model.password}"/>

**Model.java**

public class Model extends BaseObservable {
private String password;

@Bindable
public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
}

**DataBindingAdapter.java**

public class DataBindingAdapter {
@BindingAdapter("passwordValidator")
public static void passwordValidator(EditText editText, String password) {
// ignore infinite loops
int minimumLength = 5;
if (TextUtils.isEmpty(password)) {
editText.setError(null);
return;
}
if (editText.getText().toString().length() < minimumLength) {
editText.setError("Password must be minimum " + minimumLength + " length");
} else editText.setError(null);
}
}

##Solution 2 (Using custom `afterTextChanged`)

**In xml**

<variable
name="model"
type="com.innovanathinklabs.sample.data.Model"/>

<variable
name="handler"
type="sample.activities.MainActivityHandler"/>

<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:afterTextChanged="@{(edible)->handler.passwordValidator(edible)}"
android:text="@={model.password}"/>

**MainActivityHandler.java**

public class MainActivityHandler {
ActivityMainBinding binding;

public void setBinding(ActivityMainBinding binding) {
this.binding = binding;
}

public void passwordValidator(Editable editable) {
if (binding.etPassword == null) return;
int minimumLength = 5;
if (!TextUtils.isEmpty(editable.toString()) && editable.length() < minimumLength) {
binding.etPassword.setError("Password must be minimum " + minimumLength + " length");
} else {
binding.etPassword.setError(null);
}
}
}

**MainActivity.java**

public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setModel(new Model());
MainActivityHandler handler = new MainActivityHandler();
handler.setBinding(binding);
binding.setHandler(handler);
}
}


**Update**

You can also replace

android:afterTextChanged="@{(edible)->handler.passwordValidator(edible)}"

with

android:afterTextChanged="@{handler::passwordValidator}"


Because parameter are same of `android:afterTextChanged` and `passwordValidator`.





Reply

#5
This approach uses TextInputLayouts, a custom binding adapter, and creates an enum for form errors. The result I think reads nicely in the xml, and keeps all validation logic inside the ViewModel.

The ViewModel:

```
class SignUpViewModel() : ViewModel() {

val name: MutableLiveData<String> = MutableLiveData()
// the rest of your fields as normal

val formErrors = ObservableArrayList<FormErrors>()

fun isFormValid(): Boolean {
formErrors.clear()
if (name.value?.isNullOrEmpty()) {
formErrors.add(FormErrors.MISSING_NAME)
}
// all the other validation you require
return formErrors.isEmpty()
}

fun signUp() {
auth.createUser(email.value!!, password.value!!)
}

enum class FormErrors {
MISSING_NAME,
INVALID_EMAIL,
INVALID_PASSWORD,
PASSWORDS_NOT_MATCHING,
}

}
```

The BindingAdapter:
```
@BindingAdapter("app:errorText")
fun setErrorMessage(view: TextInputLayout, errorMessage: String) {
view.error = errorMessage
}
```

The XML:
```
<layout>

<data>

<import type="com.example.SignUpViewModel.FormErrors" />

<variable
name="viewModel"
type="com.example.SignUpViewModel" />

</data>

<!-- The rest of your layout file etc. -->

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_input_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorText='@{viewModel.formErrors.contains(FormErrors.MISSING_NAME) ? "Required" : ""}'>

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name"
android:text="@={viewModel.name}"/>

</com.google.android.material.textfield.TextInputLayout>

<!-- Any other fields as above format -->
```

And then, the ViewModel can be called from activity/fragment as below:
```
class YourActivity: AppCompatActivity() {

val viewModel: SignUpViewModel
// rest of class

fun onFormSubmit() {
if (viewModel.isFormValid()) {
viewModel.signUp()
// the rest of your logic to proceed to next screen etc.
}
// no need for else block if form invalid, as ViewModel, Observables
// and databinding will take care of the UI
}


}
Reply

#6
I've written a [library][1] for validating bindable fields of an Observable object.

Setup your Observable model:

class RegisterUser:BaseObservable(){
@Bindable
var name:String?=""
set(value) {
field = value
notifyPropertyChanged(BR.name)
}

@Bindable
var email:String?=""
set(value) {
field = value
notifyPropertyChanged(BR.email)
}
}

Instantiate and add rules

class RegisterViewModel : ViewModel() {

var user:LiveData<RegisterUser> = MutableLiveData<RegisterUser>().also {
it.value = RegisterUser()
}

var validator = ObservableValidator(user.value!!, BR::class.java).apply {
addRule("name", ValidationFlags.FIELD_REQUIRED, "Enter your name")

addRule("email", ValidationFlags.FIELD_REQUIRED, "Enter your email")
addRule("email", ValidationFlags.FIELD_EMAIL, "Enter a valid email")

addRule("age", ValidationFlags.FIELD_REQUIRED, "Enter your age (Underage or too old?)")
addRule("age", ValidationFlags.FIELD_MIN, "You can't be underage!", limit = 18)
addRule("age", ValidationFlags.FIELD_MAX, "You sure you're still alive?", limit = 100)

addRule("password", ValidationFlags.FIELD_REQUIRED, "Enter your password")

addRule("passwordConfirmation", ValidationFlags.FIELD_REQUIRED, "Enter password confirmation")
addRule("passwordConfirmation", ValidationFlags.FIELD_MATCH, "Passwords don't match", "password")
}

}

And setup your xml file:

<com.google.android.material.textfield.TextInputLayout
style="@style/textFieldOutlined"
error='@{viewModel.validator.getValidation("email")}'
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/email"
style="@style/myEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Your email"
android:imeOptions="actionNext"
android:inputType="textEmailAddress"
android:text="@={viewModel.user.email}" />
</com.google.android.material.textfield.TextInputLayout>

[1]:

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through