An open API service indexing awesome lists of open source software.

https://github.com/bright/kequality


https://github.com/bright/kequality

Last synced: 9 months ago
JSON representation

Awesome Lists containing this project

README

          

![build](https://github.com/bright/kequality/actions/workflows/build.yml/badge.svg?branch=master)

# kequality #

Custom equality checking utility.

It allows you to:

- Extract the custom equality checks to make them reusable
- Use different implementations of the equality check depending on the
business context
- Write declarative and easily readable equality checking logic
- Simplify Android's RecyclerView DiffUtil usage

See [Usage examples](#usage-examples) below.

## Installation ##

First, make sure you have Maven Central in your repositories:

```gradle
repositories {
mavenCentral()
}
```

Then, declare the dependency like:
```gradle
// Base kequality features
implementation("dev.bright.kequality:kequality:1.5.0")

// Android's RecyclerView DiffUtil integration
implementation("dev.bright.kequality:diffutil:1.5.0")
```

## Usage examples ##

### Custom equality check logic ###

```kotlin
data class Address(
val city: String
)

val AddressIgnoreCaseEquality = object : Equality

{
override fun areEqual(o1: Address, o2: Address): Boolean {
return o1.city.equals(o2.city, ignoreCase = true)
}
}
```

### Complex declarative equality check ###

```kotlin
data class Person(
val name: String,
val age: BigDecimal,
val address: Address
)

val PersonEquality = CompositeEquality(
Person::name.equalsEquality, // uses Any.equals()
Person::age.comparableEquality, // uses Comparable.compareTo()
Person::address.equalityBy(AddressIgnoreCaseEquality) // see above
)
```

or

```kotlin
val PersonEquality = Equality {
by { name } // uses Any.equals()
by(ComparableEquality()) { age } // uses Comparable.compareTo()
by(AddressIgnoreCaseEquality) { address } // see above
}
```

### Check if two lists contain equal objects ignoring their order ###

Regular `equals` comparison of two lists containing equal objects ordered differently returns `false`.
Using `ListEquality` you can specify if the order has to be the same to consider the lists equal.

```kotlin
data class Person(val name: String)

val people1 = listOf(Person("Alice"), Person("Bob"))
val people2 = listOf(Person("Bob"), Person("Alice"))

people1 == people2 // returns false

val regularPersonEquality = EqualsEquality()
val peopleListEquality = ListEquality(regularPersonEquality, ignoreOrder = true)
peopleListEquality.areEqual(people1, people2) // returns true
```

### Check if objects are almost the same (except a single property) ###

You can easily find out if objects are **almost** the same by excluding a single property from the equality check.
All the other properties can be compared using the regular `equals` method.

```kotlin
data class Person(val name: String, val age: Int, val height: Int)

val person1 = Person("Alice", 20, 175)
val person2 = Person("Bob", 20, 175)

val personEquality = CompositeEquality(
Person::class.declaredMemberProperties
.filterNot { it == Person::name }
.map { it.equalsEquality }
)

personEquality.areEqual(person1, person2) // returns true
```

### Simplify Android RecyclerView DiffUtil usage ###

Normally, when you use `ListAdapter`, you prepare a class implementing
`DiffUtil.ItemCallback`, for example:

```kotlin
data class Person(
val id: Long,
val name: String,
val age: BigDecimal
)
```

```kotlin
class PersonDiffUtilItemCallback : DiffUtil.ItemCallback() {

override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.name == newItem.name && oldItem.age.compareTo(newItem.age) == 0
}
}
```

The logic inside `areItemsTheSame` and `areContentsTheSame` is hardly
reusable and readable, especially when your class has a lot of
properties to compare.

Moreover, if you want to use `DiffUtil.calculateDiff` to manually
calculate the diff between two collections, you must write yet
another class extending `DiffUtil.Callback` like this:

```kotlin
class PersonDiffUtilCallback(val oldItems: List, val newItems: List) : DiffUtil.Callback() {

private val itemCallback = PersonDiffUtilItemCallback()

override fun getOldListSize(): Int = oldItems.size

override fun getNewListSize(): Int = newItems.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return itemCallback.areItemsTheSame(oldItems[oldItemPosition], newItems[newItemPosition])
}

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return itemCallback.areContentsTheSame(oldItems[oldItemPosition], newItems[newItemPosition])
}
}
```

and use it this way:

```kotlin
DiffUtil.calculateDiff(PersonDiffUtilCallback(items1, items2))
```

With kequality, you can easily convert any `Equality` into
`DiffUtil.ItemCallback` or `DiffUtil.Callback`

#### Convert `Equality` into `DiffUtil.ItemCallback` or `DiffUtil.Callback` ####

```kotlin
data class Person(
val id: Long,
val name: String,
val age: BigDecimal
)

val PersonIdEquality = Equality {
by { id }
}

val PersonContentEquality = Equality {
by { name }
by(ComparableEquality()) { age }
}

val PersonDiffUtilItemCallback: DiffUtil.ItemCallback =
DiffUtilDelegatingItemCallback(
diffUtilIdentityCheck = PersonIdEquality.diffUtilIdentityCheck(),
diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()
)

fun PersonDiffUtilCallback(oldItems: List, newItems: List): DiffUtil.Callback =
DiffUtilDelegatingCallback(
oldItems = oldItems,
newItems = newItems,
diffUtilDelegatingItemCallback = PersonDiffUtilItemCallback // or pass another diffUtilIdentityCheck and diffUtilContentCheck
)
```

#### Use `HasDiffCallbackId` to make it even shorter ####

If you let your class implement `HasDiffCallbackId` interface like this:

```kotlin
data class Person(
val id: Long,
val name: String,
val age: BigDecimal
) : HasDiffCallbackId {
override val diffCallbackId: Any
get() = id
}
```

then the previous example can be further shortened to:

```kotlin
val PersonContentEquality = Equality {
by { name }
by(ComparableEquality()) { age }
}

val PersonDiffUtilItemCallback: DiffUtil.ItemCallback =
DiffItemCallbackById(
diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()
)

fun PersonDiffUtilCallback(oldItems: List, newItems: List): DiffUtil.Callback =
DiffCallbackById(
oldItems = oldItems,
newItems = newItems,
diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()
)
```

#### Even shorter usage for simpler classes ####

The examples above were based on the `Person` class that required
customized equality check because `age` was `BigDecimal` so calling
`equals()` was not good enough (because e.g. `BigDecimal("10.0")` is not
equal to `BigDecimal("10")`)

If your class doesn't need custom equality check and you can rely on
`equals()`, e.g.

```kotlin
data class Person(
val id: Long,
val name: String
) : HasDiffCallbackId {
override val diffCallbackId: Any
get() = id
}
```

then creating the `DiffUtil` implementations based on
`EqualsEquality` is even simpler:

```kotlin
val PersonDiffUtilItemCallback: DiffUtil.ItemCallback = DiffItemCallbackById()

fun PersonDiffUtilCallback(oldItems: List, newItems: List): DiffUtil.Callback =
DiffCallbackById(
oldItems = oldItems,
newItems = newItems
)
```