2

嗨,我有一项活动LoginActivity.kt,并且LoginViewModel。我在login方法中调用登录API LoginViewModel。成功后,我想开始家庭活动。在 MVVM 方法中正确的方法是什么?

登录视图模型.kt

class LoginViewModel : BaseViewModel<LoginNavigator>(), AnkoLogger {

    val emailField = ObservableField<String>()

    private val email: String
        get() = emailField.get()

    val passwordField = ObservableField<String>()

    private val password: String
        get() = passwordField.get()

    val progressVisibility: ObservableInt = ObservableInt(View.GONE)

    @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
    fun login(view: View) {
      // here I am calling API and on success
    }

    /**
     * Validate email and password. It checks email and password is empty or not
     * and validate email address is correct or not
     * @param email email address for login
     * @param password password for login
     * @return true if email and password pass all conditions else false
     */
    private fun isEmailAndPasswordValid(email: String, password: String): Boolean {

        if (email.isEmpty()) return false

        if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false

        if (password.isEmpty()) return false

        return true
    }

}

登录活动.kt

class LoginActivity : BaseActivity(), LoginNavigator {

    @Inject
    lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        performDependencyInjection()
        super.onCreate(savedInstanceState)
        val activityLoginBinding: ActivityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
        activityLoginBinding.loginViewModel = loginViewModel
        loginViewModel.mNavigator = this
    }
4

1 回答 1

2

假设使用您的登录想法的一个简单场景,用户登录失败,应用程序需要制作一个简单的 Toast 或 SnackBar 来显示“您的用户名和密码不正确”等相关信息。您需要的代码是

吐司(必填Context

Toast.makeText(context, text, duration).show();

小吃店(必填View

Snackbar.make(findViewById(R.id.myCoordinatorLayout),
                                R.string.email_archived, Snackbar.LENGTH_SHORT);

如果你想在你的 ViewModel 中使用它(我不熟悉 Kotlin)

  @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
    void function login(final View view) {
      // here I am calling API and on success
      repo.login(result -> {
           if(result.statusCode == 401)
           Toast.makeText(view.getContext(), "Login failed...", duration).show();
      });
    }

您将以相反的方式找到活动的引用,这会使代码更复杂且难以维护,因为每次您需要获取活动或上下文的引用来执行与视图或视图中的活动相关的事情时模型而不是活动

google 示例中,您可以看到doSearch()输入完成后调用的函数。并且在获取到搜索结果后,绑定会将最新的结果放回给这个观察者,现在就是更新适配器中的结果的活动作业。

private void initSearchInputListener() {
        binding.get().input.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                doSearch(v);
                return true;
            }
            return false;
        });
        binding.get().input.setOnKeyListener((v, keyCode, event) -> {
            if ((event.getAction() == KeyEvent.ACTION_DOWN)
                    && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                doSearch(v);
                return true;
            }
            return false;
        });
    }

    private void doSearch(View v) {
        String query = binding.get().input.getText().toString();
        // Dismiss keyboard
        dismissKeyboard(v.getWindowToken());
        binding.get().setQuery(query);
        searchViewModel.setQuery(query);
    }

private void initRecyclerView() {

        binding.get().repoList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                LinearLayoutManager layoutManager = (LinearLayoutManager)
                        recyclerView.getLayoutManager();
                int lastPosition = layoutManager
                        .findLastVisibleItemPosition();
                if (lastPosition == adapter.get().getItemCount() - 1) {
                    searchViewModel.loadNextPage();
                }
            }
        });
        searchViewModel.getResults().observe(this, result -> {
            binding.get().setSearchResource(result);
            binding.get().setResultCount((result == null || result.data == null)
                    ? 0 : result.data.size());
            adapter.get().replace(result == null ? null : result.data);
            binding.get().executePendingBindings();
        });

        searchViewModel.getLoadMoreStatus().observe(this, loadingMore -> {
            if (loadingMore == null) {
                binding.get().setLoadingMore(false);
            } else {
                binding.get().setLoadingMore(loadingMore.isRunning());
                String error = loadingMore.getErrorMessageIfNotHandled();
                if (error != null) {
                    Snackbar.make(binding.get().loadMoreBar, error, Snackbar.LENGTH_LONG).show();
                }
            }
            binding.get().executePendingBindings();
        });
    }

另外,根据@Emanuel S 的回答,你会看到他的论点

对保存 Activity 上下文的 NavigationController 的 WeakReference。这是在 ViewModel 中处理上下文绑定内容的常用模式。

我强烈拒绝这个有几个原因。首先:这通常意味着您必须保留对修复上下文泄漏的 NavigationController 的引用,但根本不能解决架构问题。

最好的方法(在我看来)是使用 LiveData,它具有生命周期意识,可以做所有想要的事情。

如果您在视图模型中实现 ui 操作,您可能会考虑另一个问题,如果您在视图或上下文中获得 NullPointerException 或对其进行一些增强,您将首先找到哪个类?视图模型或活动?因为第一个你持有 UI 动作,第二个你持有数据绑定。在故障排除中两者都可能是可能的。

于 2017-11-08T02:04:50.523 回答