LiveData 类的这两种方法有什么区别?官方文档和教程对此非常模糊。在map()方法中,第一个参数称为source,但在switchMap()中,它称为trigger。这背后的理由是什么?
13 回答
根据文档
对存储在 LiveData 对象中的值应用函数,并将结果传播到下游。
与 map 类似,将函数应用于存储在 LiveData 对象中的值,并将结果解包并分发到下游。传递给 switchMap() 的函数必须返回一个 LiveData 对象。
换句话说,我可能不是 100% 正确,但如果你熟悉 RxJava;Transformations#map
有点类似于Observable#map
&Transformations#switchMap
类似于Observable#switchMap
.
举个例子,有一个 LiveData 发出一个字符串,我们想用大写字母显示该字符串。
一种方法如下;在一个活动或片段中
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
传递给的函数map
只返回一个字符串,但它Transformation#map
最终返回一个LiveData
.
第二种方法;在一个活动或片段中
Transformations.switchMap(stringsLiveData, this::getUpperCaseStringLiveData)
.observe(this, textView::setText);
private LiveData<String> getUpperCaseStringLiveData(String str) {
MutableLiveData<String> liveData = new MutableLiveData<>();
liveData.setValue(str.toUpperCase());
return liveData;
}
如果你看到Transformations#switchMap
实际上已经切换了LiveData
. 因此,再次按照文档传递给 switchMap() 的函数必须返回一个 LiveData 对象。
因此,如果map
它是您正在转换的源 ,并且在传递的情况下将充当触发器,它将在解包并将结果分发到下游后切换到另一个触发器。LiveData
switchMap
LiveData
LiveData
我的观察是,如果您的转换过程很快(不涉及数据库操作或网络活动),那么您可以选择使用map
.
但是,如果您的转换过程很慢(涉及数据库操作或网络活动),则需要使用switchMap
switchMap
执行耗时操作时使用
class MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.switchMap(mString, input -> {
final MutableLiveData<Integer> result = new MutableLiveData<>();
new Thread(new Runnable() {
@Override
public void run() {
// Pretend we are busy
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
result.postValue(code);
}
}).start();
return result;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
map
不适合耗时操作
class MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.map(mString, input -> {
/*
Note: You can't launch a Thread, or sleep right here.
If you do so, the APP will crash with ANR.
*/
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
return code;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
首先,map()
和switchMap()
方法都在主线程上调用。它们与用于快速或慢速任务无关。但是,如果您在这些方法而不是工作线程中执行复杂的计算或耗时的任务,例如解析或转换长和/或复杂的 json 响应,则可能会导致 UI 滞后,因为它们是在 UI 线程上执行的。
- 地图()
map() 方法的代码是
@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
它的作用是,它使用源 LiveData,I 是输入类型,并在 LiveData 上调用 setValue(O),其中 O 是输出类型。
为了清楚起见,让我举个例子。每当用户更改时,您希望将用户名和姓氏写入 textView。
/**
* Changes on this user LiveData triggers function that sets mUserNameLiveData String value
*/
private MutableLiveData<User> mUserLiveData = new MutableLiveData<>();
/**
* This LiveData contains the data(String for this example) to be observed.
*/
public final LiveData<String> mUserNameLiveData;
现在让我们在 mUserLiveData 更改时触发 mUserNameLiveData 的字符串更改。
/*
* map() method emits a value in type of destination data(String in this example) when the source LiveData is changed. In this example
* when a new User value is set to LiveData it trigger this function that returns a String type
*
* Input, Output
* new Function<User, String>
*
* public String apply(User input) { return output;}
*/
// Result<Output> Source<Input> Input, Output
mUserNameLiveData = Transformations.map(mUserLiveData, new Function<User, String>() {
@Override
public String apply(User input) {
// Output
return input.getFirstName() + ", " + input.getLastName();
}
});
让我们做同样的事情MediatorLiveData
/**
* MediatorLiveData is what {@link Transformations#map(LiveData, Function)} does behind the scenes
*/
public MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();
/*
* map() function is actually does this
*/
mediatorLiveData.addSource(mUserLiveData, new Observer<User>() {
@Override
public void onChanged(@Nullable User user) {
mediatorLiveData.setValue(user.getFirstName() + ", " + user.getLastName());
}
});
如果您在 Activity 或 Fragment 上观察 MediatorLiveData,您会得到与观察相同的结果LiveData<String> mUserNameLiveData
userViewModel.mediatorLiveData.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
TextView textView = findViewById(R.id.textView2);
textView.setText("User: " + s);
Toast.makeText(MainActivity.this, "User: " + s, Toast.LENGTH_SHORT).show();
}
});
- 开关映射()
每次 SourceLiveData 更改时,switchMap() 返回相同的 MediatorLiveData 而不是新的 LiveData。
它的源代码是
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
基本上它的作用是,它创建了一个最终的 MediatorLiveData 并将它设置为 Result 就像 map does() 但是这个时间函数返回 LiveData
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, **Y**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, **LiveData<Y>**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
因此,例如,如果对象更改名称字段更改,map()
则将LiveData<User>
其转换为 。String
User
switchMap()
接受一个字符串并开始LiveData<User>
使用它。使用字符串从 web 或 db 查询用户并获得 aLiveData<User>
作为结果。
上面已经有一些很好的答案了,但是直到我理解为止,我仍然在努力解决它们,所以我将尝试用我的思维方式为人们解释一个具体的例子,而不涉及技术细节和代码。
在这两种情况下map
,switchMap
都有一个源(或触发器)实时数据,并且在这两种情况下,您都希望将其转换为另一个实时数据。您将使用哪一个 - 取决于您的转换正在执行的任务。
map
考虑在任何地方都使用的相同简单示例 - 您的源实时数据包含一个User
对象 - LiveData<User>
,它指向当前登录的用户。你想在你的 UI 中显示一个文本说Current user: <USERNAME>
. 在这种情况下,来自源的每个更改信号都应恰好触发结果“映射”的一个信号LiveData
。例如,当前User
对象是“Bob”,则 UI 文本显示Current user: Bob
. 一旦LiveData<User>
触发更改,您的 UI 将观察它并将文本更新为Current user: Alice
. 很简单,线性的,一对一的变化。
switchMap
考虑以下示例 - 您想要创建一个 UI 来显示其名称与给定搜索词匹配的用户。我们可以非常聪明地将搜索词作为 LiveData!因此LiveData<String>
,每次用户输入一个新的查询字符串时,我们的Fragment
/Activity
都会简单地将文本输入值设置为ViewModel
. 结果,此实时数据将触发更改信号。一旦我们得到这个信号,我们就开始搜索用户。现在让我们考虑一下我们的搜索是如此之快以至于它立即返回一个值。此时你认为你可以只使用一个map
并返回将更新 UI 的匹配用户。好吧,您现在将遇到一个错误 - 假设您定期更新数据库,并且在下次更新后出现更多与搜索词匹配的用户!如您所见,在这种情况下,源触发器(搜索词)不一定会导致映射实时数据的单个触发器,给 UI 的映射实时数据可能仍需要在添加新用户后继续触发值数据库。此时您可能会说,我们可以返回一个“更智能”的实时数据,它不仅会等待源触发器,还会监控数据库中匹配给定术语的用户(您可以使用Room
DB out的盒子)。但随之而来的另一个问题是——如果搜索词发生变化怎么办?所以你的任期是x
,它触发了一个实时数据,该数据查询用户并密切关注数据库,它返回userx, userxx
,然后在五分钟后返回userx, userxxx
,依此类推。然后这个词就变成了y
。现在我们需要以某种方式停止收听给我们用户的智能实时数据x
,并用新的智能实时数据切换它,它将监控并给我们用户y
的名字。这正是switchMap
正在做的事情!请注意,此切换需要以这样一种方式完成,即在您的 UI 中您只需编写switchMap(...).observe
一次,这意味着switchMap
必须返回一个包装器,该包装器LiveData
将在整个执行过程中保持不变,但会在后台切换实时数据源我们。
结论
尽管它们乍一看似乎相同,但 和 的用例map
却switchMap
不同,但一旦开始实现您的用例,您就会感觉到使用哪个,主要是当您意识到在映射函数中必须调用一些来自您的其他模块(如Repositories
)返回的代码LiveData
。
switchMap :假设我们正在寻找用户名 Alice。存储库正在创建该 User LiveData 类的新实例,然后我们显示用户。一段时间后,我们需要查找用户名 Bob,存储库会创建一个新的 LiveData 实例,并且我们的 UI 订阅了该 LiveData。所以此时,我们的 UI 订阅了两个 LiveData 实例,因为我们从未删除前一个。因此,这意味着每当我们的存储库更改用户的数据时,它都会发送两次订阅。现在,我们如何解决这个问题……?
我们真正需要的是一种机制,当我们想观察一个新的来源时,它可以让我们停止从以前的来源观察。为此,我们将使用 switchMap。在底层,switchMap 使用 MediatorLiveData,只要添加了新源,它就会删除初始源。简而言之,它为我们完成了删除和添加新观察者的所有机制。
但是地图是静态的,当您不必每次都强制获取新的实时数据时使用它
map
最后你有相同的源实时数据,但它的数据(值)在发出之前随提供的函数而变化
- 使用
switchMap
,您可以使用源实时数据作为返回独立实时数据的触发器(当然您可以在函数输入中使用触发器数据)
- 触发器:所有导致 livedata 的观察者
onChanged()
调用的东西
变换.map()
fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?
trigger
mapFunction
- 曾经更改触发器执行的 LiveData 变量。
mapFunction
trigger
- LiveData发生更改时调用的函数。参数 X 是对trigger
(via it
) 的引用。该函数返回指定类型 Y 的结果,最终map()
作为 LiveData 对象返回。
map()
当您想要在LiveData 变量更改 时执行操作(通过mapFunction
)时使用。将返回一个 LiveData 对象,该对象应该被观察以被调用。trigger
map()
mapFunction
例子:
假设一个简单的投球手姓名列表、他们的平均数和他们的平均差点数:
data class Bowler(val name:String, val average:Int, var avgWHDCP:Int)
var bowlers = listOf<Bowler>(Bowler("Steve", 150,150), Bowler ("Tom", 210, 210))
假设一个MutableLiveData
Int
变量包含一个让分增量值。当此值发生变化时,avgWHDCP
需要重新计算列表中的所有投球手。最初它被设置为零。
var newHDCP:MutableLiveData<Int> = MutableLiveData(0)
创建一个调用Tranformation.map()
. 它的第一个论点是newHDCP
. 它的第二个参数是更改时要调用的函数newHDCP
。在此示例中,该函数将遍历所有投球手对象,计算avgWHDCP
投球手列表中每个投球手的新值,并将结果作为 LiveData Bowler 对象的可观察列表返回。请注意,在此示例中,原始的非 LiveData 投球手列表和返回的投球手列表将反映相同的值,因为它们引用相同的数据存储。但是,函数的结果是可观察的。投球手的原始列表不是因为它没有设置为 LiveData。
var updatedBowlers: LiveData<List<Bowler>> = Transformations.map(newHDCP) {
bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return@map bowlers
}
在您的代码中的某处,添加一个更新方法newHDCP
。在我的示例中,当单击单选按钮时,newHDCP
将被更改,并且该过程将触发调用中指定的函数Transformations.map()
rbUpdateBy20.setOnCheckedChangeListener { _, isChecked ->
viewModel.bowlingBallObject.newHDCP.value = 20
}
updatedBowlers
最后,所有这些只有在被观察到的情况下才会起作用。这将被放置在您的 Activity 或 Fragment 中,例如OnViewCreated()
viewModel.updatedBowlers.observe(viewLifecycleOwner, Observer { bowler ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
如果您想更简洁一点,并且您真的不需要对 的实时引用updatedBowlers
,那么您可以通过以下方式updateBowlers
与观察者结合:
Transformations.map(viewModel.newHDCP) {
viewModel.bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return@map viewModel.bowlers
}.observe(viewLifecycleOwner, Observer { bowler ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
基本上就是这样。每当您更改 的值时newHDCP
,将调用 中指定的函数Transformation.map()
,它将使用新计算的转换保龄球对象avgWHDCP
并返回 LiveData 对象List<Bowler>
转换.switchMap()
fun <X, Y> switchMap(source: LiveData<X>, switchMapFunction: Function<X, LiveData<Y>!>): LiveData<Y>
source
switchMapFunction
- 一旦更改触发器执行的 LiveData 变量。
switchMapFunction
- 当源 LiveData 发生更改时调用的函数。参数 X 是对同一源对象的引用(通过it
)。该switchMapFunction
函数必须返回一个 LiveData 结果,该结果通过Transformation.switchMap()
. 从本质上讲,这允许您将 LiveData 容器对象的一个引用换成另一个引用。
当您有一个引用 LiveData 对象的变量,并且您想要将该变量切换到另一个变量时使用switchMap()
,或者以不同的方式说您想要刷新现有的 LiveData 容器。这很有用,例如,如果您的 LiveData 变量正在引用数据库数据存储并且您想使用不同的参数重新查询。 switchMap
允许您重新执行查询并替换为新的 LiveData 结果。
示例:
假设一个数据库存储库包含来自 BowlingBall DAO 表的一堆保龄球查询:
private val repository = BowlingBallRepository(application)
我想执行一个查询来获取活动或非活动保龄球,具体取决于用户指定的内容。通过 UI,用户可以选择活动或非活动,因此我的查询需要同时处理两者。因此,我创建了一个MutableLiveData
保持活动或非活动状态的变量。在此示例中,我默认为“A”表示活动。
var activeFlag:MutableLiveData<String> = MutableLiveData(“A”)
现在,我们需要一个 LiveData 变量来保存我的查询结果,以获取特定状态的所有保龄球。所以我创建了一个名为allBowlingBalls
type的变量LiveData<List<BowlingBallTable>>?
并将其分配给Transformation.switchMap
. 我将变量以及将接收相同变量(通过)的 lambda 函数传递给switchMap
函数,并且该函数调用数据库存储库中的查询以重新获取具有传递状态的所有保龄球。lambda 函数的 LiveData 结果通过该方法传回并重新分配给.activeFlag
activeFlag
it
switchMap
allBowlingBalls
private var allBowlingBalls: LiveData<List<BowlingBallTable>>? = Transformations.switchMap(activeFlag) {repository.getAllBalls(it)}
我需要一种方法来触发allBowlibgBalls
. 同样,这将在activeFlag
更改时完成。在您的代码中的某处,添加一个函数来更新activeFlag
。在我的示例中,当单击单选按钮时,activeFlag
将被更改,并且该过程将触发调用中指定的函数Transformations.switchMap()
rbActive.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
viewModel.activeFlag.value = ActiveInactive.ACTIVE.flag
refreshRecycler()
}
}
最后,所有这些只有在观察到 allBowlingBalls 时才会起作用。所以首先创建一个函数来获取 allBowlingBalls:
fun getAllBowlingBalls():LiveData<List<BowlingBallTable>>? {
return allBowlingBalls
}
然后放置一个观察者getAllBowlingBalls()
:
viewModel.getAllBowlingBalls()?.observe(viewLifecycleOwner, Observer { balls ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
就是这样。每次activeFlag
更改时,allBowlingBalls
都会通过调用存储库来刷新,并且会触发onChange
观察者的事件。allBowlingBalls
一种本质上构建动态搜索引擎的简单技术。
让我用一个例子来解释我的理解。考虑一个学生数据类
data class Student(val name: String, val marks: Int)
变换.map()
将 LiveData 的值转换为另一个值。它获取值,将函数应用于该值,并将函数的输出设置为它返回的 LiveData 上的值。这是如何将其用于上述数据类的示例:
val student: LiveData<Student> = (get liveData<Student> from DB or network call)
val studentName: LiveData<String> = Transformations.map(student) {it.name}
在这里,我们从网络或数据库中获取学生 LiveData,然后我们从作为 Student 对象的 LiveData 中获取值,然后获取学生的姓名并将其映射到另一个 LiveData。
转换.switchMap()
将 LiveData 的值转换为另一个 LiveData。考虑我们要为学生实现搜索功能。每次搜索文本更改时,我们都希望更新搜索结果。以下代码显示了它是如何工作的。
val searchQuery: LiveData<String> = ...
val searchResults: LiveData<List<Student>> =
Transformations.switchMap(searchQuery) { getSearchResults(it) }
fun getSearchResults(query: String): LiveData<List<Student>> = (get liveData<List<Student>> from DB or network call)
因此,这里每次 searchQuery 中有一个新值时,都会使用新的搜索查询调用 getSearchResults 并更新 searchResults。
他们有不同的用例:
如果您有源 LiveData 并且只想将该 LiveData 中的值更改为其他数据类型,请使用map
如果您有一个源 LiveData 和一个返回 LiveData 的函数,并且您想要创建一个 LiveData 来更新基于该函数返回的 LiveData 的值。利用switchMap
分析源代码,我们看到两者switchmap
并map
返回一个新的 MediatorLiveData 实例。
map
接收一个返回该 MediatorLiveData 新值的函数,同时switchmap
接收一个返回 LiveData 新实例的函数(然后,如果 LiveData 的新实例的值发生变化,则使用它来更新 MediatorLiveData 的值)
换句话说,switchmap
如果该输入函数的 LiveData 值发生变化,则 的 LiveData 值会发生变化,switchmap
还具有取消注册从该输入函数返回的先前 LiveData 的额外好处。
根据我的经验,两者都是用你更新的内容(livedata #1)和你真正关心/观察的内容(livedata #2)来建立一座桥梁。这座桥是必要的,这样您就可以将观察者(即您的片段)的生命周期带到查看模型,然后他们可以自动LiveData
放弃所有参与的订阅。这是从一开始的主要承诺之一。所以,这将信守诺言。LiveData
如果switchMap
桥是动态的,则函数(lambda)总是返回一个新 LiveData
的 - 所以你切换到这个新的LiveData
。有了map
它是静态的。
我希望它有点帮助。
简而言之,命名类似于 rx map/switchMap。
Map 是一对一的映射,很容易理解。
另一方面,SwitchMap 一次只映射最近的值,以减少不必要的计算。
希望这个简短的答案可以轻松解决每个人的问题。
这是一个简短的
如果您希望结果值反复更改,请使用swithMap() ,如果只是一次操作,请改用map()。
示例:如果您想显示现场比赛的分数,请使用 swithMap() 。如果你想显示球队的球员名单,请使用 map()