UI libraries / InstantSearch Android / Widgets
Signature
FilterRangeConnector(
    filterState: FilterState,
    attribute: Attribute,
    bounds: ClosedRange<T>?,
    range: ClosedRange<T>?
)

About this widget

Filter Numeric Range is a filtering view made to filter between two numeric values. The most common interface for this is a slider.

The Android View and Compose UI code samples (part of the Android widget showcase repository on GitHub) illustrate the use of sliders.

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class MyActivity : AppCompatActivity() {
    val searcher = HitsSearcher(
        applicationID = ApplicationID("YourApplicationID"),
        apiKey = APIKey("YourSearchOnlyAPIKey"),
        indexName = IndexName("YourIndexName")
    )
    val price = Attribute("price")
    val groupID = FilterGroupID(price)
    val primaryBounds = 0..15
    val initialRange = 0..15
    val filters = filters {
        group(groupID) {
            range(price, initialRange)
        }
    }
    val filterState = FilterState(filters)
    val range = FilterRangeConnector(
        filterState = filterState,
        attribute = price,
        range = initialRange,
        bounds = primaryBounds
    )
    val connection = ConnectionHandler(range, searcher.connectFilterState(filterState))

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val view: NumberRangeView<Int> = MyNumberRangeView(textLabel) // your `NumberRangeView<T>` implementation
        connection += range.connectView(view)
        searcher.searchAsync()
    }

    override fun onDestroy() {
        super.onDestroy()
        connection.disconnect()
        searcher.cancel()
    }
}

Low-level API

If you want to fully control the Filter Numeric Range components and connect them manually, use the following components:

  • Searcher: The Searcher that handles your searches.
  • FilterState: The current state of the filters.
  • FilterRangeViewModel: The logic applied to the numeric ranges.
  • NumberRangeView: The view that renders the numeric range filter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MyActivity : AppCompatActivity() {

    val searcher = HitsSearcher(
        applicationID = ApplicationID("YourApplicationID"),
        apiKey = APIKey("YourSearchOnlyAPIKey"),
        indexName = IndexName("YourIndexName")
    )
    val filterState = FilterState()
    val attribute = Attribute("price")
    val viewModel = FilterRangeViewModel<Int>()
    val connection = ConnectionHandler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val view: NumberRangeView<Int> = MyNumberRangeView(textLabel) // your `NumberRangeView<T>` implementation
        connection += searcher.connectFilterState(filterState)
        connection += viewModel.connectFilterState(filterState, attribute)
        connection += viewModel.connectView(view)

        searcher.searchAsync()
    }

    override fun onDestroy() {
        super.onDestroy()
        connection.disconnect()
        searcher.cancel()
    }
}

Compose UI

InstantSearch provides the NumberRangeState as a state model, which is an implementation of the NumberRangeView interface. You need to connect NumberRangeState to the FilterRangeConnector or FilterRangeViewModel like any other NumberRangeView implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class MyActivity : AppCompatActivity() {
    val searcher = HitsSearcher(
        applicationID = ApplicationID("YourApplicationID"),
        apiKey = APIKey("YourSearchOnlyAPIKey"),
        indexName = IndexName("YourIndexName")
    )
    val price = Attribute("price")
    val groupID = FilterGroupID(price)
    val primaryBounds = 0..15
    val initialRange = 0..15
    val filters = filters {
        group(groupID) {
            range(price, initialRange)
        }
    }
    val filterState = FilterState(filters)
    val sliderState = NumberRangeState<Int>()
    val range = FilterRangeConnector(
        filterState = filterState,
        attribute = price,
        range = initialRange,
        bounds = primaryBounds
    )
    val connections = ConnectionHandler(range)

    init {
        connections += searcher.connectFilterState(filterState)
        connections += range.connectView(sliderState)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyRangeSlider(sliderState) // your own UI composable to display range and bounds as `Range<T>`
        }
        searcher.searchAsync()
    }

    fun Range<Int>.toClosedFloatRange(): ClosedFloatingPointRange<Float> {
        return min.toFloat()..max.toFloat()
    }

    override fun onDestroy() {
        super.onDestroy()
        connections.disconnect()
        searcher.cancel()
    }
}

Parameters

filterState
type: FilterState
Required

The FilterState that will hold your filters.

attribute
type: Attribute
Required

The attribute to filter.

bounds
type: ClosedRange<T>?
Optional

The limits of the acceptable range within which values are coerced.

range
type: ClosedRange<T>?
Optional

The range of values within the bounds.

View

view
type: NumberRangeView
Required

The view that renders the numeric range filter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val view = MyNumberRangeView(textLabel)
range.connectView(view)

// Example of `NumberRangeView<T>` implementation
class MyNumberRangeView(val view: TextView) : NumberRangeView<Int> {
    override var onRangeChanged: Callback<Range<Int>>? = null
    private var bounds: Range<Int>? = null

    override fun setBounds(bounds: Range<Int>?) {
        this.bounds = bounds
        view.text = bounds?.let {
            "Bounds: ${it.min} to ${it.max}"
        } ?: "No bounds"
    }

    override fun setRange(range: Range<Int>?) = Unit
}
Did you find this page helpful?