Create Your Own Widgets
On this page
If none of the existing widgets fit your use-case, you can implement your own!
You are trying to create your own widget with Android InstantSearch and that’s awesome 🎉. But that also means that you couldn’t find the widgets or built-in options you were looking for. We’d love to hear about your use case as our mission with our InstantSearch libraries is to provide the best out-of-the-box experience. Don’t hesitate to send us a quick message explaining what you were trying to achieve either using the form at the end of that page or directly by submitting a feature request.
Overview
Creating a widget takes three steps:
- Create the
MyWidgetViewModel
, containing the business logic for your widget. - Create a
MyWidgetView
interface, describing the rendering of the widget data.- Implement your view in a
MyWidgetViewImpl
that you’ll use.
- Implement your view in a
- Create the
Connection
s between yourViewModel
and the other components:- Create a
MyWidgetConnectionView
to connect yourViewModel
to itsView
. - If it uses the
Searcher
, create aMyWidgetConnectionSearcher
. - If it uses the
FilterState
, create aMyWidgetConnectionFilterState
.
- Create a
Example
We will build a widget that displays the number of searches made since it was last clicked.
Create the ViewModel
Our ViewModel
will be quite straightforward: it stores a sum
that can be increment
ed or reset
ed to 0.
We will use InstantSearch’s SubscriptionValue
to allow subscribing to changes of the sum
’s value.
1
2
3
4
5
6
7
8
9
10
11
12
class SumSearchesViewModel {
val sum = SubscriptionValue(0)
fun increment() {
sum.value++
}
fun reset() {
sum.value = 0
}
}
Create the View
interface
To interact with the data in our ViewModel
, we need a view than can display a number, and handle clicks for resetting.
1
2
3
4
5
interface SumSearchesView {
fun setSum(sum: Int) // will be called on new sum
var onReset: Callback<Unit>? // will hold the callback to reset the sum
}
Implementing our View
We can now implement a SumSearchesView
: it should display the data received in setSum
and trigger the onReset
when clicked.
1
2
3
4
5
6
7
8
9
10
11
12
class SumSearchesViewImpl(val view: TextView) : SumSearchesView {
init {
view.setOnClickListener { onReset?.invoke(Unit) }
}
override fun setSum(sum: Int) {
view.text = "$sum searches."
}
override var onReset: Callback<Unit>? = null
}
Create the ConnectionView
To link our ViewModel
and its View
, we will define a connection to describe what should happen when connecting (subscribe to sum
and set the reset callback) and when disconnecting (unsubscribe to sum
and remove the callback).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class SumSearchesConnectionView(
private val viewModel: SumSearchesViewModel,
private val view: SumSearchesView
) : ConnectionImpl() {
private val updateViewSum: (Int) -> Unit = {
view.setSum(it)
}
override fun connect() {
super.connect()
viewModel.sum.subscribePast(updateViewSum)
view.onReset = { viewModel.reset() }
}
override fun disconnect() {
super.disconnect()
viewModel.sum.unsubscribe(updateViewSum)
view.onReset = null
}
}
Create the ConnectionSearcher
Because our widget needs to be aware of searches to count them, it needs to be connected to a Searcher
.
We will subscribe to its response
to call increment()
on every new search response.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
data class SumSearchesConnectionSearcher(
private val viewModel: SumSearchesViewModel,
private val searcher: SearcherSingleIndex
) : ConnectionImpl() {
private val updateSum: (ResponseSearch?) -> Unit = {
viewModel.increment()
}
override fun connect() {
super.connect()
searcher.response.subscribe(updateSum)
}
override fun disconnect() {
super.disconnect()
searcher.response.unsubscribe(updateSum)
}
}
Convenient functions
To simplify usage of our widget, we will create two extension functions that connect the ViewModel
to other components:
1
2
3
4
5
6
7
8
9
10
11
fun SumSearchesViewModel.connectView(
view: SumSearchesView
): Connection {
return SumSearchesConnectionView(this, view)
}
fun SumSearchesViewModel.connectSearcher(
searcher: SearcherSingleIndex
): Connection {
return SumSearchesConnectionSearcher(this, searcher)
}
Putting it all together
You just created your first custom widget, congratulations! 🎉
Now you can use it in your application like any other widget:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
val searcher = SearcherSingleIndex(index) // Initialize your Searcher as usual
val view = TextView(context) // Create or find the view you want to use
// Create a connectionHandler to hold all connections
val connection = ConnectionHandler()
// Create your ViewModel and View implementation
val viewModel = SumSearchesViewModel()
val sumView = SumSearchesViewImpl(view)
// Connect your ViewModel to start displaying the count of searches
connection += viewModel.connectSearcher(searcher)
connection += viewModel.connectView(sumView)
// When you want to disconnect everything and ensure no memory leak
// for example in your Activity's onDestroy()`
connection.disconnect()