LiveData类的这两种方法之间有什么区别?官方文档和教程对此含糊不清。在map()方法中,第一个参数称为source,而在switchMap()中,其称为trigger。这背后的理由是什么?
LiveData类的这两种方法之间有什么区别?官方文档和教程对此含糊不清。在map()方法中,第一个参数称为source,而在switchMap()中,其称为trigger。这背后的理由是什么?
Answers:
根据文档
对LiveData对象中存储的值应用一个函数,并将结果传播到下游。
与map相似,对存储在LiveData对象中的值应用一个函数,并将结果解包并分派到下游。传递给switchMap()的函数必须返回LiveData对象。
换句话说,我可能不是100%正确的,但是如果您熟悉RxJava的话;Transformations#map
与Observable#map
&Transformations#switchMap
相似Observable#flatMap
。
让我们举个例子,有一个LiveData发出一个字符串,我们想用大写字母显示该字符串。
一种方法如下:在活动或片段中
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
传递给map
return的函数仅返回一个字符串,但Transformation#map
最终返回a的是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,我是输入类型,并在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的String的更改。
/*
* 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 dos()),但是这次函数返回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()
接受一个String并开始LiveData<User>
使用它。使用字符串从Web或db查询用户,并得到LiveData<User>
一个结果。
上面已经有一些不错的答案,但是直到我理解为止,我仍在与他们一起努力,所以我将尝试以我的思维方式为人们解释一个具体的示例,而无需涉及技术细节和代码。
在这两种情况中map
,switchMap
都有一个源(或触发器)实时数据,在两种情况下,您都希望将其转换为另一个实时数据。您将使用哪一个-取决于转换要执行的任务。
map
考虑一个到处使用的简单示例-您的源实时数据包含一个User
对象- LiveData<User>
,它指向当前登录的用户。您想在用户界面中显示一个文字,说Current user: <USERNAME>
。在这种情况下,来自源的每个变化信号应恰好触发结果“映射”的一个信号LiveData
。例如,当前User
对象是“ Bob”,则UI文本显示Current user: Bob
。一旦LiveData<User>
触发一个更改用户界面将观察它和更新文本Current user: Alice
。非常简单,线性,一对一的变化。
switchMap
考虑以下示例-您想创建一个UI,该UI显示名称与给定搜索词匹配的用户。我们可以很聪明,将搜索词作为LiveData保存!因此,它将是a,LiveData<String>
并且每次用户输入新的查询字符串时,我们的Fragment
/Activity
都会简单地将文本输入值设置为此实时数据ViewModel
。结果,此实时数据将触发更改信号。一旦收到此信号,我们便开始搜索用户。现在让我们考虑一下我们的搜索是如此之快,以至于它立即返回一个值。此时,您认为您可以使用map
并返回匹配的用户,这将更新用户界面。好吧,现在您将遇到一个错误-假设您定期更新数据库,而在下一次更新后,将出现更多与搜索字词匹配的用户!如您所见,在这种情况下,源触发器(搜索词)不一定会导致映射实时数据的单个触发,提供给UI的映射实时数据可能仍需要在添加新用户之后继续触发值。数据库。在这一点上,您可能会说,我们可以返回“更智能”的实时数据,该数据不仅将等待源触发器,而且还将监视数据库以查找与给定条件匹配的用户(您可以在Room
数据库不存在的情况下执行此操作)。的盒子)。但是随之而来的另一个问题是-如果搜索字词发生变化该怎么办?所以你的任期是x
,它触发了一个实时数据,该数据查询用户并密切关注数据库,然后返回userx, userxx
,然后在五分钟后返回userx, userxxx
,依此类推。然后,该术语更改为y
。现在,我们需要以某种方式停止收听为用户提供的智能实时数据x
,并使用新的智能实时数据进行切换,以监视并为用户提供y
其名字。而这正是switchMap
在做!请注意,此切换必须以这样一种方式完成:在您的UI中,您只需编写switchMap(...).observe
一次,这意味着switchMap
必须返回一个包装器LiveData
,该包装器在整个执行过程中将保持不变,但会在后台切换实时数据源我们。
尽管乍一看它们看上去是一样的,但是用例map
和switchMap
却是不同的,一旦开始实现用例,您会感觉要使用哪个用例,主要是当您意识到在映射函数中必须调用一些来自其他模块的代码(如Repositories
)返回LiveData
。
val searcResultLiveData = database.getFirstUserMatching("alice")
并开始收听“驴友”的搜索结果更改。然后,您需要将其映射到字符串“ Found:<用户名>”。映射将不起作用,因为您会调用它map
,searcResultLiveData
但是一旦更改了搜索词,就必须更改搜索结果实时数据,则还需要更新映射的实时数据。
X
-让我们对其进行调用val liveX: LiveData<X>
。然后您需要保留一个实时数据对象Y
,该对象取决于X
:val liveY: LiveData<Y>
。实施地图将是合乎逻辑的:val liveY = liveX.map {...}
。然后,您将开始在{...}
and bam中编码映射功能!您意识到,在映射函数中,您必须调用返回实时数据的第三方函数(如DB调用)!然后,您别无选择,只能使用switchMap
代替map
。
switchMap:假设我们正在寻找用户名Alice。存储库正在创建该User LiveData类的新实例,然后,我们显示用户。一段时间后,我们需要查找用户名Bob,在存储库中创建了LiveData的新实例,我们的UI订阅了该LiveData。因此,此刻,我们的UI订阅了两个LiveData实例,因为我们从未删除前一个实例。因此,这意味着无论我们的存储库何时更改用户数据,它都会发送两次订阅。现在,我们如何解决这个问题……?
我们真正需要的是一种机制,只要我们要观察新的源,就可以停止从先前的源进行观察。为此,我们将使用switchMap。在后台,switchMap使用MediatorLiveData,只要添加了新的源,它就会删除初始源。简而言之,它完成了所有机制,为我们删除和添加了一个新的Observer。
但是地图是静态的,它在您每次不必强制获取新的实时数据时使用
fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?
trigger
-一次更改的LiveData变量触发mapFunction
执行。
mapFunction
-对trigger
LiveData进行更改时要调用的函数。参数X是对trigger
(via it
)的引用。该函数返回指定类型Y的结果,该结果最终map()
作为LiveData对象返回。
使用map()
时要执行的操作(通过mapFunction
)的时候trigger
LiveData变量的变化。 map()
将返回一个LiveData对象,应注意观察该对象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圆顶硬礼帽对象列表返回。请注意,在此示例中,原始的非LiveData圆顶硬礼帽列表和返回的圆顶硬礼帽列表将反映相同的值,因为它们引用相同的数据存储。但是,该功能的结果是可观察到的。圆顶硬礼帽的原始列表不是,因为它没有设置为LiveData。
var updatedBowlers: LiveData<List<Bowler>> = Transformations.map(newHDCP) {
bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return@map bowlers
}
在代码中的某处,添加一个update方法newHDCP
。在我的示例中,当单击单选按钮时,newHDCP
将被更改,并且该过程将触发以调用在Transformations.map()
rbUpdateBy20.setOnCheckedChangeListener { _, isChecked ->
viewModel.bowlingBallObject.newHDCP.value = 20
}
最后,所有这些只有在updatedBowlers
观察到的情况下才能起作用。这可以通过以下方法放置在“活动”或“片段”中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>
fun <X, Y> switchMap(source: LiveData<X>, switchMapFunction: Function<X, LiveData<Y>!>): LiveData<Y>
source
-一次更改的LiveData变量将触发switchMapFunction
执行。
switchMapFunction
-对源LiveData进行更改时调用的函数。参数X引用相同的源对象(通过it
)。该switchMapFunction
函数必须返回一个LiveData结果,从而有效地得到通过返回Transformation.switchMap()
。本质上,这使您可以将LiveData容器对象的一个引用换成另一个引用。
使用switchMap()
时,你有一个变量引用LiveData对象,你想这个变量切换到另一个,或者说,它要刷新现有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
函数,该activeFlag
函数将接收相同的activeFlag
变量(通过it
),并且该函数调用数据库存储库中的查询以重新获取具有传递状态的所有保龄球。lambda函数的LiveData结果通过该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)
将LiveData的值转换为另一个值。它获取值,将Function应用于该值,然后将Function的输出设置为它返回的LiveData上的值。这是如何将其用于上述数据类的示例:
val student: LiveData<Student> = (get liveData<Student> from DB or network call)
val studentName: LiveData<String> = Transformations.map(student) {it.name}
在这里,我们从网络或数据库中获取学生LiveData,然后从LiveData中获取值(即Student对象),然后获取学生的姓名并将其映射到另一个LiveData。
将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。