Reputation: 66546
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());
}
}
Is it okay for ViewModel class to contain Android and View classes, isn't this bad for unit testing?
Which class should a custom view model class extend? ViewModel
or
BaseObservable/Observable
?
Upvotes: 0
Views: 1400
Reputation: 3021
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/
Upvotes: 2