Jetpack学习9-ViewModel

[TOC]

LiveData

LiveData概述

LiveData是一个可观察的数据持有者类。与常规observable不同,LiveData是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如 activities, fragments, or services.。此感知确保LiveData仅更新处于活动生命周期状态的应用程序组件观察者。

注意:要将LiveData组件导入您的Android项目,请参见向项目中添加组件

LiveData认为,如果Observer类所表示的观察者的生命周期处于STARTEDRESUMED状态,则该观察者处于活动状态。LiveData仅通知活动观察者有关更新的信息。注册观察LiveData对象的非活动观察者 不会收到有关更改的通知。

您可以注册一个观察者和实现该LifecycleOwner 接口的对象配对。这种关系允许在相应Lifecycle对象的状态更改为DESTROYED时删除观察者。这对于activities和fragments特别有用,因为它们可以安全地观察LiveData对象而不用担心泄漏--当活动和片段的生命周期被破坏时,它们会立即被取消订阅。

有关如何使用LiveData的详细信息,请参阅使用LiveData对象

使用LiveData的优点

使用LiveData具有以下优势:

确保UI与数据状态匹配

​ LiveData遵循观察者模式。当生命周期的状态改变时,LiveData会通知Observer对象。你可以整合代码使用Observer对象来更新UI。与每次应用数据更改时更新UI不同,观察者可以生命周期的状态改变时更新UI。

没有内存泄漏

​ 观察者绑定 Lifecycle对象并在其相关生命周期被破坏后自行清理。

没有因stopped activities而崩溃

​ 如果观察者的生命周期处于非活动状态(例如,在后台堆栈中的activity的情况下),则它不会接收任何LiveData事件。

不再需要手动生命周期处理

​ UI组件只观察相关数据,不停止或恢复观察。LiveData会自动管理所有这些,因为它在观察的同时会知道相关的生命周期状态变化。

始终保持最新数据

​ 如果生命周期变为非活动状态,它将在再次变为活动状态时接收最新数据。例如,后台activity在返回前台后立即接收最新数据。

适当的配置更改

​ 如果由于配置更改(例如设备轮换)而重新创建activity或fragment,则会立即接收最新的可用数据。

共享资源

​ 您可以使用单例模式扩展LiveData对象来包装系统服务,以便在应用程序中共享它们。LiveData对象连接到系统服务一次,然后任何需要该资源的观察者只能观察该LiveData 对象。有关更多信息,请参阅扩展LiveData

使用LiveData对象

按照以下步骤使用LiveData对象:

  1. 创建一个LiveData用于保存特定类型数据的实例。这通常在您的ViewModel类内完成 。
  2. 创建一个定义onChanged()方法的Observer对象,控制当LiveData对象持有的数据发生更改是的情况。您通常在UI控制器中创建一个Observer对象,例如activity或fragment。
  3. 使用observe()方法将Observer对象附加到LiveData对象。observer()方法接受LifecycleOwner对象。这将Observer对象订阅到LiveData对象,以便通知它更改。通常将Observer对象附加到UI控制器中,例如activity或fragment。

注意:可以使用observeForever(Observer)方法注册一个没有关联LifecycleOwner对象的观察者。在这种情况下,观察者被认为总是处于活动状态,因此总是会收到关于修改的通知。可以调用removeObserver(Observer)方法删除这些观察者。

当您更新存储在LiveData对象中的值时,只要附加的LifecycleOwner处于活动状态,它就会触发所有注册的观察者。

LiveData允许UI控制器观察者订阅更新。当LiveData对象保存的数据发生更改时,UI会自动响应更新。

创建LiveData对象

LiveData是一个包装器,可用于任何数据,包括实现Collections(如List)的对象。LiveData对象通常存储在ViewModel对象中,通过getter方法访问,如下面的示例所示:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> currentName;

    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }

// Rest of the ViewModel...
}

最初,没有设置LiveData对象中的数据。

