9

RxJava Filtering Operators [FREE]

 3 years ago
source link: https://www.raywenderlich.com/10928684-rxjava-filtering-operators
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RxJava is a powerful library for handling asynchronous code based on events. It avoids callbacks by providing a composable style of programming. RxJava provides many versatile operators you can use to process your data in straightforward ways.

In this tutorial, you’ll learn about RxJava Filtering Operators, a group of powerful operators that let you work with the data you need at a specific step of the data transformation process.

In the process you’ll learn how to:

  • Take a select number of elements from your data stream.
  • Let your data stream skip elements based on a condition.
  • Filter your data based on a predicate.
  • Take an element only after the user finishes giving input.
  • Act on sequential elements only when they differ.

These operators will form the base of your RxJava vocabulary. Time to dive in!

Note : This tutorial assumes you’re familiar with the basics of Android development in Kotlin with RxJava. If you’re new to Kotlin and Android, check out thisKotlin tutorial and this Beginning Android Development tutorial.

To start learning RxJava read this Reactive Programming with RxAndroid tutorial.

Getting Started

Download the starter project by clicking on the Download Materials button at the top or bottom of the tutorial. Then, open the starter project in Android Studio, where you’ll find Foodeat , a restaurant-searching app.

Build and run. You’ll see the following screen:

1-243x500.png

You’ll see a list of restaurants ordered from highest to lowest rated. The app stores the restaurants in a Room database. You’ll also see a search field and a menu with some items that represent filtering criteria on the toolbar.

2-243x500.png

You’ll develop features to filter information based on the criteria given by the search field and menu items. With this information, you’ll quickly pick somewhere to eat. :]

Before you start coding, take a moment to review some theory about what RxJava is.

What is RxJava?

RxJava is a library that extends the observer pattern for processing asynchronous code by using sequences and functional style operators. RxJava provides several advantages. With it, you can:

  • React to new data in a sequential and isolated manner.
  • Simplify multithreading.
  • Avoid callback hell.
  • Use operators that let you declare how to process data in each step of the chain in a simple way.

To make the most of these advantages, RxJava provides several helpful operators.

What are RxJava Filtering Operators?

RxJava comes with several operators to process data streams. You’ll use RxJava Filtering Operators extensively throughout your RxJava career. These filtering operators apply conditional constraints to the events you receive on your data stream.

filter_mod_marbles-480x201.png

You can apply these constraints to limit the values emitted by your stream, ensuring you work with only the elements you need. For example, the image above shows you can use a filter operator to get only even values from your stream.

The most common filtering operators are:

debounce
distinct
elementAt
filter
ignoreElements
last
skip
skipLast
take
takeLast
takeUntil

While you’ll only work with a handful of these operators in this tutorial, you can use what you learn here to implement the other filtering operators. The first operator you’ll use is take .

Using the Take Operator

The take operator emits only the first n items emitted and then completes.

take_marbles-480x201.png

You’ll use this operator to take and display the highest rated restaurants. Open RestaurantViewModel.kt and replace getTopRestaurants() with:

fun getTopRestaurants() {
  _restaurantsLiveData.value = Resource.Loading

  restaurantSource
      .take(5)
      .showResults()
}

Here you get the stream of restaurants, let through only the first five emitted values, and then show the results. Look at the bottom of RestaurantViewModel.kt . You’ll see showResults() :

//1
private fun Observable<Restaurant>.showResults() {
  this.toList() //2
      .subscribeOn(Schedulers.io()) //3
      .map { Resource.Success(it) } //4
      .subscribe(_restaurantsLiveData::postValue) //5
      .addTo(disposables) //6
}

This handy extension function helps you keep your code clean and DRY , or Don’t Repeat Yourself. Here’s a detailed breakdown:

  1. You create an extension function for showing the results for any Observable emitting Restaurant values.
  2. As soon as the Observable completes, you create a list with all the emitted values.
  3. Then you subscribe to the corresponding scheduler.
  4. You wrap the resulting list in a Resource object with a type of Success .
  5. Next, you send all the Resource values to the corresponding LiveData .
  6. Finally, you add the resulting disposable to a CompositeDisposable which is disposed of when the ViewModel is removed. This avoids memory leaks. Remember to always clean up after yourself. :]

