Android MVVM

原文地址
http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/

1、Android DataBinding:再见MVP,你好MVVM

当我们谈到android应用程序的架构模式时,MVP一直是占主流的地位。就像 Ted Mosby, Nucleus and Mortar 框架都是用Presenters 架构,使我们的app结构变得非常清晰,还帮助我们解决一个凑名昭着的问题:android设备的旋转和状态持久。当然这不是直接的MVP概念,但事实上这一架构模型,使我们的代码解耦。

数据绑定,是在Google I/O 2015提出并且Android M预览版是的支持库。声称改变一切。根据Wikipedia 是介绍的MVP,Presenter 有以下作用:

Presenter 根据模型和视图。它从存储库(模型)中检索数据,并将其格式化为在视图中显示的数据。

问题是,数据绑定框架将代替Presenter 的主要职责(作用在模型和视图),而其余的功能则是留给增强模型–ViewModel(检索数据库和并格式处理)。视图模型是ViewModel是一个标准的java类,其唯一的责任是代表在一个View中的数据。它可以合并来自多个源(模型)的数据,并展示数据。我以前有一篇在ViewModel的短文章,它主要讲述了Data Model 和Transport Model的不同。

构建我们最终的MVVM–模型视图ViewModel,MVVM概念最初是由微软在2005年(不要让它吓到你了)。下面我说明MVP到MVVM的变化,引用Hanne Dorfmann介绍他的 Ted Mosby framework中使用的图片
这里写图片描述

这里写图片描述

所有的数据的绑定和更新都是通过数据绑定框架(Data Binding Framework)来完成的。Observablefield类允许视图对模型的变化做出反应,而XML引用字段改变时也会改变ViewModel 。当作用在视图上时,你也可以通过程序订阅变化,比如一个CheckBox点击时,让TextView时禁用。如果有一个标准的java类能够表示View的状态,这样的好处显而易见。并且我们可以很容易的单元测试。

注意在MVP上图有一个方法调用 Presenter.loadUsers()的方法。在MVVM模式中这所有的方法都定义在 ViewMode。从维基百科文章:

The view model is an abstraction of the view that exposes public properties and commands

在MVP模式很中,你的模型很可能是“简单”的类,它只保存数据。不要害怕把业务逻辑放在你的模型或视图模型中。这是面向对象编程的一个核心原则。回到 Presenter.loadUsers()这个方法,我们没有在ViewModel中调用这个方法,视图是通过绑定在xml文件中。如果我们不把数据绑定,我们仍然必须使用以前的android语法:onclick语法,或手动添加监听代码。

2、处理系统调用

仍然有一系列的工作在View中去做,比如初始化系统的回调,打开dialog或者涉及Context的对象。不要这些代码放到ViewModel。如果你把像这种Context放入到ViewModel中,这是错误的。

我还没有找到最好的解决办法,但有一些可以尝试。一种方式是在View的ViewModel保持一个presenter 一个接口的引用。这样你就不会降低可测试性。但是没有一个单独的presenter 的类在我们程序中,我坚持认为,只要保持它的简单接口的具体实现。另一种方式就像event bus或者Square Otto初始化方式 ShowToastMessage(“hello world”)(具体参见eventbus 或者otto的使用)。这将产生一个更大的分离View和ViewModel –但那是好事情吗?

3、我们是否需要框架呢?

因此,Data Binding框架代替其他的框架吗?只有部分。我希望看到的是这些框架进化为MVVM风格框架,使我们可以利用最好的数据绑定同时依赖第三方的框架到最小,并保持框架小而简单。而Presenter 的时代就要结束了,这个Presenter仅仅做一些重要的工作比如生命周期管理和视图状态(ViewModel)持久性。不幸的是,这并没有改变。

我最近了解到Android ViewModel框架,这实际上可能是一个很好的适合MVVM和Android的数据绑定。

4、总结

当我第一听说Android M 所有工作是为了改进sdk和关注开发者们。我是多么的兴奋。当接受到数据绑定的时候,我是如此的吃惊,我已经和其他平台上的数据绑定工作多年:WinForms,WPF,Silverlight和Windows手机。我知道这将有助于我们写更清洁的架构和更少的耦合代码。这个框架与我们一起工作,而不是反对我们,现在,它终于要来了。

但是,它仍然有一些缺点。事实上你定义的xml文件,它没有编译,也就是没办法进行单元测试。这样你会了现许多错误在运行期间,而不是在编译期间。不幸的是,如果你经常忘记绑定view呢?
这就是为什么我希望谷歌能够让Android Studio支持数据绑定到最大的语法和编译期间检查的XML,自动完成。支持重命名在xml字段中。从我用的Android Studdio1.3测试版-中思考这样的问题。有些东西是支持的,很也有许多不支持,但是我看到了很大的希望。

