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

时间:2017-11-30 14:36:18

标签: android android-architecture-components android-livedata

LiveData类的这两种方法有什么区别?官方文档和教程对此非常模糊。在 map()方法中,第一个参数名为 source ,但在 switchMap()中称为触发器。这背后的理由是什么?

11 个答案:

答案 0 :(得分:17)

根据文件

Transformations.map()

  

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

Transformations.switchMap()

  

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

换句话说,如果您熟悉RxJava,我可能不是100%正确; Transformations#map有点类似Observable#map& Transformations#switchMapObservable#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: BobLiveData<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()
每次SourceLiveData更改时,

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,您可以将源实时数据仅用作触发器,以返回独立的实时数据(当然,您可以在函数输入中使用触发数据)

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

答案 5 :(得分:3)

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

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

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

答案 6 :(得分:2)

Map()在概念上与RXJava中的用法相同,基本上您是在另一个Java中更改LiveData的参数 enter image description here

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

答案 7 :(得分:0)

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

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

另一方面,

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

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

答案 8 :(得分:0)

Transformation.map()

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对象}

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容器对象的一个​​引用换为另一个。

当您有一个引用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)

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。

答案 10 :(得分:0)

这里是一个简短的

如果您希望结果值重复更改,请使用 swithMap() 如果只是一次操作,请使用 ma​​p() 代替。

示例:如果您想显示现场比赛的分数,请使用 swithMap() 。 如果你想显示一个团队的球员列表,使用 map()