Note : In RestaurantViewModel.kt you’ll find two observables that serve as your data source. The first one, restaurantsSource , is the direct result from the query to your Room database. The second one, restaurantSource , is the previous observable but its elements are flatten so you can work directly with each element to assure the stream completes.

Build and run. Go to the toolbar menu and tap the Top 5 Rated item. You’ll now see the filtered list showing the highest-rated restaurants.

5-243x500.png

The Take Operator Variations

There are many overloads and variations of take you can choose from to best suit your needs:

  • takeLast : Only emits the final n items emmitted by an Observable . This operator needs an Observable that completes to know which elements are last. Otherwise, it won’t emit them.
  • takeUntil : Emits items until a secondary Observable triggers by emitting an item or terminating.
  • takeWhile : Emits items while a specified condition is true.
  • elementAt : This is not necesarily a direct descendant of the take operator. But, by specifing an index, you can take that particular element in the corresponding index, achieving a very similar behavior.

Next, you’ll work with the skipwhile operator.

Using the SkipWhile Operator

Next, you’ll filter your restaurant list to show only restaurants rated three stars or fewer. You can start reducing your short-list for possible restaurants the next time you go out. :]

The skipWhile operator lets you skip values while a certain condition is true. When the condition is no longer met, the Observable starts emitting the rest of the values.

skipWhile_marbles-480x201.png

In RestaurantViewModel.kt , replace getLowestRatedRestaurants() with:

fun getLowestRatedRestaurants() {
  _restaurantsLiveData.value = Resource.Loading

  restaurantSource
      .skipWhile { it.rating > 3 }
      .showResults()
}

You get the restaurant stream of values, then skip all the values while the rating is above three.

Build and run. Go to the toolbar menu, but this time tap the Rated 3 and Below item. Now, you’ll see a list with all the restaurants with a rating of three stars and below. Instead of taking or skipping a specific number of items, you’re filtering for a particular condition.

7-243x500.png

Now that you’re more comfortable filtering your data, it’s time to go further.

The SkipWhile Operator Variations

skipWhile also has a few varients, each with their own conditions:

  • skip : Skips the first n values and then emits the rest.
  • skipLast : This operator won’t emit unless it has an Observable that completes and lets it know which elements are last. It skips the final n items emmitted by an Observable .
  • skipUntil : Skips items until a secondary Observable triggers by either emitting an item or terminating.

Sometimes you’ll only care about emitted values when the data stream completes. The ignoreElements operator is the perfect candidate for these occasions.

Using the IgnoreElements Operator

ignoreElements transforms the Observable in a Completable . A Completable behaves similary to an Observable except it only emits terminal events, either onError or onComplete , and no values.

ignoreElements_marbles-480x201.png

You’ll use ignoreElements to tell the user when data was successfully fetched and filtered. In RestaurantViewModel.kt , add the following code to the bottom of showResults :

this.ignoreElements() //1
    .subscribeOn(Schedulers.io()) //2
    .observeOn(AndroidSchedulers.mainThread()) //3
    .subscribe {
      _uiLiveData.value = Resource.Success(Unit) //4
    }
    .addTo(disposables) //5

Here’s a code breakdown:

  1. Any Observable useing this extension also triggers a Completable .
  2. The completable subscribes to the corresponding scheduler.
  3. Then the completable observes the stream of data on the main thread provided by RxAndroid.
  4. Everytime the stream of data completes, the completable sends a Resource of type Success to the activity through the corresponding LiveData .
  5. Finally, the completable adds the resulting disposable to a CompositeDisposable that disposes when the ViewModel is removed.

Your whole extension will look like this:

private fun Observable<Restaurant>.showResults() {
  this.toList()
      .subscribeOn(Schedulers.io())
      .map { Resource.Success(it) }
      .subscribe(_restaurantsLiveData::postValue)
      .addTo(disposables)

  this.ignoreElements()
      .subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe {
        _uiLiveData.value = Resource.Success(Unit)
      }
      .addTo(disposables)
}