5、Code example

下面分别使用mvp和mvvm的代码示例
这里写图片描述
MVP – VIEW – XML

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin"
                tools:context=".MainActivityFragment">

    <TextView
        android:text="..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:id="@+id/loggedInUserCount"/>

    <TextView
        android:text="# logged in users:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="false"
        android:layout_toLeftOf="@+id/loggedInUserCount"/>

    <RadioGroup
        android:layout_marginTop="40dp"
        android:id="@+id/existingOrNewUser"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:orientation="horizontal">

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Returning user"
            android:id="@+id/returningUserRb"/>

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New user"
            android:id="@+id/newUserRb"
            />

    </RadioGroup>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/username_block"
        android:layout_below="@+id/existingOrNewUser">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Username:"
            android:id="@+id/textView"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/username"
            android:minWidth="200dp"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="false"
        android:id="@+id/password_block"
        android:layout_below="@+id/username_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Password:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:ems="10"
            android:id="@+id/password"/>

    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/password_block"
        android:id="@+id/email_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Email:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:ems="10"
            android:id="@+id/email"/>
    </LinearLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Log in"
        android:id="@+id/loginOrCreateButton"
        android:layout_below="@+id/email_block"
        android:layout_centerHorizontal="true"/>
</RelativeLayout>

MVP – VIEW – JAVA

package com.nilzor.presenterexample;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.hannesdorfmann.mosby.mvp.MvpFragment;
import com.hannesdorfmann.mosby.mvp.MvpView;
import butterknife.InjectView;
import butterknife.OnClick;

public class MainActivityFragment extends MvpFragment implements MvpView {
    @InjectView(R.id.username)
    TextView mUsername;

    @InjectView(R.id.password)
    TextView mPassword;

    @InjectView(R.id.newUserRb)
    RadioButton mNewUserRb;

    @InjectView(R.id.returningUserRb)
    RadioButton mReturningUserRb;

    @InjectView(R.id.loginOrCreateButton)
    Button mLoginOrCreateButton;

    @InjectView(R.id.email_block)
    ViewGroup mEmailBlock;

    @InjectView(R.id.loggedInUserCount)
    TextView mLoggedInUserCount;

    public MainActivityFragment() {
    }

    @Override
    public MainPresenter createPresenter() {
        return new MainPresenter();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        attachEventListeners();
    }

    private void attachEventListeners() {
        mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
        mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
    }

    /** Prepares the initial state of the view upon startup */
    public void setInitialState() {
        mReturningUserRb.setChecked(true);
        updateDependentViews();
    }

    /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
    public void updateDependentViews() {
        if (mReturningUserRb.isChecked()) {
            mEmailBlock.setVisibility(View.GONE);
            mLoginOrCreateButton.setText(R.string.log_in);
        }
        else {
            mEmailBlock.setVisibility(View.VISIBLE);
            mLoginOrCreateButton.setText(R.string.create_user);
        }
    }

    public void setNumberOfLoggedIn(int numberOfLoggedIn) {
        mLoggedInUserCount.setText(""  + numberOfLoggedIn);
    }

    @OnClick(R.id.loginOrCreateButton)
    public void loginOrCreate() {
        if (mNewUserRb.isChecked()) {
            Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
        }
    }
}

MVP – PRESENTER

package com.nilzor.presenterexample;

import android.os.Handler;
import android.os.Message;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;

public class MainPresenter implements MvpPresenter {
    MainModel mModel;
    private MainActivityFragment mView;

    public MainPresenter() {
        mModel = new MainModel();
    }

    @Override
    public void attachView(MainActivityFragment view) {
        mView = view;
        view.setInitialState();
        updateViewFromModel();
        ensureModelDataIsLoaded();
    }

    @Override
    public void detachView(boolean retainInstance) {
        mView = null;
    }

