Ad

Should ViewModel Class Contain Android Elements?

- 1 answer

Moving from MVP to MVVM and trying to learn from tutorials on web.

Some of the tutorials state that ViewModel classes should not have any reference to Activity or View(android.view.View) classes.

But in some of the tutorials i've seen Views are used in ViewModel class and Activities to start other Activities using ViewModel. For example:

import android.arch.lifecycle.ViewModel;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;

import com.journaldev.androidmvvmbasics.interfaces.LoginResultCallback;
import com.journaldev.androidmvvmbasics.model.User;

public class LoginViewModel extends ViewModel {
    private User user;
    private LoginResultCallback mDataListener;

    LoginViewModel(@NonNull final LoginResultCallback loginDataListener) {
        mDataListener = loginDataListener;
        user = new User("", "");
    }


    public TextWatcher getEmailTextWatcher() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void afterTextChanged(Editable editable) {
                user.setEmail(editable.toString());
            }
        };
    }

    public TextWatcher getPasswordTextWatcher() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                user.setPassword(editable.toString());
            }
        };
    }


    public void onLoginClicked(@NonNull final View view) {
        checkDataValidity();
    }

    private void checkDataValidity() {
        if (user.isInputDataValid())
            mDataListener.onSuccess("Login was successful");
        else {
            mDataListener.onError("Email or Password not valid");
        }
    }
}

Another one with View.OnClickListener

public class PostViewModel extends BaseObservable {

    private Context context;
    private Post post;
    private Boolean isUserPosts;

    public PostViewModel(Context context, Post post, boolean isUserPosts) {
        this.context = context;
        this.post = post;
        this.isUserPosts = isUserPosts;
    }

    public String getPostScore() {
        return String.valueOf(post.score) + context.getString(R.string.story_points);
    }

    public String getPostTitle() {
        return post.title;
    }

    public Spannable getPostAuthor() {
        String author = context.getString(R.string.text_post_author, post.by);
        SpannableString content = new SpannableString(author);
        int index = author.indexOf(post.by);
        if (!isUserPosts) content.setSpan(new UnderlineSpan(), index, post.by.length() + index, 0);
        return content;
    }

    public int getCommentsVisibility() {
        return  post.postType == Post.PostType.STORY && post.kids == null ? View.GONE : View.VISIBLE;
    }

    public View.OnClickListener onClickPost() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Post.PostType postType = post.postType;
                if (postType == Post.PostType.JOB || postType == Post.PostType.STORY) {
                    launchStoryActivity();
                } else if (postType == Post.PostType.ASK) {
                    launchCommentsActivity();
                }
            }
        };
    }

    public View.OnClickListener onClickAuthor() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(UserActivity.getStartIntent(context, post.by));
            }
        };
    }

    public View.OnClickListener onClickComments() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                launchCommentsActivity();
            }
        };
    }

    private void launchStoryActivity() {
        context.startActivity(ViewStoryActivity.getStartIntent(context, post));
    }

    private void launchCommentsActivity() {
        context.startActivity(CommentsActivity.getStartIntent(context, post));
    }
}

Another one with Activity Reference

public class UserProfileViewModel {

    /* ------------------------------ Constructor */

    private Activity activity;

    /* ------------------------------ Constructor */

    UserProfileViewModel(@NonNull Activity activity) {
        this.activity = activity;
    }

    /* ------------------------------ Main method */

    /**
     * On profile image clicked
     *
     * @param userName name of user
     */
    public void onProfileImageClicked(@NonNull String userName) {

        Bundle bundle = new Bundle();
        bundle.putString("USERNAME", userName);
        Intent intent = new Intent(activity, UserDetailActivity.class);
        intent.putExtras(bundle);
        activity.startActivity(intent);
    }

     /**
     * @param editable         editable
     * @param userProfileModel the model of user profile
     */
    public void userNameTextChange(@NonNull Editable editable,
                                   @NonNull UserProfileModel userProfileModel) {

        userProfileModel.setUserName(editable.toString());
        Log.e("ViewModel", userProfileModel.getUserName());
    }
}
  1. Is it okay for ViewModel class to contain Android and View classes, isn't this bad for unit testing?

  2. Which class should a custom view model class extend? ViewModel or BaseObservable/Observable?

  3. Is there any tutorial link that shows simple usage of MVVM and with only focus on architecture without any Dagger2, LiveData, or RxJava extensions? I'm only looking for MVVM tutorials for now.
Ad

Answer

From the documentation:

Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.

This is because a ViewModel survives configuration changes. Let's say you have an activity and you rotate the device. The activity is killed and a new instance is created. If you put views in the viewmodel, then the activity won't be garbage collected because the views hold the reference to the previous activity. Also, the views themselves will be recreated but you're keeping old views in the viewmodel. Basically don't put any views, context, activity in the viewmodel.

Here's a sample from google: https://github.com/googlesamples/android-sunflower/

Ad
source: stackoverflow.com
Ad