Since both your getTopRestaurants() and getLowestRatedRestaurants() use this extension for displaying results, your Activity is notified on a separate LiveData when those streams have successfully completed.

Build and run. Go to the toolbar menu and tap either Top 5 Rated or Rated 3 and Below . A toast will notify you as soon as the data processing completes.

9-243x500.png

Next, you’ll learn about the filter operator.

Using the Filter Operator

The aptly-named filter is one of the most important and versatile filtering operators. It only passes values that match a particular predicate you declare.

filter_search_marbles-480x201.png

The filter operator is a perfect fit for shortening your list to only the restaurants that match your specific search query. Open your RestaurantViewModel.kt and replace setQueryListener() with:

//1
fun setQueryListener(queryObservable: Observable<String>) {
  queryObservable
      .observeOn(AndroidSchedulers.mainThread())
      .map(::filterSource) //2
      .subscribe()
}


private fun filterSource(query: String) {
  Log.d(this::class.java.simpleName, "Search query: $query")

  _restaurantsLiveData.value = Resource.Loading

  restaurantSource //3
      .filter { restaurant ->
        if (query.isEmpty()) return@filter true //4
        restaurant.name.contains(query, true)//5
      }
      .showResults()
}

Here’s a breakdown:

queryObservable
filterSource()

Build and run. Tap the search icon, input Isl and notice your list is filtered with every letter you type.

With the filter operator, you can tap every element of your stream and sequentially and simply filter it with any conditional logic you see fit.

11-243x500.png

Next, you’ll learn about the debounce operator.

Using the Debounce Operator

Look at your Logcat and filter it to show logs from RestaurantViewModel. Notice how the filtering occurs with every letter you enter.

logcat_no_debounce_distinct-1-650x231.png

If you were using a remote server, this would have a significant impact on your app's performance. It could raise your server costs for unnecessary transactions. Ideally, you'll only call your data source when you're relatively sure the query you received is the one the user wants to search for.

This problem seems like a big headache, but RxJava has your back with debounce .

The debounce operator only emits an item from an Observable if a particular time has passed without emitting another item.

debounce_marbles-480x201.png

This isn't as confusing as it may seem. debounce lets through only the last item in a certain timespan. Open RestaurantViewModel.kt and replace setQueryListener() with:

fun setQueryListener(queryObservable: Observable<String>) {
  queryObservable
      .debounce(300, TimeUnit.MILLISECONDS)
      .observeOn(AndroidSchedulers.mainThread())
      .map(::filterSource)
      .subscribe()
}

All you did is add the debounce operator. Here, debounce only lets values through after a pause of 300 milliseconds . From the app perspective, this means the Observable waits to emit the last item, which will be the most recent query, until the user stops typing characters for at least 300 milliseconds.

As a result, you get considerable performance improvement with very little code.

Build and run. Again, search for Isl and try to write without stopping much between characters. In your Logcat , you'll see the Observable only let through and filtered one query as expected, Isl .

Without the help of RxJava, adding this performance-boosting functionality will be cumbersome.

logcat_with_debounce-650x231.png

11-243x500.png

Note : There's a family of similar operators that are also handy in situations like this, throttle . The throttle operator emits only one item from a group of emitted values within periodic time intervals. Here are two for reference:
throttleFirst
throttleLast

It's time for the last operator, distinctUntilChanged .

Using the DistinctUntilChanged Operator

You'll use the last operator to resolve a small edge case. When you search for a specific query and quickly add and remove a letter, you might be fast enough to beat your debounce operator. If you do, you'll search and filter your list based on the same search query.

Build and run, then search for Isl . As soon as the list filters go on, add an a and remove it within your debounce 300 milliseconds time period.

Check your Logcat and see the list filters based on the same search query. Since that query already filtered the list, this search is unnecessary.

logcat_no_distinct-650x232.png

11-243x500.png

This is an easy fix with the distintctUntilChanged operator. This operator only lets through values that are different from their predecessors. Any sequential duplicated items are suppressed.

1-1-480x200.png

Open RestaurantViewModel.kt and replace setQueryListener with:

