map()和switchMap()方法之间有什么区别?


Answers:


72

根据文档

Transformations.map()

对LiveData对象中存储的值应用一个函数,并将结果传播到下游。

Transformations.switchMap()

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

换句话说,我可能不是100%正确的,但是如果您熟悉RxJava的话;Transformations#mapObservable#mapTransformations#switchMap相似Observable#flatMap

让我们举个例子,有一个LiveData发出一个字符串,我们想用大写字母显示该字符串。

一种方法如下:在活动或片段中

Transformations.map(stringsLiveData, String::toUpperCase)
    .observe(this, textView::setText);

传递给mapreturn的函数仅返回一个字符串,但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在展开并向下游分配结果之后,它将切换到另一个触发器


20
那将解释命名。因此,它们都应该在底层LiveData每次更改时都被触发,并且“切换”意味着LiveData将被切换到另一个LiveData对象。谢谢!
Igor Bubelov '17

2
很好的解释-我在仓库中添加了switchMap和Map组合的示例。github.com/ febaisi/ListenableWorkerExample/blob/master/app/src / ...按钮事件并切换到适当的LiveData,它是一个Worker结果的映射。我希望它也有帮助。
febaisi

两种Transformations(map,switchMap)都返回LiveData对象。
Qumber Abbas

30

我的观察是,如果您的转换过程很快(不涉及数据库操作或网络活动),则可以选择使用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);
    }
}

10
简单而清晰的响应,大多数响应只是一遍又一遍地解释它在内部的工作方式,但是我关心的第一件事就是为什么要使用它,而无需了解它的内部行为。谢谢。
Ambroise Rabier '18

26

首先,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();
    }
});
  • switchMap()

每当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>一个结果。


2
好答案!
Narshim

17

Map() 在概念上与RXJava中使用的相同,基本上是在另一个中更改LiveData的参数 在此处输入图片说明

相反,您将使用SwitchMap()将LiveData本身替换为另一个!典型的情况是,例如,当您从存储库中检索一些数据并“消除”以前的LiveData(以进行垃圾回收,通常使其内存效率更高)时,您传递的LiveData执行相同的操作(获取查询实例)


1
到目前为止,唯一可以通过一个原始的简单例子反映现实的答案
Ace

13

上面已经有一些不错的答案,但是直到我理解为止,我仍在与他们一起努力,所以我将尝试以我的思维方式为人们解释一个具体的示例,而无需涉及技术细节和代码。

在这两种情况中mapswitchMap都有一个(或触发器)实时数据,在两种情况下,您都希望将其转换为另一个实时数据。您将使用哪一个-取决于转换要执行的任务。

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,该包装器在整个执行过程中将保持不变,但会在后台切换实时数据源我们。

结论

尽管乍一看它们看上去是一样的,但是用例mapswitchMap却是不同的,一旦开始实现用例,您会感觉要使用哪个用例,主要是当您意识到在映射函数中必须调用一些来自其他模块的代码(如Repositories)返回LiveData


很好的解释。我对差异有清楚的了解,但是如果在搜索用户时使用“ map”而不是“ switchmap”来获取用户对象,并且每次都将其包装在相同的livedata中,将会是一个问题。为什么在查询新用户时需要更改livedata实例本身?@frangulyan
Hari Kiran

@HariKiran因为单个用户搜索的结果是动态的,所以它会随着时间而变化,这就是为什么它是实时数据。因此,假设您打电话val searcResultLiveData = database.getFirstUserMatching("alice")并开始收听“驴友”的搜索结果更改。然后,您需要将其映射到字符串“ Found:<用户名>”。映射将不起作用,因为您会调用它mapsearcResultLiveData但是一旦更改了搜索词,就必须更改搜索结果实时数据,则还需要更新映射的实时数据。
frangulyan

1
@HariKiran在现实世界中,您将从其他角度满足情况。您将为某些对象创建实时数据X-让我们对其进行调用val liveX: LiveData<X>。然后您需要保留一个实时数据对象Y,该对象取决于Xval liveY: LiveData<Y>。实施地图将是合乎逻辑的:val liveY = liveX.map {...}。然后,您将开始在{...}and bam中编码映射功能!您意识到,在映射函数中,您必须调用返回实时数据的第三方函数(如DB调用)!然后,您别无选择,只能使用switchMap代替map
frangulyan

5
  • 最后,map您拥有相同的源实时数据,但是在发出之前,它的数据(值)随提供的功能而变化

  • 使用switchMap,您可以将源活动数据用作返回独立活动数据的触发器(当然,您可以在函数输入中使用触发器数据)

  • 触发:导致livedata观察者onChanged()调用的所有因素

4

switchMap:假设我们正在寻找用户名Alice。存储库正在创建该User LiveData类的新实例,然后,我们显示用户。一段时间后,我们需要查找用户名Bob,在存储库中创建了LiveData的新实例,我们的UI订阅了该LiveData。因此,此刻,我们的UI订阅了两个LiveData实例,因为我们从未删除前一个实例。因此,这意味着无论我们的存储库何时更改用户数据,它都会发送两次订阅。现在,我们如何解决这个问题……?

我们真正需要的是一种机制,只要我们要观察新的源,就可以停止从先前的源进行观察。为此,我们将使用switchMap。在后台,switchMap使用MediatorLiveData,只要添加了新的源,它就会删除初始源。简而言之,它完成了所有机制,为我们删除和添加了一个新的Observer。

但是地图是静态的,它在您每次不必强制获取新的实时数据时使用


0

简而言之,命名类似于rx map / switchMap。

Map是一对一的映射,很容易理解。

另一方面,SwitchMap一次仅映射最新值以减少不必要的计算。

希望这个简短的答案可以轻松解决每个人的问题。


0

Transformation.map()

fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?

trigger-一次更改的LiveData变量触发mapFunction执行。

mapFunction-对triggerLiveData进行更改时要调用的函数。参数X是对trigger(via it)的引用。该函数返回指定类型Y的结果,该结果最终map()作为LiveData对象返回。

使用map()时要执行的操作(通过mapFunction)的时候triggerLiveData变量的变化。 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()
    }
})

基本上就是这样。每当您更改的值时newHDCPTransformation.map()都会调用中指定的函数,它将使用新计算出的圆顶礼帽对象进行转换,avgWHDCP并返回一个LiveData对象List<Bowler>

Transformation.switchMap()

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变量,该变量将保存我的查询结果以获取特定状态的所有保龄球。因此,我创建了一个名为allBowlingBallstype的变量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。一种基本构建动态搜索引擎的简单技术。


0

让我用一个例子来解释我的理解。考虑一个学生数据班

data class Student(val name: String, val marks: Int)

Transformation.map()

将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。

Transformation.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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.