    private void ensureModelDataIsLoaded() {
        if (!mModel.isLoaded()) {
            mModel.loadAsync(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    updateViewFromModel();
                    return true;
                }
            });
        }
    }

    /** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
    private void updateViewFromModel() {
        if (mView != null && mModel.isLoaded()) {
            mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
        }
    }
}

MVP – MODEL

package com.nilzor.presenterexample;

import android.os.AsyncTask;
import android.os.Handler;
import java.util.Random;

public class MainModel {
    public Integer numberOfUsersLoggedIn;
    private boolean mIsLoaded;
    public boolean isLoaded() {
        return mIsLoaded;
    }

    public void loadAsync(final Handler.Callback onDoneCallback) {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn = new Random().nextInt(1000);
                mIsLoaded = true;
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onDoneCallback.handleMessage(null);
            }
        }.execute((Void) null);
    }
}

用mvvm代码如下
MVVM – VIEW – XML

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="data" type="com.nilzor.presenterexample.MainModel"/>
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MainActivityFragment">

        <TextView
            android:text="@{data.numberOfUsersLoggedIn}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:id="@+id/loggedInUserCount"/>

        <TextView
            android:text="# logged in users:"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="false"
            android:layout_toLeftOf="@+id/loggedInUserCount"/>

        <RadioGroup
            android:layout_marginTop="40dp"
            android:id="@+id/existingOrNewUser"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:orientation="horizontal">

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Returning user"
                android:checked="@{data.isExistingUserChecked}"
                android:id="@+id/returningUserRb"/>

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="New user"
                android:id="@+id/newUserRb"
                />

        </RadioGroup>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/username_block"
            android:layout_below="@+id/existingOrNewUser">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Username:"
                android:id="@+id/textView"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/username"
                android:minWidth="200dp"/>
        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="false"
            android:id="@+id/password_block"
            android:layout_below="@+id/username_block">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Password:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:ems="10"
                android:id="@+id/password"/>

        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/password_block"
            android:id="@+id/email_block"
            android:visibility="@{data.emailBlockVisibility}">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Email:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress"
                android:ems="10"
                android:id="@+id/email"/>
        </LinearLayout>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.loginOrCreateButtonText}"
            android:id="@+id/loginOrCreateButton"
            android:layout_below="@+id/email_block"
            android:layout_centerHorizontal="true"/>
    </RelativeLayout>
</layout>

MVVM – VIEW – JAVA

package com.nilzor.presenterexample;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Toast;

import com.nilzor.presenterexample.databinding.FragmentMainBinding;

public class MainActivityFragment extends Fragment {
    private FragmentMainBinding mBinding;
    private MainModel mViewModel;

    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        mBinding = FragmentMainBinding.bind(view);
        mViewModel = new MainModel(this, getResources());
        mBinding.setData(mViewModel);
        attachButtonListener();
        return view;
    }

    private void attachButtonListener() {
        mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.logInClicked();
            }
        });
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        ensureModelDataIsLodaded();
    }

    private void ensureModelDataIsLodaded() {
        if (!mViewModel.isLoaded()) {
            mViewModel.loadAsync();
        }
    }

    public void showShortToast(String text) {
        Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
}

MVVM – VIEWMODEL

package com.nilzor.presenterexample;

import android.content.res.Resources;
import android.databinding.ObservableField;
import android.os.AsyncTask;
import android.view.View;

import java.util.Random;

public class MainModel {
    public ObservableField numberOfUsersLoggedIn = new ObservableField();
    public ObservableField isExistingUserChecked = new ObservableField();
    public ObservableField emailBlockVisibility = new ObservableField();
    public ObservableField loginOrCreateButtonText = new ObservableField();
    private boolean mIsLoaded;
    private MainActivityFragment mView;
    private Resources mResources;

    public MainModel(MainActivityFragment view, Resources resources) {
        mView = view;
        mResources = resources; // You might want to abstract this for testability
        setInitialState();
        updateDependentViews();
        hookUpDependencies();
    }
    public boolean isLoaded() {
        return mIsLoaded;
    }

    private void setInitialState() {
        numberOfUsersLoggedIn.set("...");
        isExistingUserChecked.set(true);
    }

    private void hookUpDependencies() {
        isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
                updateDependentViews();
            }
        });
    }

    public void updateDependentViews() {
        if (isExistingUserChecked.get()) {
            emailBlockVisibility.set(View.GONE);
            loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
        }
        else {
            emailBlockVisibility.set(View.VISIBLE);
            loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
        }
    }

    public void loadAsync() {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
                mIsLoaded = true;
                return null;
            }
        }.execute((Void) null);
    }

    public void logInClicked() {
        // Illustrating the need for calling back to the view though testable interfaces.
        if (isExistingUserChecked.get()) {
            mView.showShortToast("Invalid username or password");
        }
        else {
            mView.showShortToast("Please enter a valid email address");
        }
    }
}

更多连接
http://willowtreeapps.com/blog/mvvm-on-android-what-you-need-to-know/

<think>嗯,用户想了解Android中的MVVM架构模式和实现方法。首先,我需要回忆一下MVVM的基本概念和组成部分。根据引用[3],MVVM有三个主要组件:Model、View和ViewModel。Model负责处理数据和业务逻辑,View负责显示数据和用户交互,ViewModel则处理请求并为View提供所需的数据。 接下来,我应该分步骤解释MVVM的实现。首先可能需要介绍如何分层结构,比如Model层通常包含数据源,比如数据库或网络请求。然后ViewModel层如何连接Model和View,使用LiveData或StateFlow这样的可观察数据持有者。View层通常是Activity或Fragment,负责观察ViewModel中的数据变化并更新UI。 用户可能对具体的代码实现感兴趣,比如如何创建ViewModel,如何在View中观察数据变化。引用[5]提到了使用导航组件和safe args插件进行Fragment之间的导航,这可能也是一个相关点,可以结合MVVM的结构来说明如何实现界面跳转。 还需要考虑数据绑定的方式,比如使用Data Binding库来减少样板代码,或者使用ViewBinding。同时,可能需要提到Repository模式,作为Model层的一部分,用于管理多个数据源的协调,比如本地数据库和远程API的结合。引用[1]中的Pokedex项目就展示了MVVM和Repository模式的结合,这可能是一个很好的例子。 另外,用户可能想知道MVVM的优势,比如分离关注点,便于测试,以及如何避免内存泄漏,比如在ViewModel中使用生命周期感知的组件。同时,可以提到常见的库,如Jetpack中的ViewModel、LiveData,以及协程或RxJava用于异步操作。 在实现步骤中,可能需要指导用户如何添加必要的依赖项,例如在build.gradle中引入Lifecycle组件、Data Binding等。然后创建各个层级的类,比如Entity、Repository、ViewModel,并在Activity/Fragment中初始化ViewModel,观察数据变化。 最后,用户可能会有疑问,比如如何测试ViewModel,如何处理网络请求的错误情况,或者如何结合其他架构组件如Room、Retrofit等。这些问题可以作为相关问题的部分,引导用户进一步学习。</think>### Android MVVM 架构模式实现方法分步解析 #### 一、MVVM 核心组件(三要素) 1. **Model 层** 负责数据获取和业务逻辑,包含: - 数据实体类(如`User`) - 数据源(如`LocalDataSource`本地数据库、`RemoteDataSource`网络请求) - 仓库类(`Repository`协调多数据源)[^1][^3] 2. **ViewModel 层** 将数据转换为可观察状态,通过`LiveData`/`StateFlow`向View暴露数据: ```kotlin class UserViewModel : ViewModel() { private val _users = MutableLiveData<List<User>>() val users: LiveData<List<User>> = _users fun loadUsers() { viewModelScope.launch { _users.value = repository.getUsers() // 从仓库获取数据 } } } ``` 3. **View 层** Activity/Fragment负责: - 初始化数据绑定 - 监听ViewModel数据变化 - 处理UI交互事件 示例观察数据: ```kotlin viewModel.users.observe(viewLifecycleOwner) { userList -> adapter.submitList(userList) // 更新RecyclerView } ``` #### 二、关键技术实现 1. **数据绑定(Data Binding)** 在`build.gradle`启用: ```gradle android { buildFeatures { dataBinding true } } ``` XML布局绑定ViewModel: ```xml <layout> <data> <variable name="viewModel" type="com.example.UserViewModel"/> </data> <TextView android:text="@{viewModel.userName}"/> </layout> ``` 2. **依赖注入** 使用Hilt实现依赖管理: ```kotlin @HiltViewModel class UserViewModel @Inject constructor( private val repository: UserRepository ) : ViewModel() ``` 3. **导航组件集成** 使用`Navigation Component`进行Fragment跳转: ```kotlin findNavController().navigate( HomeFragmentDirections.actionToDetailFragment(userId) ) ``` 通过`safe args`插件保证类型安全[^5] #### 三、典型代码结构 ``` app/ ├── model/ │ ├── User.kt # 数据实体 │ ├── UserRepository.kt # 仓库类 ├── view/ │ ├── UserFragment.kt │ └── UserAdapter.kt └── viewmodel/ └── UserViewModel.kt ``` #### 四、优势与注意事项 1. **核心优势** - 关注点分离(数据逻辑与UI解耦) - 自动化的生命周期管理 - 方便单元测试(ViewModel可独立测试) 2. **常见问题处理** - **内存泄漏预防**:使用`viewLifecycleOwner`替代Activity生命周期 - **状态管理**:通过`SavedStateHandle`保存配置变更时的临时数据 - **错误处理**:在ViewModel中封装错误状态: ```kotlin sealed class UiState { object Loading : UiState() data class Success(val data: T) : UiState() data class Error(val message: String) : UiState() } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值