fun setQueryListener(queryObservable: Observable<String>) {
  queryObservable
      .debounce(300, TimeUnit.MILLISECONDS)
      .distinctUntilChanged()
      .observeOn(AndroidSchedulers.mainThread())
      .map(::filterSource)
      .subscribe()
}

Here you added the distintUntilChanged operator. Build and run, search for Isl and then add and remove the letter a within the 300 milliseconds debounce period. Check you Logcat and you'll see your data was only fetched and filtered once with the query Isl !

logcat_with_debounce-650x231.png

11-243x500.png

Note : There's a more general distinct operator. Instead of suppressing sequential duplicates, as distinctUntilChanged does, it suppresses duplicates anywhere on your data stream.

Where to Go From Here?

You can download the final version of this project using the Download Materials button at the top or bottom of this tutorial.

Congratulations on making it all the way through! Now you can work with filtering operators and understand their full potential. There are many ways of achieving the same result. The path you take depends on what you need and how you compose your operators.

If you want to continue your journey in RxJava, check out the ReactiveX Filtering Operators documentation. Another great resource is our awesome book, Reactive Programming with Kotlin .

If you have any questions, comments, or want to show off what other filtering operators you used in this project, feel free to join the discussion below!


Recommend

  • 121
    • www.jianshu.com 6 years ago
    • Cache

    RxJava 之 TestScheduler - 简书

    TestScheduler 是专门用于测试的调度器,跟其他调度器的区别是TestScheduler只有被调用了时间才会继续。TestScheduler是一种特殊的、非线程安全的调度器,用于测试一些不引入真实并发性、允许手动推进虚拟时间的调度器。 在 RxJava2.x 中,原先RxJava1.x的Schedule...

  • 150

    作者: @怪盗kidou如需转载需在明显位置保留作者信息及原文链接如果博客中有不恰当之处欢迎留言交流http://www.jianshu.com/p/4e78d447394e 1、前言 一年多没有写新博客了,今天又来水一篇,算是对《你真的会用RxJava么?RxJava线程变换之observeOn与subscribeOn》...

  • 177

    前言 由于当前正在写的项目集成了两个网络请求框架(Volley and Retrofit)对比之下也是选择了Retrofit。既然选择那自然要让自己以后开发更加省力(就是懒)。于是我在Retrofit中加入了Rxjava,这也是当下蛮流行的一个请求框架。然后又利用了Kotlin的一些新特性,...

  • 145
    • 掘金 juejin.im 6 years ago
    • Cache

    Rxjava这一篇就够了,墙裂推荐

    最近项目里面有用到Rxjava框架,感觉很强大的巨作,所以在网上搜了很多相关文章,发现一片文章很不错,今天把这篇文章分享给大家,感谢原作者的倾心贡献,那么下面大家来看看这篇文章:前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboar

  • 82
    • zhuanlan.zhihu.com 6 years ago
    • Cache

    深入简出 RxJava

    深入简出 RxJavanekocode社畜 | github.com/nekocode

  • 97
    • www.jianshu.com 6 years ago
    • Cache

    RxJava 学习笔记 (二) - 简书

    作者: 一字马胡 转载标志 【2017-12-15】 更新日志 导入 在RxJava学习笔记系列的第一篇文章中,我分析了RxJava中核心的两个对象Observable和Observer,并且分析了Observer是如何实现订阅一个Observable的,作为学习RxJava的第一篇分析总结,内容较为浅显,本文将...

  • 129

    在Andoid中如何使用RxJava 2进行多线程编程?

  • 87

    摘要: 原创出处 http://www.iocoder.cn/RxJava/scheduler/ 「芋道源码」欢迎转载,保留摘要,谢谢! 本文主要基于 RxJava 1.2.X 版本 本系列写作目的,为了辅助 Hystrix 的理解,因此会较为零散与琐碎,望见谅见谅。 ...

  • 97

    Android RxJava 背压策略:图文 + 实例 全面解析 2018年01月17日 00:47 ·  阅读 3140

  • 8

    FlatList, Filtering in FlatList, Spread Operators in React-Native In Today’s article, we are going to see how to work with FlatLists in React Native and We will add a search bar as a header to the FlatList and...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK