首页 > 解决方案 > ViewModel 类是否应该包含 Android 元素?

问题描述

从 MVP 迁移到 MVVM 并尝试从网络教程中学习。

一些教程指出 ViewModel 类不应该对 Activity 或 View(android.view.View) 类有任何引用。

但在一些教程中,我看到 ViewModel 类和活动中使用视图来使用 ViewModel 启动其他活动。 例如:

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

另一个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));
    }
}

另一个带有活动参考的

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. ViewModel 类可以包含 Android 和 View 类,这对单元测试来说不是很糟糕吗?

  2. 自定义视图模型类应该扩展哪个类?ViewModel还是 BaseObservable/Observable

  3. 是否有任何教程链接显示 MVVM 的简单用法,并且只关注没有任何 Dagger2、LiveData 或 RxJava 扩展的架构?我现在只在寻找 MVVM 教程。

标签: androidmvvm

解决方案


文档

注意:ViewModel 绝不能引用视图、生命周期或任何可能持有对活动上下文的引用的类。

这是因为ViewModel配置更改仍然存在。假设您有一个活动并且您旋转设备。该活动被杀死并创建一个新实例。如果您将视图放在视图模型中,那么该活动将不会被垃圾收集,因为视图持有对前一个活动的引用。此外,视图本身将被重新创建,但您将旧视图保留在视图模型中。基本上不要在视图模型中放置任何视图、上下文、活动。

这是来自谷歌的示例:https ://github.com/googlesamples/android-sunflower/


推荐阅读