Skip to content

Commit 65a88a2

Browse files
authored
Implement support for local filtering (#14)
1 parent 7d49714 commit 65a88a2

File tree

12 files changed

+549
-62
lines changed

12 files changed

+549
-62
lines changed

stream-android-core/src/main/java/io/getstream/android/core/api/filter/Filter.kt

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,69 @@ package io.getstream.android.core.api.filter
1717

1818
import io.getstream.android.core.annotations.StreamInternalApi
1919
import io.getstream.android.core.annotations.StreamPublishedApi
20-
import io.getstream.android.core.internal.filter.FilterOperator
20+
import io.getstream.android.core.internal.filter.BinaryOperator
21+
import io.getstream.android.core.internal.filter.CollectionOperator
22+
import io.getstream.android.core.internal.filter.FilterOperations
2123

2224
/**
2325
* Base interface for filters used in Stream API operations.
2426
*
2527
* Filters are used to specify criteria for querying and retrieving data from Stream services. Each
2628
* filter implementation defines specific matching logic for different comparison operations.
2729
*/
28-
@StreamPublishedApi public sealed interface Filter<F : FilterField>
30+
@StreamPublishedApi public sealed interface Filter<M, F : FilterField<M>>
2931

30-
internal data class BinaryOperationFilter<F : FilterField, V : Any>(
31-
val operator: FilterOperator,
32+
internal data class BinaryOperationFilter<M, F : FilterField<M>>(
33+
val operator: BinaryOperator,
3234
val field: F,
33-
val value: V,
34-
) : Filter<F>
35+
val value: Any,
36+
) : Filter<M, F>
3537

36-
internal data class CollectionOperationFilter<F : FilterField>(
37-
internal val operator: FilterOperator,
38-
val filters: Set<Filter<F>>,
39-
) : Filter<F>
38+
internal data class CollectionOperationFilter<M, F : FilterField<M>>(
39+
internal val operator: CollectionOperator,
40+
val filters: Set<Filter<M, F>>,
41+
) : Filter<M, F>
4042

4143
/** Converts a [Filter] instance to a request map suitable for API queries. */
4244
@StreamInternalApi
43-
public fun Filter<*>.toRequest(): Map<String, Any> =
45+
public fun Filter<*, *>.toRequest(): Map<String, Any> =
4446
when (this) {
4547
is BinaryOperationFilter<*, *> -> mapOf(field.remote to mapOf(operator.remote to value))
46-
is CollectionOperationFilter<*> ->
47-
mapOf(operator.remote to filters.map(Filter<*>::toRequest))
48+
is CollectionOperationFilter<*, *> ->
49+
mapOf(operator.remote to filters.map(Filter<*, *>::toRequest))
50+
}
51+
52+
/** Checks if this filter matches the given item. */
53+
@StreamInternalApi
54+
public infix fun <M, F : FilterField<M>> Filter<M, F>.matches(item: M): Boolean =
55+
when (this) {
56+
is BinaryOperationFilter<M, F> -> {
57+
val fieldValue = field.localValue(item)
58+
val filterValue = value
59+
val notNull = fieldValue != null
60+
61+
with(FilterOperations) {
62+
when (operator) {
63+
BinaryOperator.EQUAL -> notNull && fieldValue == filterValue
64+
BinaryOperator.GREATER -> notNull && fieldValue greater filterValue
65+
BinaryOperator.LESS -> notNull && fieldValue less filterValue
66+
BinaryOperator.GREATER_OR_EQUAL ->
67+
notNull && fieldValue greaterOrEqual filterValue
68+
BinaryOperator.LESS_OR_EQUAL -> notNull && fieldValue lessOrEqual filterValue
69+
BinaryOperator.IN -> notNull && fieldValue `in` filterValue
70+
BinaryOperator.QUERY -> notNull && search(filterValue, where = fieldValue)
71+
BinaryOperator.AUTOCOMPLETE -> notNull && fieldValue autocompletes filterValue
72+
BinaryOperator.EXISTS -> fieldValue exists filterValue
73+
BinaryOperator.CONTAINS -> notNull && fieldValue contains filterValue
74+
BinaryOperator.PATH_EXISTS -> notNull && fieldValue containsPath filterValue
75+
}
76+
}
77+
}
78+
79+
is CollectionOperationFilter<M, F> -> {
80+
when (operator) {
81+
CollectionOperator.AND -> filters.all { it.matches(item) }
82+
CollectionOperator.OR -> filters.any { it.matches(item) }
83+
}
84+
}
4885
}

stream-android-core/src/main/java/io/getstream/android/core/api/filter/FilterField.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ package io.getstream.android.core.api.filter
1919
* Interface representing a field that can be used in filters for querying data from the Stream API.
2020
*
2121
* Implementations of this interface should provide [remote], which is the name of the field as
22-
* expected by the Stream API.
22+
* expected by the Stream API, and [localValue], a function to extract the field's value from a
23+
* model instance.
2324
*/
24-
public interface FilterField {
25+
public interface FilterField<M> {
2526
/** The name of this field as expected by the Stream API. */
2627
public val remote: String
28+
29+
/** Function to extract the field's value from a model instance. */
30+
public val localValue: (M) -> Any?
2731
}

stream-android-core/src/main/java/io/getstream/android/core/api/filter/Filters.kt

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package io.getstream.android.core.api.filter
1717

18-
import io.getstream.android.core.internal.filter.FilterOperator
18+
import io.getstream.android.core.annotations.StreamPublishedApi
19+
import io.getstream.android.core.internal.filter.BinaryOperator
20+
import io.getstream.android.core.internal.filter.CollectionOperator
1921

2022
/** Utility class for building filters. */
2123
public object Filters {
@@ -25,17 +27,19 @@ public object Filters {
2527
* @param filters The filters to combine.
2628
* @return A filter that matches when all provided filters match.
2729
*/
28-
public fun <F : FilterField> and(vararg filters: Filter<F>): Filter<F> =
29-
CollectionOperationFilter(FilterOperator.AND, filters.toSet())
30+
@StreamPublishedApi
31+
public fun <M, F : FilterField<M>> and(vararg filters: Filter<M, F>): Filter<M, F> =
32+
CollectionOperationFilter(CollectionOperator.AND, filters.toSet())
3033

3134
/**
3235
* Creates a filter that combines multiple filters with a logical OR operation.
3336
*
3437
* @param filters The filters to combine.
3538
* @return A filter that matches when any of the specified filters match.
3639
*/
37-
public fun <F : FilterField> or(vararg filters: Filter<F>): Filter<F> =
38-
CollectionOperationFilter(FilterOperator.OR, filters.toSet())
40+
@StreamPublishedApi
41+
public fun <M, F : FilterField<M>> or(vararg filters: Filter<M, F>): Filter<M, F> =
42+
CollectionOperationFilter(CollectionOperator.OR, filters.toSet())
3943
}
4044

4145
/**
@@ -44,111 +48,124 @@ public object Filters {
4448
* @param value The value to check equality against.
4549
* @return A filter that matches when this field equals the specified value.
4650
*/
47-
public fun <F : FilterField> F.equal(value: Any): Filter<F> =
48-
BinaryOperationFilter(FilterOperator.EQUAL, this, value)
51+
@StreamPublishedApi
52+
public fun <M, F : FilterField<M>> F.equal(value: Any): Filter<M, F> =
53+
BinaryOperationFilter(BinaryOperator.EQUAL, this, value)
4954

5055
/**
5156
* Creates a filter that checks if this field is greater than a specific value.
5257
*
5358
* @param value The value to check against.
5459
* @return A filter that matches when this field is greater than the specified value.
5560
*/
56-
public fun <F : FilterField> F.greater(value: Any): Filter<F> =
57-
BinaryOperationFilter(FilterOperator.GREATER, this, value)
61+
@StreamPublishedApi
62+
public fun <M, F : FilterField<M>> F.greater(value: Any): Filter<M, F> =
63+
BinaryOperationFilter(BinaryOperator.GREATER, this, value)
5864

5965
/**
6066
* Creates a filter that checks if this field is greater than or equal to a specific value.
6167
*
6268
* @param value The value to check against.
6369
* @return A filter that matches when this field is greater than or equal to the specified value.
6470
*/
65-
public fun <F : FilterField> F.greaterOrEqual(value: Any): Filter<F> =
66-
BinaryOperationFilter(FilterOperator.GREATER_OR_EQUAL, this, value)
71+
@StreamPublishedApi
72+
public fun <M, F : FilterField<M>> F.greaterOrEqual(value: Any): Filter<M, F> =
73+
BinaryOperationFilter(BinaryOperator.GREATER_OR_EQUAL, this, value)
6774

6875
/**
6976
* Creates a filter that checks if this field is less than a specific value.
7077
*
7178
* @param value The value to check against.
7279
* @return A filter that matches when this field is less than the specified value.
7380
*/
74-
public fun <F : FilterField> F.less(value: Any): Filter<F> =
75-
BinaryOperationFilter(FilterOperator.LESS, this, value)
81+
@StreamPublishedApi
82+
public fun <M, F : FilterField<M>> F.less(value: Any): Filter<M, F> =
83+
BinaryOperationFilter(BinaryOperator.LESS, this, value)
7684

7785
/**
7886
* Creates a filter that checks if this field is less than or equal to a specific value.
7987
*
8088
* @param value The value to check against.
8189
* @return A filter that matches when this field is less than or equal to the specified value.
8290
*/
83-
public fun <F : FilterField> F.lessOrEqual(value: Any): Filter<F> =
84-
BinaryOperationFilter(FilterOperator.LESS_OR_EQUAL, this, value)
91+
@StreamPublishedApi
92+
public fun <M, F : FilterField<M>> F.lessOrEqual(value: Any): Filter<M, F> =
93+
BinaryOperationFilter(BinaryOperator.LESS_OR_EQUAL, this, value)
8594

8695
/**
8796
* Creates a filter that checks if this field's value is in a specific list of values.
8897
*
8998
* @param values The list of values to check against.
9099
* @return A filter that matches when this field's value is in the specified list.
91100
*/
92-
public fun <F : FilterField> F.`in`(values: List<Any>): Filter<F> =
93-
BinaryOperationFilter(FilterOperator.IN, this, values.toSet())
101+
@StreamPublishedApi
102+
public fun <M, F : FilterField<M>> F.`in`(values: List<Any>): Filter<M, F> =
103+
BinaryOperationFilter(BinaryOperator.IN, this, values.toSet())
94104

95105
/**
96106
* Creates a filter that checks if this field's value is in a specific set of values.
97107
*
98108
* @param values The values to check against.
99109
* @return A filter that matches when this field's value is in the specified values.
100110
*/
101-
public fun <F : FilterField> F.`in`(vararg values: Any): Filter<F> =
102-
BinaryOperationFilter(FilterOperator.IN, this, values.toSet())
111+
@StreamPublishedApi
112+
public fun <M, F : FilterField<M>> F.`in`(vararg values: Any): Filter<M, F> =
113+
BinaryOperationFilter(BinaryOperator.IN, this, values.toSet())
103114

104115
/**
105116
* Creates a filter that performs a full-text query on this field.
106117
*
107118
* @param value The query string to search for.
108119
* @return A filter that matches based on the full-text query.
109120
*/
110-
public fun <F : FilterField> F.query(value: String): Filter<F> =
111-
BinaryOperationFilter(FilterOperator.QUERY, this, value)
121+
@StreamPublishedApi
122+
public fun <M, F : FilterField<M>> F.query(value: String): Filter<M, F> =
123+
BinaryOperationFilter(BinaryOperator.QUERY, this, value)
112124

113125
/**
114126
* Creates a filter that performs autocomplete matching on this field.
115127
*
116128
* @param value The string to autocomplete against.
117129
* @return A filter that matches based on autocomplete functionality.
118130
*/
119-
public fun <F : FilterField> F.autocomplete(value: String): Filter<F> =
120-
BinaryOperationFilter(FilterOperator.AUTOCOMPLETE, this, value)
131+
@StreamPublishedApi
132+
public fun <M, F : FilterField<M>> F.autocomplete(value: String): Filter<M, F> =
133+
BinaryOperationFilter(BinaryOperator.AUTOCOMPLETE, this, value)
121134

122135
/**
123136
* Creates a filter that checks if this field exists.
124137
*
125138
* @return A filter that matches when this field exists.
126139
*/
127-
public fun <F : FilterField> F.exists(): Filter<F> =
128-
BinaryOperationFilter(FilterOperator.EXISTS, this, true)
140+
@StreamPublishedApi
141+
public fun <M, F : FilterField<M>> F.exists(): Filter<M, F> =
142+
BinaryOperationFilter(BinaryOperator.EXISTS, this, true)
129143

130144
/**
131145
* Creates a filter that checks if this field does not exist.
132146
*
133147
* @return A filter that matches when this field does not exist.
134148
*/
135-
public fun <F : FilterField> F.doesNotExist(): Filter<F> =
136-
BinaryOperationFilter(FilterOperator.EXISTS, this, false)
149+
@StreamPublishedApi
150+
public fun <M, F : FilterField<M>> F.doesNotExist(): Filter<M, F> =
151+
BinaryOperationFilter(BinaryOperator.EXISTS, this, false)
137152

138153
/**
139154
* Creates a filter that checks if this field contains a specific value.
140155
*
141156
* @param value The value to check for within this field.
142157
* @return A filter that matches when this field contains the specified value.
143158
*/
144-
public fun <F : FilterField> F.contains(value: Any): Filter<F> =
145-
BinaryOperationFilter(FilterOperator.CONTAINS, this, value)
159+
@StreamPublishedApi
160+
public fun <M, F : FilterField<M>> F.contains(value: Any): Filter<M, F> =
161+
BinaryOperationFilter(BinaryOperator.CONTAINS, this, value)
146162

147163
/**
148164
* Creates a filter that checks if a specific path exists within this field.
149165
*
150166
* @param value The path to check for existence.
151167
* @return A filter that matches when the specified path exists in this field.
152168
*/
153-
public fun <F : FilterField> F.pathExists(value: String): Filter<F> =
154-
BinaryOperationFilter(FilterOperator.PATH_EXISTS, this, value)
169+
@StreamPublishedApi
170+
public fun <M, F : FilterField<M>> F.pathExists(value: String): Filter<M, F> =
171+
BinaryOperationFilter(BinaryOperator.PATH_EXISTS, this, value)

stream-android-core/src/main/java/io/getstream/android/core/api/filter/Sort.kt renamed to stream-android-core/src/main/java/io/getstream/android/core/api/sort/Sort.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.getstream.android.core.api.filter
16+
package io.getstream.android.core.api.sort
1717

1818
/**
1919
* A sort configuration that combines a sort field with a direction.

stream-android-core/src/main/java/io/getstream/android/core/api/filter/SortComparator.kt renamed to stream-android-core/src/main/java/io/getstream/android/core/api/sort/SortComparator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.getstream.android.core.api.filter
16+
package io.getstream.android.core.api.sort
1717

1818
import io.getstream.android.core.annotations.StreamInternalApi
1919

stream-android-core/src/main/java/io/getstream/android/core/api/filter/SortDirection.kt renamed to stream-android-core/src/main/java/io/getstream/android/core/api/sort/SortDirection.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.getstream.android.core.api.filter
16+
package io.getstream.android.core.api.sort
1717

1818
/**
1919
* The direction of a sort operation. This enum defines whether a sort should be performed in

stream-android-core/src/main/java/io/getstream/android/core/api/filter/SortField.kt renamed to stream-android-core/src/main/java/io/getstream/android/core/api/sort/SortField.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.getstream.android.core.api.filter
16+
package io.getstream.android.core.api.sort
1717

1818
import io.getstream.android.core.annotations.StreamInternalApi
1919
import io.getstream.android.core.annotations.StreamPublishedApi

0 commit comments

Comments
 (0)