LiveData类的这两种方法有什么区别?官方文档和教程对此非常模糊。在 map()方法中,第一个参数名为 source ,但在 switchMap()中称为触发器。这背后的理由是什么?
答案 0 :(得分:17)
根据文件
对存储在LiveData对象中的值应用函数,并将结果传播到下游。
与map类似,将函数应用于存储在LiveData对象中的值,并将结果解包并调度到下游。 传递给switchMap()的函数必须返回一个LiveData对象。
换句话说,如果您熟悉RxJava,我可能不是100%正确; Transformations#map
有点类似Observable#map
& Transformations#switchMap
与Observable#flatMap
类似。
让我们举一个例子,有一个LiveData发出一个字符串,我们想用大写字母显示该字符串。
一种方法如下;在活动或片段中
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
传递给map
的函数只返回一个字符串,但最终返回Transformation#map
的{{1}}。
第二种方法;在活动或片段中
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
。所以,再次根据文档传递给switchMap()的函数必须返回一个LiveData对象。
因此,如果LiveData
正在改变来源 map
,而LiveData
,则switchMap
将会采取行动LiveData
作为触发器,在解包并向下游调度结果后,它将切换到另一个LiveData
。
答案 1 :(得分:12)
上面已经有一些不错的答案,但是直到我理解为止,我仍在与他们一起努力,所以我将尝试用我的思维方式为人们解释一个具体的示例,而无需涉及技术细节和代码。
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保存!因此它将是LiveData<String>
,并且每次用户输入新的查询字符串时,我们的Fragment
/ Activity
都会简单地将文本输入值设置为ViewModel
中的此实时数据。结果,此实时数据将触发更改信号。一旦收到此信号,我们便开始搜索用户。现在让我们考虑一下我们的搜索是如此之快,以至于它立即返回一个值。此时,您认为您可以只使用 map
并返回匹配的用户,这将更新UI。好吧,现在您将遇到一个错误-假设您定期更新数据库,而在下一次更新后,将出现更多与搜索字词匹配的用户!如您所见,在这种情况下,源触发器(搜索词)不一定会导致映射实时数据的单个触发,提供给UI的映射实时数据可能仍需要在添加新用户之后继续触发值。数据库。此时,您可能会说,我们可以返回“更智能”的实时数据,该数据不仅将等待源触发器,还将监视数据库中与给定术语匹配的用户(您可以使用{{ 1}}开箱即用。但是随之而来的另一个问题是-如果搜索字词发生变化该怎么办?因此,您的用词是Room
,它触发了一个实时数据,该数据查询用户并关注数据库,它返回x
,然后在五分钟后返回userx, userxx
,依此类推。然后,该术语更改为userx, userxxx
。现在,我们需要以某种方式停止收听为用户提供y
的智能实时数据,并切换它与新的智能实时数据,它将监视并为我们的用户提供{{ 1}}。这正是 x
所做的!请注意,此切换必须以这种方式完成,即在您的UI中只需编写一次y
,这意味着switchMap
必须返回一个包装器switchMap(...).observe
,该包装器将保持不变整个执行过程中,但会切换实时数据源给我们。
尽管乍看之下它们看起来一样,但 switchMap
和 LiveData
的用例却不同,您将获得感觉开始使用案例时应该使用哪种,主要是当您意识到在映射函数中必须从其他模块(例如map
)中调用一些返回switchMap
的代码。>
答案 2 :(得分:6)
我的观察是,如果转换过程很快(不涉及数据库操作或网络活动),那么您可以选择使用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);
}
}
答案 3 :(得分:5)
首先,map()
和switchMap()
方法都在主线程上被调用。它们与用于快速或慢速任务无关。但是,如果您在这些方法而不是工作线程中执行复杂的计算或耗时任务,例如解析或转换了较长和/或复杂的json响应,则可能会导致UI滞后,因为它们是在UI线程上执行的。 / p>
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();
}
});
switchMap()返回相同的MediatorLiveData而不是 new 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()
接受一个字符串,并使用它获取LiveData<User>
。使用字符串从Web或数据库中查询用户,并得到一个LiveData<User>
。
答案 4 :(得分:5)
map
,您最终拥有相同的源实时数据,但是它的数据(值)随提供的功能而变化,然后发出
switchMap
,您可以将源实时数据仅用作触发器,以返回独立的实时数据(当然,您可以在函数输入中使用触发数据)
onChanged()
调用的所有因素答案 5 :(得分:3)
switchMap: 假设我们正在寻找用户名Alice。存储库正在创建该User LiveData类的新实例,然后,我们显示用户。一段时间后,我们需要查找用户名Bob,在存储库中创建了LiveData的新实例,并且我们的UI订阅了该LiveData。因此,此刻,我们的UI订阅了LiveData的两个实例,因为我们从未删除前一个实例。因此,这意味着无论我们的存储库何时更改用户数据,它都会发送两次订阅。现在,我们如何解决这个问题……?
我们真正需要的是一种机制,只要我们要观察新的源,就可以停止从先前的源进行观察。为此,我们将使用switchMap。在后台,switchMap使用MediatorLiveData,只要添加了新的源,它就会删除初始源。简而言之,它完成了所有为我们删除和添加新观察者的机制。
但是地图是静态的,它在您不必每次都强制获取新的实时数据时使用
答案 6 :(得分:2)
Map()在概念上与RXJava中的用法相同,基本上您是在另一个Java中更改LiveData的参数
SwitchMap(),您将用另一个LiveData本身替代它!典型的情况是,例如,当您从存储库中检索一些数据并“消除”以前的LiveData(以进行垃圾回收,通常使其内存效率更高)时,您传递一个 new LiveData来执行相同的操作(例如获取查询)
答案 7 :(得分:0)
简而言之,命名类似于rx map / switchMap。
地图是一对一的映射,很容易理解。
另一方面,SwitchMap仅一次映射最新值以减少不必要的计算。
希望这个简短的答案可以轻松解决每个人的问题。
答案 8 :(得分:0)
fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?
trigger
-一次更改的LiveData变量将触发mapFunction
执行。
mapFunction
-对trigger
LiveData进行更改时调用的函数。参数X是对trigger
的引用(通过it
)。该函数返回指定类型Y的结果,该结果最终由map()
作为LiveData对象返回。
当map()
LiveData变量更改时,要使用mapFunction
执行操作(通过trigger
),请使用map()
。 mapFunction
将返回一个LiveData对象,在调用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
更改时要调用的函数。在此示例中,该函数将遍历所有圆顶硬礼帽对象,为圆顶硬礼帽列表中的每个圆顶硬礼帽计算新的var updatedBowlers: LiveData<List<Bowler>> = Transformations.map(newHDCP) {
bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return@map bowlers
}
,并将结果作为可观察到的LiveData圆顶硬礼帽对象列表返回。请注意,在此示例中,原始的非LiveData圆顶硬礼帽列表和返回的圆顶硬礼帽列表将反映相同的值,因为它们引用相同的数据存储。但是,该功能的结果是可观察到的。圆顶硬礼帽的原始列表不是,因为它没有设置为LiveData。
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
中指定的函数,它将使用新计算的List<Bowler>
转换圆顶硬礼帽对象并返回{{1}的LiveData对象}
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容器对象的一个引用换为另一个。
当您有一个引用LiveData对象的变量,并且想要将该变量切换到另一个变量时,或者说您想刷新现有LiveData容器的另一种方式时,请使用switchMap()
。例如,如果您的LiveData变量正在引用数据库数据存储并且您想使用不同的参数重新查询,则此功能很有用。 switchMap
允许您重新执行查询并替换为新的LiveData结果。
示例:
假设数据库存储库中有来自BowlingBall DAO表的一堆保龄球查询:
private val repository = BowlingBallRepository(application)
我想执行一个查询,该查询根据用户指定的内容来获取活动或不活动的保龄球。通过UI,用户可以选择活动或不活动,因此我的查询需要同时处理这两者。因此,我创建了一个MutableLiveData
变量,该变量保持活动状态或非活动状态。在此示例中,我默认为“ A”表示有效。
var activeFlag:MutableLiveData<String> = MutableLiveData(“A”)
现在,我们需要一个LiveData变量,该变量将保存查询的结果,以获取特定状态的所有保龄球。因此,我创建了一个名为allBowlingBalls
的变量LiveData<List<BowlingBallTable>>?
,并将其分配给Transformation.switchMap
。我将switchMap
变量和一个lambda函数传递给activeFlag
函数,该函数将接收相同的activeFlag
变量(通过it
),并且该函数调用a在数据库存储库中查询以重新获取具有传递状态的所有保龄球。 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
事件。一种基本构建动态搜索引擎的简单技术。
答案 9 :(得分:0)
让我用一个例子解释我的理解。考虑一个学生数据类
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。
答案 10 :(得分:0)
这里是一个简短的
如果您希望结果值重复更改,请使用 swithMap() 如果只是一次操作,请使用 map() 代替。
示例:如果您想显示现场比赛的分数,请使用 swithMap() 。 如果你想显示一个团队的球员列表,使用 map()