注意:请确保将更新UI的LiveData对象存储在ViewModel对象中,而不是存储在活动或片段中,原因如下:

  • 避免臃肿的activity和fragment。这些UI控制器负责显示数据而不是保存数据状态。
  • 将LiveData实例与特定的活动或片段实例解耦,并允许LiveData对象在配置更改后继续存在。

您可以在ViewModel指南中阅读更多关于ViewModel类的好处和用法的信息。

观察LiveData对象

在大多数情况下,app组件的onCreate()方法是开始观察LiveData对象的正确位置, 原因如下:

  • 确保系统不会对activity或fragment的onResume()方法进行多余的调用。
  • 确保activity或fragment在变为活动状态时可以及时显示数据。一旦app组件处于STARTED状态,它就会从它所观察的LiveData对象接受最新的值。只有在这只了要观察的LiveData对象时才会发生这种情况。

通常,LiveData仅在数据更改时才提供更新,并且仅在活动观察者时提供更新。这种行为的一个例外是,观察者在从非活动状态更改为活动状态时也会收到更新。此外,如果观察者第二次从非活动状态更改为活动状态,则只有在自上次活动状态以来该值发生更改时才会收到更新。

public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        model = ViewModelProviders.of(this).get(NameViewModel.class);


        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                nameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.getCurrentName().observe(this, nameObserver);
    }
}

在用nameObserver作为参数传递调用observe()之后,立即调用onChanged(),提供存储在mCurrentName中的最新值。如果LiveData对象没有在mCurrentName中设置值,则不调用onChanged()

更新LiveData对象

LiveData没有公开的方法来更新存储的数据。 MutableLiveData 类公开 setValue(T)postValue(T) 方法,如果需要编辑存储在LiveData对象中的值,必须使用这些方法。通常在ViewModel中使用MutableLiveData,然后ViewModel只向观察者公开不可变的LiveData对象。

在建立观察者关系之后,您可以更新LiveData对象的值,如下面的例子所示,当用户点击一个按钮时,将触发所有的观察者:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        model.getCurrentName().setValue(anotherName);
    }
});

在示例中调用setValue(T)会导致观察者使用John Doe值调用其onChanged()方法。该示例展示了一个按钮按下,(调用setValue()或postValue()来更新mName)。除此之外还有许多原因,可以调用setValue()或postValue()来更新mName,包括响应网络请求或数据库加载完成;在所有情况下,调用setValue()postValue()都会触发观察者并更新UI。

注意:您必须在主线程中调用 setValue(T) 方法来更新LiveData对象。如果代码在工作线程中执行,则可以使用该 postValue(T) 方法来更新LiveData对象。

LiveData和Room一起使用

Room persistence库支持可观察的查询,这些查询返回LiveData对象。可观察查询是作为数据库访问对象(DAO)的一部分编写的。

当数据更新后,Room会生成更新LiveData对象所需的所有代码。当需要时,生成的代码在后台线程上异步查询。此模式对于使UI中显示的数据与存储在数据库中的数据保持同步非常有用。您可以在Room persistent library guide阅读有关Room和DAO的更多信息。

扩展LiveData

如果观察者的生命周期处于STARTEDRESUMED 状态,LiveData会将观察者视为处于活动状态 。以下示例代码说明了如何扩展 LiveData类:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

本例中price侦听器的实现包括以下重要方法:

  • LiveData对象有一个活跃的观察者时,调用onActive()方法。这意味着您需要从这个方法开始观察股票价格的更新。
  • LiveData对象没有任何活跃观察者时,调用onInactive()方法。由于没有观察者在监听,因此没有理由与StockManager服务保持联系。
  • setValue(T) 方法更新LiveData实例的值,并通知任何活跃的观察者有关更改的信息。

您可以按如下方式使用StockLiveData类:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

observe()方法将fragment(它是LifecycleOwner的一个实例)作为第一个参数传递。这样做表示此观察者绑定到与所有者关联的Lifecycle对象,这意味着:

  • 如果Lifecycle对象未处于活跃状态,则即使值发生更改,也不会调用观察者。
  • Lifecycle的对象被破坏之后,将自动删除观察者。

