{"id":31105955,"url":"https://github.com/bright/kequality","last_synced_at":"2025-09-17T04:48:43.000Z","repository":{"id":77452221,"uuid":"285881048","full_name":"bright/kequality","owner":"bright","description":null,"archived":false,"fork":false,"pushed_at":"2024-06-25T10:47:32.000Z","size":128,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-07T02:38:16.964Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bright.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-07T17:04:33.000Z","updated_at":"2025-03-30T00:47:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"0b3ec0bc-01dd-436b-a0e4-48dc1e9b6c4e","html_url":"https://github.com/bright/kequality","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/bright/kequality","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bright%2Fkequality","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bright%2Fkequality/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bright%2Fkequality/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bright%2Fkequality/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bright","download_url":"https://codeload.github.com/bright/kequality/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bright%2Fkequality/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275537149,"owners_count":25482345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-17T04:48:41.150Z","updated_at":"2025-09-17T04:48:42.993Z","avatar_url":"https://github.com/bright.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"![build](https://github.com/bright/kequality/actions/workflows/build.yml/badge.svg?branch=master)\n\n# kequality #\n\nCustom equality checking utility.\n\nIt allows you to:\n\n- Extract the custom equality checks to make them reusable\n- Use different implementations of the equality check depending on the\n  business context\n- Write declarative and easily readable equality checking logic\n- Simplify Android's RecyclerView DiffUtil usage\n\nSee [Usage examples](#usage-examples) below.\n\n## Installation ##\n\nFirst, make sure you have Maven Central in your repositories:\n\n```gradle\nrepositories {\n    mavenCentral()\n}\n```\n\nThen, declare the dependency like:\n```gradle\n// Base kequality features\nimplementation(\"dev.bright.kequality:kequality:1.5.0\")\n\n// Android's RecyclerView DiffUtil integration\nimplementation(\"dev.bright.kequality:diffutil:1.5.0\")\n```\n\n## Usage examples ##\n\n### Custom equality check logic ###\n\n```kotlin\ndata class Address(\n    val city: String\n)\n\nval AddressIgnoreCaseEquality = object : Equality\u003cAddress\u003e {\n    override fun areEqual(o1: Address, o2: Address): Boolean {\n        return o1.city.equals(o2.city, ignoreCase = true)\n    }\n}\n```\n\n### Complex declarative equality check ###\n\n```kotlin\ndata class Person(\n    val name: String,\n    val age: BigDecimal,\n    val address: Address\n)\n\nval PersonEquality = CompositeEquality(\n    Person::name.equalsEquality,    // uses Any.equals()\n    Person::age.comparableEquality, // uses Comparable.compareTo()\n    Person::address.equalityBy(AddressIgnoreCaseEquality) // see above\n)\n```\n\nor\n\n```kotlin\nval PersonEquality = Equality\u003cPerson\u003e {\n    by { name }                         // uses Any.equals()\n    by(ComparableEquality()) { age }    // uses Comparable.compareTo()\n    by(AddressIgnoreCaseEquality) { address } // see above\n}\n```\n\n### Check if two lists contain equal objects ignoring their order ###\n\nRegular `equals` comparison of two lists containing equal objects ordered differently returns `false`.\nUsing `ListEquality` you can specify if the order has to be the same to consider the lists equal.\n\n```kotlin\ndata class Person(val name: String)\n\nval people1 = listOf(Person(\"Alice\"), Person(\"Bob\"))\nval people2 = listOf(Person(\"Bob\"), Person(\"Alice\"))\n\npeople1 == people2 // returns false\n\nval regularPersonEquality = EqualsEquality\u003cPerson\u003e()\nval peopleListEquality = ListEquality(regularPersonEquality, ignoreOrder = true)\npeopleListEquality.areEqual(people1, people2) // returns true\n```\n\n### Check if objects are almost the same (except a single property) ###\n\nYou can easily find out if objects are **almost** the same by excluding a single property from the equality check.\nAll the other properties can be compared using the regular `equals` method.\n\n```kotlin\ndata class Person(val name: String, val age: Int, val height: Int)\n\nval person1 = Person(\"Alice\", 20, 175)\nval person2 = Person(\"Bob\", 20, 175)\n\nval personEquality = CompositeEquality(\n    Person::class.declaredMemberProperties\n        .filterNot { it == Person::name }\n        .map { it.equalsEquality }\n)\n\n\npersonEquality.areEqual(person1, person2) // returns true\n```\n\n### Simplify Android RecyclerView DiffUtil usage ###\n\nNormally, when you use `ListAdapter`, you prepare a class implementing\n`DiffUtil.ItemCallback\u003cT\u003e`, for example:\n\n```kotlin\ndata class Person(\n    val id: Long,\n    val name: String,\n    val age: BigDecimal\n)\n```\n\n```kotlin\nclass PersonDiffUtilItemCallback : DiffUtil.ItemCallback\u003cPerson\u003e() {\n\n    override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {\n        return oldItem.id == newItem.id\n    }\n\n    override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {\n        return oldItem.name == newItem.name \u0026\u0026 oldItem.age.compareTo(newItem.age) == 0\n    }\n}\n```\n\nThe logic inside `areItemsTheSame` and `areContentsTheSame` is hardly\nreusable and readable, especially when your class has a lot of\nproperties to compare.\n\nMoreover, if you want to use `DiffUtil.calculateDiff` to manually\ncalculate the diff between two collections, you must write yet\nanother class extending `DiffUtil.Callback` like this:\n\n```kotlin\nclass PersonDiffUtilCallback(val oldItems: List\u003cPerson\u003e, val newItems: List\u003cPerson\u003e) : DiffUtil.Callback() {\n    \n    private val itemCallback = PersonDiffUtilItemCallback()\n    \n    override fun getOldListSize(): Int = oldItems.size\n\n    override fun getNewListSize(): Int = newItems.size\n\n    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n        return itemCallback.areItemsTheSame(oldItems[oldItemPosition], newItems[newItemPosition])\n    }\n\n    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n        return itemCallback.areContentsTheSame(oldItems[oldItemPosition], newItems[newItemPosition])\n    }\n}\n```\n\nand use it this way:\n\n```kotlin\nDiffUtil.calculateDiff(PersonDiffUtilCallback(items1, items2))\n```\n\nWith kequality, you can easily convert any `Equality\u003cT\u003e` into\n`DiffUtil.ItemCallback\u003cT\u003e` or `DiffUtil.Callback`\n\n#### Convert `Equality\u003cT\u003e` into `DiffUtil.ItemCallback\u003cT\u003e` or `DiffUtil.Callback` ####\n\n```kotlin\ndata class Person(\n    val id: Long,\n    val name: String,\n    val age: BigDecimal\n)\n\nval PersonIdEquality = Equality\u003cPerson\u003e {\n    by { id }\n}\n\nval PersonContentEquality = Equality\u003cPerson\u003e {\n    by { name }\n    by(ComparableEquality()) { age }\n}\n\nval PersonDiffUtilItemCallback: DiffUtil.ItemCallback\u003cPerson\u003e =\n    DiffUtilDelegatingItemCallback(\n        diffUtilIdentityCheck = PersonIdEquality.diffUtilIdentityCheck(),\n        diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()\n    )\n\nfun PersonDiffUtilCallback(oldItems: List\u003cPerson\u003e, newItems: List\u003cPerson\u003e): DiffUtil.Callback =\n    DiffUtilDelegatingCallback(\n        oldItems = oldItems,\n        newItems = newItems,\n        diffUtilDelegatingItemCallback = PersonDiffUtilItemCallback // or pass another diffUtilIdentityCheck and diffUtilContentCheck\n    )\n```\n\n#### Use `HasDiffCallbackId` to make it even shorter ####\n\nIf you let your class implement `HasDiffCallbackId` interface like this:\n\n```kotlin\ndata class Person(\n    val id: Long,\n    val name: String,\n    val age: BigDecimal\n) : HasDiffCallbackId {\n    override val diffCallbackId: Any\n        get() = id\n}\n```\n\nthen the previous example can be further shortened to:\n\n```kotlin\nval PersonContentEquality = Equality\u003cPerson\u003e {\n    by { name }\n    by(ComparableEquality()) { age }\n}\n\nval PersonDiffUtilItemCallback: DiffUtil.ItemCallback\u003cPerson\u003e =\n    DiffItemCallbackById(\n        diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()\n    )\n\nfun PersonDiffUtilCallback(oldItems: List\u003cPerson\u003e, newItems: List\u003cPerson\u003e): DiffUtil.Callback =\n    DiffCallbackById(\n        oldItems = oldItems,\n        newItems = newItems,\n        diffUtilContentCheck = PersonContentEquality.diffUtilContentCheck()\n    )\n```\n\n#### Even shorter usage for simpler classes ####\n\nThe examples above were based on the `Person` class that required\ncustomized equality check because `age` was `BigDecimal` so calling\n`equals()` was not good enough (because e.g. `BigDecimal(\"10.0\")` is not\nequal to `BigDecimal(\"10\")`)\n\nIf your class doesn't need custom equality check and you can rely on\n`equals()`, e.g.\n\n```kotlin\ndata class Person(\n    val id: Long,\n    val name: String\n) : HasDiffCallbackId {\n    override val diffCallbackId: Any\n        get() = id\n}\n```\n\nthen creating the `DiffUtil` implementations based on\n`EqualsEquality\u003cT\u003e` is even simpler:\n\n```kotlin\nval PersonDiffUtilItemCallback: DiffUtil.ItemCallback\u003cPerson\u003e = DiffItemCallbackById()\n\nfun PersonDiffUtilCallback(oldItems: List\u003cPerson\u003e, newItems: List\u003cPerson\u003e): DiffUtil.Callback =\n    DiffCallbackById(\n        oldItems = oldItems,\n        newItems = newItems\n    )\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbright%2Fkequality","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbright%2Fkequality","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbright%2Fkequality/lists"}