12

I am making a user login screen using MVVM design pattern but I am stuck when its come to implement the logic for phone number validation. I read out Rules to follow when working with mvvm (Rule no. 4) that View should not have any logic in it, Not even a simple if condition. All logic for the view happens in ViewModel.

Here is my ViewModel class.

public class LoginViewModel extends AndroidViewModel {

    private LoginRepository loginRepository;
    private HashMap<String,String> mNumberParam;
    private MutableLiveData<Boolean> isValidated;

    public LoginViewModel(@NonNull Application application) {
        super(application);
        loginRepository=LoginRepository.getInstance();
        isValidated=new MutableLiveData<>();
    }


    public LiveData<List<OtpEnterModel.Data>> enterNumberApiHit(){
        return loginRepository.enterNumberApiHit(mNumberParam);
    }


    public void onSubmitClick(String number){

        //if mobile number not enter or wrong enter show message ,and tell the view to hide other view
        if (number==null) {
            Toast.makeText(getApplication(), "Invalid mobile number", Toast.LENGTH_SHORT).show();
            isValidated.setValue(false);

        } else {
            //otherwise save mobile number in hashMap ,and tell the view to work further
            isValidated.setValue(true);
            saveNumberParam(number);
        }
    }

    //to save the mobile number in hashMap with key i.e mobile_number.
    private void saveNumberParam(String mobileNumber) {

        //if hashMap null then initialize it
        if (mNumberParam ==null) {
            mNumberParam = new HashMap<>();
        }
        mNumberParam.put(MyConstant.MOBILE_NUMBER, mobileNumber);
    }

    public LiveData<Boolean> isValidated(){
      return isValidated;
    }
}

Here is my View class.

public class EnterNumber extends AppCompatActivity implements View.OnClickListener, FragmentManager.OnBackStackChangedListener {

    //dataType
    private Context context;
    private FragmentManager manager;
    private LoginViewModel loginViewModel;

    //views
    private EditText enterMobileEDT;
    private ProgressBar progressBar;
    private Button btnNumber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_enter_number);

        manager=getSupportFragmentManager();
        context = EnterNumber.this;
        loginViewModel= ViewModelProviders.of(this).get(LoginViewModel.class);

        init();
        setListener();
    }

    //all views initialize here
    private void init() {
        enterMobileEDT = findViewById(R.id.enterMobileET);
        progressBar=findViewById(R.id.progressBar);
        btnNumber=findViewById(R.id.btn_number);
    }

    //listener for views
    private void setListener() {
        btnNumber.setOnClickListener(this);
        manager.addOnBackStackChangedListener(this);
    }

    //check for mobile number and send otp by hitting API
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_number) {
            loginViewModel.onSubmitClick(enterMobileEDT.getText().toString());
            numberValidation();
        }
    }

    //check for editText length
    public void numberValidation() {

        loginViewModel.isValidated().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {

                if(aBoolean){
                    loginApiHit();
                }
                hideShowView(aBoolean);
            }
        });
    }

    //hide and show the view based on condition
    public void hideShowView(boolean wantToShowProgress) {

        if(!wantToShowProgress){
            progressBar.setVisibility(View.INVISIBLE);
            btnNumber.setEnabled(true);

        }else {
            progressBar.setVisibility(View.VISIBLE);
            btnNumber.setEnabled(false);
        }
    }

}

How Can I move all if/else condition from View to ViewModel?

Lokik Soni
  • 602
  • 1
  • 5
  • 25
  • Not matter in Java or Kotlin or something else. What's your business logic? Could you simple summary your business logic? – Sinh Phan Sep 17 '19 at 02:33
  • In simple it could be email and password validation. – Lokik Soni Sep 17 '19 at 02:42
  • Business logic should be included in the data layer(such as model class). – Ethan Choi Sep 17 '19 at 02:58
  • @EthanChoi so should I pass mail and password edit text string in model for validation? – Lokik Soni Sep 17 '19 at 03:48
  • I guess if you want to completely remove the logic, you can move the if condition to viewModel and observe View.Visibility and button state instead. But your logic seems fine too. Here is an article about this topic https://www.freecodecamp.org/news/model-view-viewmodel-android-tutorial/ – moonLander_ Mar 11 '22 at 15:52

1 Answers1

7

Question

How Can I move all if/else condition from View to ViewModel?

  • It is recommended that you remove all business logic in the View.
  • View only has code for updates UI that coupled ViewModel data(LiveData), which can be reduced via ViewDataBininding library.
  • Finally, View just has code related to setup ViewDataBinding and ViewModel.

ViewModel

public class LoginViewModel extends AndroidViewModel {
    ...
    private MutableLiveData<String> _email = new MutableLiveData<>(); // is binded some UI such as EditText..

    LiveData<Boolean> emailValidate = Transformations.map(_email, this::emailValidate);

    private boolean emailValidate(String email) {
        return true; // implements email validation logic
    }
    ...
}

View

...
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
    super.onCreate(savedInstanceState, persistentState);
    LoginViewModel loginViewModel= ViewModelProviders.of(this).get(LoginViewModel.class);
    subscribe(loginViewModel);
}

private void subscribe(LoginViewModel loginViewModel) {
    loginViewModel.emailValidate.observe(this, this::setEmailValidateLayout); 
    // You shouldn't implement observing in the onClick event. Overlapping observers problem.
}

private void setEmailValidateLayout(boolean validate) {
    progressBar.setVisibility(validate ? View.VISIBLE : View.INVISIBLE);
    btnNumber.setEnabled(validate);
}
...
Community
  • 1
  • 1
Ethan Choi
  • 2,339
  • 18
  • 28
  • but in your answer, there is also if/else condition so how it is different from my code – Lokik Soni Sep 27 '19 at 04:26
  • @LokikSoni What's wrong? `if/else condition` included in your code is no problem. Please note that I have modified the code for your situation. – Ethan Choi Sep 27 '19 at 05:29
  • Email validation shouldn't be implemented directly in the ViewModel, but should instead delegate to domain / model layer implementation that encapsulates those business rules. – Carter Hudson Apr 05 '22 at 19:52