LiveData对象具有生命周期感知这一事实意味着您可以在多个activities,fragments和services之间共享它们。为了简化示例,您可以将LiveData类实现为单例,如下所示:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

您可以在fragment中使用它,如下所示:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(symbol).observe(this, price -> {
            // Update the UI.
        });
    }
}

多个fragments和activities可以观察MyPriceListener实例。LiveData只在其中一个或多个是可见且活跃的情况下才连接到系统服务。

转换LiveData

您可能希望在将LiveData对象分发给观察者之前对其中存储的值进行更改 ,或者您可能需要根据另一个LiveData实例的值返回不同的LiveData实例。 Lifecycle包提供了Transformations 类其中包含支持这些场景的帮助方法。

Transformations.map()

​ 将函数应用于存储在LiveData对象中值,并将结果传播到下游。

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

Transformations.switchMap()

​ 类似于map(),将函数应用于存储在LiveData 对象中的值,并将结果解包并分派到下游。传递给的函数switchMap()必须返回一个LiveData对象,如下例所示:

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

您可以使用转换方法在观察者的生命周期中传递信息。除非观察者正在观察返回的LiveData对象,否则不会计算变换。由于转换是延迟计算的,因此生命周期相关的行为会被隐式传递下去,而不需要额外的显式调用或依赖项。

如果您认为在ViewModel对象中需要一个Lifecycle对象,那么转换可能是更好的解决方案。例如,假设您有一个UI组件,它接受一个地址并返回该地址的邮政编码。您可以为该组件实现ViewModel,如下面的示例代码所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

然后,UI组件需要从前面的LiveData对象注销注册,并在每次调用getPostalCode()时注册到新实例。此外,如果重新创建UI组件,它将触发对repository.getPostCode()方法的另一次调用,而不是使用前一个调用的结果。

相反,您可以将邮政编码查找实现为地址输入的转换,如以下示例所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

在这种情况下,postalCode字段被定义为addressInput转换 。只要您的应用程序有一个与postalCode字段相关联的活动观察者,该字段的值就会在addressInput发生更改时重新计算和检索。

这种机制允许在app相对底层创建按需延迟计算的LiveData对象。ViewModel对象可以轻松获取对LiveData对象的引用,然后在其(app相对上层)上定义转换规则。

创建新的转换

有十几种不同的转换可能对您的应用有用,但默认情况下不提供。要实现自己的转换,您可以使用MediatorLiveData 类,该类侦听其他 LiveData对象并处理它们发出的事件。

MediatorLiveData正确地将其状态传播到源LiveData对象。要了解有关此模式的更多信息,请参阅Transformations 类的参考文档 。

合并多个LiveData源

MediatorLiveDataLiveData的一个子类,允许您合并多个LiveData数据源。 只要任何原始LiveData源对象发生更改,就会触发MediatorLiveData对象的观察者。

例如,如果UI中有一个可以从本地数据库或网络更新的LiveData对象,那么可以向MediatorLiveData对象添加以下源:

  • 与数据库中存储的数据关联的LiveData对象。
  • 与从网络访问的数据关联的LiveData对象。

您的活动只需要观察MediatorLiveData对象就可以从这两个源接收更新。有关详细示例,请参见应用程序体系结构指南的附录:公开网络状态部分。

其他资源

要了解关于LiveData类的更多信息,请参考以下参考资料。

例子

Codelabs

Blogs

Videos

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 1.孤独多好啊. “又去图书馆啊?”听到我关门的声音,埋头打游戏的室友悠悠的来了句。 我没吭声,不想回答也懒得回答...
    麦冬先生阅读 3,521评论 10 13
  • 一不小心将你丢失 丢失在无涯的荒漠里 回首欲相牵 后知后觉 已然失去你 后悔莫及 过去日夜相伴 今日悔断愁肠 对你...
    雨初33阅读 284评论 0 0
  • 学习英语跟宝宝一起 看书 做美食 开个补习班 写文章
    馨儿妈阅读 206评论 0 1
  • 策略模式 (Strategy Pattern) 定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他...
    潘杉杉_06_03阅读 1,219评论 0 0