expo-contacts 56.0.7 → 56.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/android/build.gradle +9 -2
  3. package/android/src/main/java/expo/modules/contacts/next/domain/ContactRepository.kt +6 -4
  4. package/android/src/main/java/expo/modules/contacts/next/domain/model/email/EmailField.kt +12 -5
  5. package/android/src/main/java/expo/modules/contacts/next/domain/model/email/EmailLabel.kt +6 -1
  6. package/android/src/main/java/expo/modules/contacts/next/domain/model/event/EventField.kt +12 -5
  7. package/android/src/main/java/expo/modules/contacts/next/domain/model/event/EventLabel.kt +6 -1
  8. package/android/src/main/java/expo/modules/contacts/next/domain/model/headers/DisplayNameField.kt +2 -3
  9. package/android/src/main/java/expo/modules/contacts/next/domain/model/headers/PhotoThumbnailUriField.kt +2 -1
  10. package/android/src/main/java/expo/modules/contacts/next/domain/model/headers/PhotoUriField.kt +2 -3
  11. package/android/src/main/java/expo/modules/contacts/next/domain/model/nickname/NicknameField.kt +12 -5
  12. package/android/src/main/java/expo/modules/contacts/next/domain/model/nickname/NicknameLabel.kt +6 -1
  13. package/android/src/main/java/expo/modules/contacts/next/domain/model/note/NoteField.kt +4 -2
  14. package/android/src/main/java/expo/modules/contacts/next/domain/model/organization/OrganizationField.kt +7 -5
  15. package/android/src/main/java/expo/modules/contacts/next/domain/model/phone/PhoneField.kt +12 -5
  16. package/android/src/main/java/expo/modules/contacts/next/domain/model/phone/PhoneLabel.kt +6 -1
  17. package/android/src/main/java/expo/modules/contacts/next/domain/model/photo/PhotoField.kt +2 -1
  18. package/android/src/main/java/expo/modules/contacts/next/domain/model/relationship/RelationField.kt +12 -5
  19. package/android/src/main/java/expo/modules/contacts/next/domain/model/relationship/RelationLabel.kt +6 -1
  20. package/android/src/main/java/expo/modules/contacts/next/domain/model/structuredname/StructuredNameField.kt +11 -9
  21. package/android/src/main/java/expo/modules/contacts/next/domain/model/structuredpostal/StructuredPostalField.kt +16 -9
  22. package/android/src/main/java/expo/modules/contacts/next/domain/model/structuredpostal/StructuredPostalLabel.kt +6 -1
  23. package/android/src/main/java/expo/modules/contacts/next/domain/model/website/WebsiteField.kt +12 -5
  24. package/android/src/main/java/expo/modules/contacts/next/domain/model/website/WebsiteLabel.kt +6 -1
  25. package/android/src/main/java/expo/modules/contacts/next/domain/query/QueryAggregator.kt +4 -3
  26. package/android/src/main/java/expo/modules/contacts/next/extensions/ContentResolverExtensions.kt +1 -1
  27. package/android/src/main/java/expo/modules/contacts/next/extensions/CursorExtensions.kt +13 -0
  28. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/EmailLabelMapper.kt +2 -0
  29. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/EventLabelMapper.kt +2 -0
  30. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/NicknameLabelMapper.kt +2 -0
  31. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/PhoneLabelMapper.kt +2 -0
  32. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/RelationshipLabelMapper.kt +2 -0
  33. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/StructuredPostalLabelMapper.kt +2 -0
  34. package/android/src/main/java/expo/modules/contacts/next/mappers/domain/data/list/label/WebsiteLabelMapper.kt +2 -0
  35. package/expo-module.config.json +1 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/{56.0.7/expo.modules.contacts-56.0.7-sources.jar → 56.0.8/expo.modules.contacts-56.0.8-sources.jar} +0 -0
  37. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8-sources.jar.md5 +1 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8-sources.jar.sha1 +1 -0
  39. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8-sources.jar.sha256 +1 -0
  40. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8-sources.jar.sha512 +1 -0
  41. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.aar +0 -0
  42. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.aar.md5 +1 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.aar.sha1 +1 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.aar.sha256 +1 -0
  45. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.aar.sha512 +1 -0
  46. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/{56.0.7/expo.modules.contacts-56.0.7.module → 56.0.8/expo.modules.contacts-56.0.8.module} +22 -22
  47. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.module.md5 +1 -0
  48. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.module.sha1 +1 -0
  49. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.module.sha256 +1 -0
  50. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.module.sha512 +1 -0
  51. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/{56.0.7/expo.modules.contacts-56.0.7.pom → 56.0.8/expo.modules.contacts-56.0.8.pom} +1 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.pom.md5 +1 -0
  53. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.pom.sha1 +1 -0
  54. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.pom.sha256 +1 -0
  55. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.8/expo.modules.contacts-56.0.8.pom.sha512 +1 -0
  56. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/maven-metadata.xml +4 -4
  57. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/maven-metadata.xml.md5 +1 -1
  58. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/maven-metadata.xml.sha1 +1 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/maven-metadata.xml.sha256 +1 -1
  60. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/maven-metadata.xml.sha512 +1 -1
  61. package/package.json +4 -4
  62. package/prebuilds/output/debug/xcframeworks/ExpoContacts.tar.gz +0 -0
  63. package/prebuilds/output/release/xcframeworks/ExpoContacts.tar.gz +0 -0
  64. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7-sources.jar.md5 +0 -1
  65. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7-sources.jar.sha1 +0 -1
  66. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7-sources.jar.sha256 +0 -1
  67. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7-sources.jar.sha512 +0 -1
  68. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.aar +0 -0
  69. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.aar.md5 +0 -1
  70. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.aar.sha1 +0 -1
  71. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.aar.sha256 +0 -1
  72. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.aar.sha512 +0 -1
  73. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.module.md5 +0 -1
  74. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.module.sha1 +0 -1
  75. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.module.sha256 +0 -1
  76. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.module.sha512 +0 -1
  77. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.pom.md5 +0 -1
  78. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.pom.sha1 +0 -1
  79. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.pom.sha256 +0 -1
  80. package/local-maven-repo/host/exp/exponent/expo.modules.contacts/56.0.7/expo.modules.contacts-56.0.7.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.8 — 2026-06-05
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fix `getDetails` throwing NPE on malformed label ([#46405](https://github.com/expo/expo/pull/46405) by [@Wenszel](https://github.com/Wenszel))
18
+
13
19
  ## 56.0.7 — 2026-05-21
14
20
 
15
21
  ### 🐛 Bug fixes
@@ -4,12 +4,19 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '56.0.7'
7
+ version = '56.0.8'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.contacts"
11
11
  defaultConfig {
12
12
  versionCode 29
13
- versionName "56.0.7"
13
+ versionName "56.0.8"
14
14
  }
15
15
  }
16
+
17
+ dependencies {
18
+ testImplementation 'junit:junit:4.13.2'
19
+ testImplementation 'io.mockk:mockk:1.13.5'
20
+ testImplementation "org.robolectric:robolectric:4.16"
21
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
22
+ }
@@ -21,6 +21,8 @@ import expo.modules.contacts.next.domain.wrappers.RawContactId
21
21
  import expo.modules.contacts.next.extensions.asSequence
22
22
  import expo.modules.contacts.next.extensions.extractId
23
23
  import expo.modules.contacts.next.extensions.getContactIdFromRawContactId
24
+ import expo.modules.contacts.next.extensions.getNullableString
25
+ import expo.modules.contacts.next.extensions.getRequiredString
24
26
  import expo.modules.contacts.next.extensions.queryOne
25
27
  import expo.modules.contacts.next.extensions.safeApplyBatch
26
28
  import expo.modules.contacts.next.extensions.safeDelete
@@ -75,7 +77,7 @@ class ContactRepository(val contentResolver: ContentResolver) {
75
77
  ).use { cursor ->
76
78
  ensureActive()
77
79
  cursor.asSequence()
78
- .map { it.getString(0) }
80
+ .map { it.getRequiredString(it.getColumnIndexOrThrow(ContactsContract.Data._ID)) }
79
81
  .map { DataId(it) }
80
82
  .toList()
81
83
  }
@@ -146,7 +148,7 @@ class ContactRepository(val contentResolver: ContentResolver) {
146
148
  cursor
147
149
  .asSequence()
148
150
  .take(limit ?: Int.MAX_VALUE)
149
- .map { it.getString(it.getColumnIndexOrThrow(ContactId.COLUMN_IN_CONTACTS_TABLE)) }
151
+ .map { it.getRequiredString(it.getColumnIndexOrThrow(ContactId.COLUMN_IN_CONTACTS_TABLE)) }
150
152
  .map { ContactId(it) }
151
153
  .toList()
152
154
  }
@@ -272,7 +274,7 @@ class ContactRepository(val contentResolver: ContentResolver) {
272
274
  contentResolver.queryOne(
273
275
  uri = ContactsContract.Contacts.CONTENT_URI,
274
276
  column = ContactsContract.Contacts.LOOKUP_KEY,
275
- extractor = Cursor::getString,
277
+ extractor = Cursor::getNullableString,
276
278
  selection = "${ContactId.COLUMN_IN_CONTACTS_TABLE} = ?",
277
279
  selectionArgs = arrayOf(contactId.value)
278
280
  )
@@ -286,7 +288,7 @@ class ContactRepository(val contentResolver: ContentResolver) {
286
288
  contentResolver.queryOne(
287
289
  uri = ContactsContract.RawContacts.CONTENT_URI,
288
290
  column = RawContactId.COLUMN_IN_RAW_CONTACTS_TABLE,
289
- extractor = Cursor::getString,
291
+ extractor = Cursor::getRequiredString,
290
292
  selection = selectionBuilder.toString(),
291
293
  selectionArgs = arrayOf(contactId.value)
292
294
  )?.let {
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.Email
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.email.operations.ExistingEmail
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object EmailField : ExtractableField.Data<ExistingEmail> {
10
13
  override val mimeType = Email.CONTENT_ITEM_TYPE
@@ -18,21 +21,25 @@ object EmailField : ExtractableField.Data<ExistingEmail> {
18
21
 
19
22
  override fun extract(cursor: Cursor): ExistingEmail = with(cursor) {
20
23
  return ExistingEmail(
21
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
22
- address = getString(getColumnIndexOrThrow(Email.ADDRESS)),
24
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
25
+ address = getNullableString(getColumnIndexOrThrow(Email.ADDRESS)),
23
26
  label = extractLabel()
24
27
  )
25
28
  }
26
29
 
27
30
  private fun Cursor.extractLabel(): EmailLabel =
28
- when (getInt(getColumnIndexOrThrow(Email.TYPE))) {
31
+ when (getNullableInt(Email.TYPE)) {
29
32
  Email.TYPE_HOME -> EmailLabel.Home
30
33
  Email.TYPE_WORK -> EmailLabel.Work
31
34
  Email.TYPE_OTHER -> EmailLabel.Other
32
35
  Email.TYPE_MOBILE -> EmailLabel.Mobile
36
+ null -> {
37
+ val customLabel = getNullableString(getColumnIndexOrThrow(Email.LABEL))
38
+ EmailLabel.MalformedType(customLabel)
39
+ }
33
40
  else -> {
34
- val customLabel = getString(getColumnIndexOrThrow(Email.LABEL))
35
- EmailLabel.Custom(customLabel ?: "")
41
+ val customLabel = getNullableString(getColumnIndexOrThrow(Email.LABEL))
42
+ customLabel?.let { EmailLabel.Custom(it) } ?: EmailLabel.MalformedCustom
36
43
  }
37
44
  }
38
45
  }
@@ -2,10 +2,15 @@ package expo.modules.contacts.next.domain.model.email
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Email
4
4
 
5
- sealed class EmailLabel(val type: Int, val label: String? = null) {
5
+ sealed class EmailLabel(val type: Int?, val label: String? = null) {
6
6
  object Home : EmailLabel(Email.TYPE_HOME)
7
7
  object Work : EmailLabel(Email.TYPE_WORK)
8
8
  object Other : EmailLabel(Email.TYPE_OTHER)
9
9
  object Mobile : EmailLabel(Email.TYPE_MOBILE)
10
10
  class Custom(label: String) : EmailLabel(Email.TYPE_CUSTOM, label)
11
+
12
+ // Ideally these states would not be necessary, but Android does not enforce
13
+ // type and label columns as non-null, so malformed label data can exist there.
14
+ class MalformedType(label: String?) : EmailLabel(null, label)
15
+ object MalformedCustom : EmailLabel(Email.TYPE_CUSTOM)
11
16
  }
@@ -6,6 +6,9 @@ import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.event.operations.ExistingEvent
7
7
  import expo.modules.contacts.next.domain.wrappers.ContactDate
8
8
  import expo.modules.contacts.next.domain.wrappers.DataId
9
+ import expo.modules.contacts.next.extensions.getNullableInt
10
+ import expo.modules.contacts.next.extensions.getNullableString
11
+ import expo.modules.contacts.next.extensions.getRequiredString
9
12
 
10
13
  object EventField : ExtractableField.Data<ExistingEvent> {
11
14
  override val mimeType = Event.CONTENT_ITEM_TYPE
@@ -18,23 +21,27 @@ object EventField : ExtractableField.Data<ExistingEvent> {
18
21
  )
19
22
 
20
23
  override fun extract(cursor: Cursor): ExistingEvent = with(cursor) {
21
- val dateString = getString(getColumnIndexOrThrow(Event.START_DATE))
24
+ val dateString = getNullableString(getColumnIndexOrThrow(Event.START_DATE))
22
25
 
23
26
  return ExistingEvent(
24
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
27
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
25
28
  startDate = if (dateString != null) ContactDate(dateString) else null,
26
29
  label = extractLabel()
27
30
  )
28
31
  }
29
32
 
30
33
  private fun Cursor.extractLabel(): EventLabel =
31
- when (getInt(getColumnIndexOrThrow(Event.TYPE))) {
34
+ when (getNullableInt(Event.TYPE)) {
32
35
  Event.TYPE_ANNIVERSARY -> EventLabel.Anniversary
33
36
  Event.TYPE_BIRTHDAY -> EventLabel.Birthday
34
37
  Event.TYPE_OTHER -> EventLabel.Other
38
+ null -> {
39
+ val customLabel = getNullableString(getColumnIndexOrThrow(Event.LABEL))
40
+ EventLabel.MalformedType(customLabel)
41
+ }
35
42
  else -> {
36
- val customLabel = getString(getColumnIndexOrThrow(Event.LABEL))
37
- EventLabel.Custom(customLabel)
43
+ val customLabel = getNullableString(getColumnIndexOrThrow(Event.LABEL))
44
+ customLabel?.let { EventLabel.Custom(it) } ?: EventLabel.MalformedCustom
38
45
  }
39
46
  }
40
47
  }
@@ -2,9 +2,14 @@ package expo.modules.contacts.next.domain.model.event
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Event
4
4
 
5
- sealed class EventLabel(val type: Int, val label: String? = null) {
5
+ sealed class EventLabel(val type: Int?, val label: String? = null) {
6
6
  object Anniversary : EventLabel(Event.TYPE_ANNIVERSARY)
7
7
  object Birthday : EventLabel(Event.TYPE_BIRTHDAY)
8
8
  object Other : EventLabel(Event.TYPE_OTHER)
9
9
  class Custom(label: String) : EventLabel(Event.TYPE_CUSTOM, label)
10
+
11
+ // Ideally these states would not be necessary, but Android does not enforce
12
+ // type and label columns as non-null, so malformed label data can exist there.
13
+ class MalformedType(label: String?) : EventLabel(null, label)
14
+ object MalformedCustom : EventLabel(Event.TYPE_CUSTOM)
10
15
  }
@@ -4,15 +4,14 @@ import android.database.Cursor
4
4
  import android.provider.ContactsContract.Contacts
5
5
  import expo.modules.contacts.next.domain.model.Extractable
6
6
  import expo.modules.contacts.next.domain.model.ExtractableField
7
+ import expo.modules.contacts.next.extensions.getNullableString
7
8
 
8
9
  object DisplayNameField : ExtractableField.Contacts<DisplayName> {
9
10
  override val projection = arrayOf(Contacts.DISPLAY_NAME)
10
11
 
11
12
  override fun extract(cursor: Cursor) =
12
13
  DisplayName(
13
- cursor.getString(
14
- cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)
15
- )
14
+ cursor.getNullableString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME))
16
15
  )
17
16
  }
18
17
 
@@ -4,13 +4,14 @@ import android.database.Cursor
4
4
  import android.provider.ContactsContract.Contacts
5
5
  import expo.modules.contacts.next.domain.model.Extractable
6
6
  import expo.modules.contacts.next.domain.model.ExtractableField
7
+ import expo.modules.contacts.next.extensions.getNullableString
7
8
 
8
9
  object PhotoThumbnailUriField : ExtractableField.Contacts<PhotoThumbnailUri> {
9
10
  override val projection = arrayOf(Contacts.PHOTO_THUMBNAIL_URI)
10
11
 
11
12
  override fun extract(cursor: Cursor) =
12
13
  PhotoThumbnailUri(
13
- cursor.getString(cursor.getColumnIndexOrThrow(Contacts.PHOTO_THUMBNAIL_URI))
14
+ cursor.getNullableString(cursor.getColumnIndexOrThrow(Contacts.PHOTO_THUMBNAIL_URI))
14
15
  )
15
16
  }
16
17
 
@@ -4,15 +4,14 @@ import android.database.Cursor
4
4
  import android.provider.ContactsContract.Contacts
5
5
  import expo.modules.contacts.next.domain.model.Extractable
6
6
  import expo.modules.contacts.next.domain.model.ExtractableField
7
+ import expo.modules.contacts.next.extensions.getNullableString
7
8
 
8
9
  object PhotoUriField : ExtractableField.Contacts<PhotoUri> {
9
10
  override val projection = arrayOf(Contacts.PHOTO_URI)
10
11
 
11
12
  override fun extract(cursor: Cursor) =
12
13
  PhotoUri(
13
- cursor.getString(
14
- cursor.getColumnIndexOrThrow(Contacts.PHOTO_URI)
15
- )
14
+ cursor.getNullableString(cursor.getColumnIndexOrThrow(Contacts.PHOTO_URI))
16
15
  )
17
16
  }
18
17
 
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.Nickname
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.nickname.operations.ExistingNickname
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object NicknameField : ExtractableField.Data<ExistingNickname> {
10
13
  override val mimeType = Nickname.CONTENT_ITEM_TYPE
@@ -18,22 +21,26 @@ object NicknameField : ExtractableField.Data<ExistingNickname> {
18
21
 
19
22
  override fun extract(cursor: Cursor): ExistingNickname = with(cursor) {
20
23
  return ExistingNickname(
21
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
22
- name = getString(getColumnIndexOrThrow(Nickname.NAME)),
24
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
25
+ name = getNullableString(getColumnIndexOrThrow(Nickname.NAME)),
23
26
  label = extractLabel()
24
27
  )
25
28
  }
26
29
 
27
30
  private fun Cursor.extractLabel(): NicknameLabel =
28
- when (getInt(getColumnIndexOrThrow(Nickname.TYPE))) {
31
+ when (getNullableInt(Nickname.TYPE)) {
29
32
  Nickname.TYPE_DEFAULT -> NicknameLabel.Default
30
33
  Nickname.TYPE_OTHER_NAME -> NicknameLabel.OtherName
31
34
  Nickname.TYPE_MAIDEN_NAME -> NicknameLabel.MaidenName
32
35
  Nickname.TYPE_SHORT_NAME -> NicknameLabel.ShortName
33
36
  Nickname.TYPE_INITIALS -> NicknameLabel.Initials
37
+ null -> {
38
+ val customLabel = getNullableString(getColumnIndexOrThrow(Nickname.LABEL))
39
+ NicknameLabel.MalformedType(customLabel)
40
+ }
34
41
  else -> {
35
- val customLabel = getString(getColumnIndexOrThrow(Nickname.LABEL))
36
- NicknameLabel.Custom(customLabel)
42
+ val customLabel = getNullableString(getColumnIndexOrThrow(Nickname.LABEL))
43
+ customLabel?.let { NicknameLabel.Custom(it) } ?: NicknameLabel.MalformedCustom
37
44
  }
38
45
  }
39
46
  }
@@ -2,11 +2,16 @@ package expo.modules.contacts.next.domain.model.nickname
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Nickname
4
4
 
5
- sealed class NicknameLabel(val type: Int, val label: String? = null) {
5
+ sealed class NicknameLabel(val type: Int?, val label: String? = null) {
6
6
  object Default : NicknameLabel(Nickname.TYPE_DEFAULT)
7
7
  object OtherName : NicknameLabel(Nickname.TYPE_OTHER_NAME)
8
8
  object MaidenName : NicknameLabel(Nickname.TYPE_MAIDEN_NAME)
9
9
  object ShortName : NicknameLabel(Nickname.TYPE_SHORT_NAME)
10
10
  object Initials : NicknameLabel(Nickname.TYPE_INITIALS)
11
11
  class Custom(label: String) : NicknameLabel(Nickname.TYPE_CUSTOM, label)
12
+
13
+ // Ideally these states would not be necessary, but Android does not enforce
14
+ // type and label columns as non-null, so malformed label data can exist there.
15
+ class MalformedType(label: String?) : NicknameLabel(null, label)
16
+ object MalformedCustom : NicknameLabel(Nickname.TYPE_CUSTOM)
12
17
  }
@@ -5,6 +5,8 @@ import android.provider.ContactsContract.CommonDataKinds.Note
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.note.operations.ExistingNote
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableString
9
+ import expo.modules.contacts.next.extensions.getRequiredString
8
10
 
9
11
  object NoteField : ExtractableField.Data<ExistingNote> {
10
12
  override val projection = arrayOf(
@@ -16,7 +18,7 @@ object NoteField : ExtractableField.Data<ExistingNote> {
16
18
 
17
19
  override fun extract(cursor: Cursor) =
18
20
  ExistingNote(
19
- dataId = DataId(cursor.getString(cursor.getColumnIndexOrThrow(Note._ID))),
20
- note = cursor.getString(cursor.getColumnIndexOrThrow(Note.NOTE))
21
+ dataId = DataId(cursor.getRequiredString(cursor.getColumnIndexOrThrow(Note._ID))),
22
+ note = cursor.getNullableString(cursor.getColumnIndexOrThrow(Note.NOTE))
21
23
  )
22
24
  }
@@ -5,6 +5,8 @@ import android.provider.ContactsContract.CommonDataKinds.Organization
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.organization.operations.ExistingOrganization
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableString
9
+ import expo.modules.contacts.next.extensions.getRequiredString
8
10
 
9
11
  object OrganizationField : ExtractableField.Data<ExistingOrganization> {
10
12
  override val mimeType = Organization.CONTENT_ITEM_TYPE
@@ -19,11 +21,11 @@ object OrganizationField : ExtractableField.Data<ExistingOrganization> {
19
21
 
20
22
  override fun extract(cursor: Cursor): ExistingOrganization = with(cursor) {
21
23
  return ExistingOrganization(
22
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
23
- company = getString(getColumnIndexOrThrow(Organization.COMPANY)),
24
- department = getString(getColumnIndexOrThrow(Organization.DEPARTMENT)),
25
- jobTitle = getString(getColumnIndexOrThrow(Organization.TITLE)),
26
- phoneticName = getString(getColumnIndexOrThrow(Organization.PHONETIC_NAME))
24
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
25
+ company = getNullableString(getColumnIndexOrThrow(Organization.COMPANY)),
26
+ department = getNullableString(getColumnIndexOrThrow(Organization.DEPARTMENT)),
27
+ jobTitle = getNullableString(getColumnIndexOrThrow(Organization.TITLE)),
28
+ phoneticName = getNullableString(getColumnIndexOrThrow(Organization.PHONETIC_NAME))
27
29
  )
28
30
  }
29
31
  }
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.Phone
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.phone.operations.ExistingPhone
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object PhoneField : ExtractableField.Data<ExistingPhone> {
10
13
  override val mimeType = Phone.CONTENT_ITEM_TYPE
@@ -18,14 +21,14 @@ object PhoneField : ExtractableField.Data<ExistingPhone> {
18
21
 
19
22
  override fun extract(cursor: Cursor): ExistingPhone = with(cursor) {
20
23
  return ExistingPhone(
21
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
22
- number = getString(getColumnIndexOrThrow(Phone.NUMBER)),
24
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
25
+ number = getNullableString(getColumnIndexOrThrow(Phone.NUMBER)),
23
26
  label = extractLabel()
24
27
  )
25
28
  }
26
29
 
27
30
  private fun Cursor.extractLabel(): PhoneLabel =
28
- when (getInt(getColumnIndexOrThrow(Phone.TYPE))) {
31
+ when (getNullableInt(Phone.TYPE)) {
29
32
  Phone.TYPE_HOME -> PhoneLabel.Home
30
33
  Phone.TYPE_MOBILE -> PhoneLabel.Mobile
31
34
  Phone.TYPE_WORK -> PhoneLabel.Work
@@ -46,9 +49,13 @@ object PhoneField : ExtractableField.Data<ExistingPhone> {
46
49
  Phone.TYPE_WORK_PAGER -> PhoneLabel.WorkPager
47
50
  Phone.TYPE_ASSISTANT -> PhoneLabel.Assistant
48
51
  Phone.TYPE_MMS -> PhoneLabel.Mms
52
+ null -> {
53
+ val customLabel = getNullableString(getColumnIndexOrThrow(Phone.LABEL))
54
+ PhoneLabel.MalformedType(customLabel)
55
+ }
49
56
  else -> {
50
- val customLabel = getString(getColumnIndexOrThrow(Phone.LABEL))
51
- PhoneLabel.Custom(customLabel)
57
+ val customLabel = getNullableString(getColumnIndexOrThrow(Phone.LABEL))
58
+ customLabel?.let { PhoneLabel.Custom(it) } ?: PhoneLabel.MalformedCustom
52
59
  }
53
60
  }
54
61
  }
@@ -2,7 +2,7 @@ package expo.modules.contacts.next.domain.model.phone
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Phone
4
4
 
5
- sealed class PhoneLabel(val type: Int, val label: String? = null) {
5
+ sealed class PhoneLabel(val type: Int?, val label: String? = null) {
6
6
  object Home : PhoneLabel(Phone.TYPE_HOME)
7
7
  object Mobile : PhoneLabel(Phone.TYPE_MOBILE)
8
8
  object Work : PhoneLabel(Phone.TYPE_WORK)
@@ -24,4 +24,9 @@ sealed class PhoneLabel(val type: Int, val label: String? = null) {
24
24
  object Assistant : PhoneLabel(Phone.TYPE_ASSISTANT)
25
25
  object Mms : PhoneLabel(Phone.TYPE_MMS)
26
26
  class Custom(label: String) : PhoneLabel(Phone.TYPE_CUSTOM, label)
27
+
28
+ // Ideally these states would not be necessary, but Android does not enforce
29
+ // type and label columns as non-null, so malformed label data can exist there.
30
+ class MalformedType(label: String?) : PhoneLabel(null, label)
31
+ object MalformedCustom : PhoneLabel(Phone.TYPE_CUSTOM)
27
32
  }
@@ -5,6 +5,7 @@ import android.provider.ContactsContract.CommonDataKinds.Photo
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.photo.operations.ExistingPhoto
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getRequiredString
8
9
 
9
10
  object PhotoField : ExtractableField.Data<ExistingPhoto> {
10
11
  override val mimeType = Photo.CONTENT_ITEM_TYPE
@@ -16,7 +17,7 @@ object PhotoField : ExtractableField.Data<ExistingPhoto> {
16
17
 
17
18
  override fun extract(cursor: Cursor): ExistingPhoto = with(cursor) {
18
19
  return@with ExistingPhoto(
19
- dataId = DataId(getString(getColumnIndexOrThrow(Photo._ID))),
20
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(Photo._ID))),
20
21
  photo = getBlob(getColumnIndexOrThrow(Photo.PHOTO))
21
22
  )
22
23
  }
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.Relation
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.relationship.operations.ExistingRelation
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object RelationField : ExtractableField.Data<ExistingRelation> {
10
13
  override val projection = arrayOf(Relation._ID, Relation.NAME, Relation.TYPE, Relation.LABEL)
@@ -13,14 +16,14 @@ object RelationField : ExtractableField.Data<ExistingRelation> {
13
16
 
14
17
  override fun extract(cursor: Cursor): ExistingRelation = with(cursor) {
15
18
  return ExistingRelation(
16
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
17
- name = getString(getColumnIndexOrThrow(Relation.NAME)),
19
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
20
+ name = getNullableString(getColumnIndexOrThrow(Relation.NAME)),
18
21
  label = extractLabel()
19
22
  )
20
23
  }
21
24
 
22
25
  private fun Cursor.extractLabel() =
23
- when (getInt(getColumnIndexOrThrow(Relation.TYPE))) {
26
+ when (getNullableInt(Relation.TYPE)) {
24
27
  Relation.TYPE_ASSISTANT -> RelationLabel.Assistant
25
28
  Relation.TYPE_BROTHER -> RelationLabel.Brother
26
29
  Relation.TYPE_CHILD -> RelationLabel.Child
@@ -35,9 +38,13 @@ object RelationField : ExtractableField.Data<ExistingRelation> {
35
38
  Relation.TYPE_RELATIVE -> RelationLabel.Relative
36
39
  Relation.TYPE_SISTER -> RelationLabel.Sister
37
40
  Relation.TYPE_SPOUSE -> RelationLabel.Spouse
41
+ null -> {
42
+ val customLabel = getNullableString(getColumnIndexOrThrow(Relation.LABEL))
43
+ RelationLabel.MalformedType(customLabel)
44
+ }
38
45
  else -> {
39
- val customLabel = getString(getColumnIndexOrThrow(Relation.LABEL))
40
- RelationLabel.Custom(customLabel)
46
+ val customLabel = getNullableString(getColumnIndexOrThrow(Relation.LABEL))
47
+ customLabel?.let { RelationLabel.Custom(it) } ?: RelationLabel.MalformedCustom
41
48
  }
42
49
  }
43
50
  }
@@ -2,7 +2,7 @@ package expo.modules.contacts.next.domain.model.relationship
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Relation
4
4
 
5
- sealed class RelationLabel(val type: Int, val label: String? = null) {
5
+ sealed class RelationLabel(val type: Int?, val label: String? = null) {
6
6
  object Assistant : RelationLabel(Relation.TYPE_ASSISTANT)
7
7
  object Brother : RelationLabel(Relation.TYPE_BROTHER)
8
8
  object Child : RelationLabel(Relation.TYPE_CHILD)
@@ -18,4 +18,9 @@ sealed class RelationLabel(val type: Int, val label: String? = null) {
18
18
  object Sister : RelationLabel(Relation.TYPE_SISTER)
19
19
  object Spouse : RelationLabel(Relation.TYPE_SPOUSE)
20
20
  class Custom(label: String) : RelationLabel(Relation.TYPE_CUSTOM, label)
21
+
22
+ // Ideally these states would not be necessary, but Android does not enforce
23
+ // type and label columns as non-null, so malformed label data can exist there.
24
+ class MalformedType(label: String?) : RelationLabel(null, label)
25
+ object MalformedCustom : RelationLabel(Relation.TYPE_CUSTOM)
21
26
  }
@@ -6,6 +6,8 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName
6
6
  import expo.modules.contacts.next.domain.model.ExtractableField
7
7
  import expo.modules.contacts.next.domain.model.structuredname.operations.ExistingStructuredName
8
8
  import expo.modules.contacts.next.domain.wrappers.DataId
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
9
11
 
10
12
  object StructuredNameField : ExtractableField.Data<ExistingStructuredName> {
11
13
  override val mimeType = StructuredName.CONTENT_ITEM_TYPE
@@ -26,15 +28,15 @@ object StructuredNameField : ExtractableField.Data<ExistingStructuredName> {
26
28
 
27
29
  override fun extract(cursor: Cursor): ExistingStructuredName = with(cursor) {
28
30
  return ExistingStructuredName(
29
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
30
- givenName = getString(getColumnIndexOrThrow(StructuredName.GIVEN_NAME)),
31
- familyName = getString(getColumnIndexOrThrow(StructuredName.FAMILY_NAME)),
32
- middleName = getString(getColumnIndexOrThrow(StructuredName.MIDDLE_NAME)),
33
- prefix = getString(getColumnIndexOrThrow(StructuredName.PREFIX)),
34
- suffix = getString(getColumnIndexOrThrow(StructuredName.SUFFIX)),
35
- phoneticGivenName = getString(getColumnIndexOrThrow(StructuredName.PHONETIC_GIVEN_NAME)),
36
- phoneticMiddleName = getString(getColumnIndexOrThrow(StructuredName.PHONETIC_MIDDLE_NAME)),
37
- phoneticFamilyName = getString(getColumnIndexOrThrow(StructuredName.PHONETIC_FAMILY_NAME))
31
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
32
+ givenName = getNullableString(getColumnIndexOrThrow(StructuredName.GIVEN_NAME)),
33
+ familyName = getNullableString(getColumnIndexOrThrow(StructuredName.FAMILY_NAME)),
34
+ middleName = getNullableString(getColumnIndexOrThrow(StructuredName.MIDDLE_NAME)),
35
+ prefix = getNullableString(getColumnIndexOrThrow(StructuredName.PREFIX)),
36
+ suffix = getNullableString(getColumnIndexOrThrow(StructuredName.SUFFIX)),
37
+ phoneticGivenName = getNullableString(getColumnIndexOrThrow(StructuredName.PHONETIC_GIVEN_NAME)),
38
+ phoneticMiddleName = getNullableString(getColumnIndexOrThrow(StructuredName.PHONETIC_MIDDLE_NAME)),
39
+ phoneticFamilyName = getNullableString(getColumnIndexOrThrow(StructuredName.PHONETIC_FAMILY_NAME))
38
40
  )
39
41
  }
40
42
  }
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.structuredpostal.operations.ExistingStructuredPostal
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object StructuredPostalField : ExtractableField.Data<ExistingStructuredPostal> {
10
13
  override val mimeType = StructuredPostal.CONTENT_ITEM_TYPE
@@ -22,24 +25,28 @@ object StructuredPostalField : ExtractableField.Data<ExistingStructuredPostal> {
22
25
 
23
26
  override fun extract(cursor: Cursor): ExistingStructuredPostal = with(cursor) {
24
27
  return ExistingStructuredPostal(
25
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
26
- street = getString(getColumnIndexOrThrow(StructuredPostal.STREET)),
27
- city = getString(getColumnIndexOrThrow(StructuredPostal.CITY)),
28
- region = getString(getColumnIndexOrThrow(StructuredPostal.REGION)),
29
- postcode = getString(getColumnIndexOrThrow(StructuredPostal.POSTCODE)),
30
- country = getString(getColumnIndexOrThrow(StructuredPostal.COUNTRY)),
28
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
29
+ street = getNullableString(getColumnIndexOrThrow(StructuredPostal.STREET)),
30
+ city = getNullableString(getColumnIndexOrThrow(StructuredPostal.CITY)),
31
+ region = getNullableString(getColumnIndexOrThrow(StructuredPostal.REGION)),
32
+ postcode = getNullableString(getColumnIndexOrThrow(StructuredPostal.POSTCODE)),
33
+ country = getNullableString(getColumnIndexOrThrow(StructuredPostal.COUNTRY)),
31
34
  label = extractLabel()
32
35
  )
33
36
  }
34
37
 
35
38
  private fun Cursor.extractLabel() =
36
- when (getInt(getColumnIndexOrThrow(StructuredPostal.TYPE))) {
39
+ when (getNullableInt(StructuredPostal.TYPE)) {
37
40
  StructuredPostal.TYPE_HOME -> StructuredPostalLabel.Home
38
41
  StructuredPostal.TYPE_WORK -> StructuredPostalLabel.Work
39
42
  StructuredPostal.TYPE_OTHER -> StructuredPostalLabel.Other
43
+ null -> {
44
+ val customLabel = getNullableString(getColumnIndexOrThrow(StructuredPostal.LABEL))
45
+ StructuredPostalLabel.MalformedType(customLabel)
46
+ }
40
47
  else -> {
41
- val customLabel = getString(getColumnIndexOrThrow(StructuredPostal.LABEL))
42
- StructuredPostalLabel.Custom(customLabel)
48
+ val customLabel = getNullableString(getColumnIndexOrThrow(StructuredPostal.LABEL))
49
+ customLabel?.let { StructuredPostalLabel.Custom(it) } ?: StructuredPostalLabel.MalformedCustom
43
50
  }
44
51
  }
45
52
  }
@@ -2,9 +2,14 @@ package expo.modules.contacts.next.domain.model.structuredpostal
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.StructuredPostal
4
4
 
5
- sealed class StructuredPostalLabel(val type: Int, val label: String? = null) {
5
+ sealed class StructuredPostalLabel(val type: Int?, val label: String? = null) {
6
6
  object Home : StructuredPostalLabel(StructuredPostal.TYPE_HOME)
7
7
  object Work : StructuredPostalLabel(StructuredPostal.TYPE_WORK)
8
8
  object Other : StructuredPostalLabel(StructuredPostal.TYPE_OTHER)
9
9
  class Custom(label: String) : StructuredPostalLabel(StructuredPostal.TYPE_CUSTOM, label)
10
+
11
+ // Ideally these states would not be necessary, but Android does not enforce
12
+ // type and label columns as non-null, so malformed label data can exist there.
13
+ class MalformedType(label: String?) : StructuredPostalLabel(null, label)
14
+ object MalformedCustom : StructuredPostalLabel(StructuredPostal.TYPE_CUSTOM)
10
15
  }
@@ -5,6 +5,9 @@ import android.provider.ContactsContract.CommonDataKinds.Website
5
5
  import expo.modules.contacts.next.domain.model.ExtractableField
6
6
  import expo.modules.contacts.next.domain.model.website.operations.ExistingWebsite
7
7
  import expo.modules.contacts.next.domain.wrappers.DataId
8
+ import expo.modules.contacts.next.extensions.getNullableInt
9
+ import expo.modules.contacts.next.extensions.getNullableString
10
+ import expo.modules.contacts.next.extensions.getRequiredString
8
11
 
9
12
  object WebsiteField : ExtractableField.Data<ExistingWebsite> {
10
13
  override val projection = arrayOf(Website._ID, Website.URL, Website.TYPE, Website.LABEL)
@@ -13,14 +16,14 @@ object WebsiteField : ExtractableField.Data<ExistingWebsite> {
13
16
 
14
17
  override fun extract(cursor: Cursor): ExistingWebsite = with(cursor) {
15
18
  return ExistingWebsite(
16
- dataId = DataId(getString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
17
- url = getString(getColumnIndexOrThrow(Website.URL)),
19
+ dataId = DataId(getRequiredString(getColumnIndexOrThrow(DataId.COLUMN_IN_DATA_TABLE))),
20
+ url = getNullableString(getColumnIndexOrThrow(Website.URL)),
18
21
  label = extractLabel()
19
22
  )
20
23
  }
21
24
 
22
25
  private fun Cursor.extractLabel() =
23
- when (getInt(getColumnIndexOrThrow(Website.TYPE))) {
26
+ when (getNullableInt(Website.TYPE)) {
24
27
  Website.TYPE_HOMEPAGE -> WebsiteLabel.Homepage
25
28
  Website.TYPE_BLOG -> WebsiteLabel.Blog
26
29
  Website.TYPE_FTP -> WebsiteLabel.Ftp
@@ -28,9 +31,13 @@ object WebsiteField : ExtractableField.Data<ExistingWebsite> {
28
31
  Website.TYPE_WORK -> WebsiteLabel.Work
29
32
  Website.TYPE_OTHER -> WebsiteLabel.Other
30
33
  Website.TYPE_PROFILE -> WebsiteLabel.Profile
34
+ null -> {
35
+ val customLabel = getNullableString(getColumnIndexOrThrow(Website.LABEL))
36
+ WebsiteLabel.MalformedType(customLabel)
37
+ }
31
38
  else -> {
32
- val customLabel = getString(getColumnIndexOrThrow(Website.LABEL))
33
- WebsiteLabel.Custom(customLabel)
39
+ val customLabel = getNullableString(getColumnIndexOrThrow(Website.LABEL))
40
+ customLabel?.let { WebsiteLabel.Custom(it) } ?: WebsiteLabel.MalformedCustom
34
41
  }
35
42
  }
36
43
  }
@@ -2,7 +2,7 @@ package expo.modules.contacts.next.domain.model.website
2
2
 
3
3
  import android.provider.ContactsContract.CommonDataKinds.Website
4
4
 
5
- sealed class WebsiteLabel(val type: Int, val label: String? = null) {
5
+ sealed class WebsiteLabel(val type: Int?, val label: String? = null) {
6
6
  object Homepage : WebsiteLabel(Website.TYPE_HOMEPAGE)
7
7
  object Blog : WebsiteLabel(Website.TYPE_BLOG)
8
8
  object Ftp : WebsiteLabel(Website.TYPE_FTP)
@@ -11,4 +11,9 @@ sealed class WebsiteLabel(val type: Int, val label: String? = null) {
11
11
  object Other : WebsiteLabel(Website.TYPE_OTHER)
12
12
  object Profile : WebsiteLabel(Website.TYPE_PROFILE)
13
13
  class Custom(label: String) : WebsiteLabel(Website.TYPE_CUSTOM, label)
14
+
15
+ // Ideally these states would not be necessary, but Android does not enforce
16
+ // type and label columns as non-null, so malformed label data can exist there.
17
+ class MalformedType(label: String?) : WebsiteLabel(null, label)
18
+ object MalformedCustom : WebsiteLabel(Website.TYPE_CUSTOM)
14
19
  }
@@ -21,6 +21,7 @@ import expo.modules.contacts.next.domain.model.structuredname.operations.Existin
21
21
  import expo.modules.contacts.next.domain.model.structuredpostal.operations.ExistingStructuredPostal
22
22
  import expo.modules.contacts.next.domain.model.website.operations.ExistingWebsite
23
23
  import expo.modules.contacts.next.domain.wrappers.ContactId
24
+ import expo.modules.contacts.next.extensions.getRequiredString
24
25
 
25
26
  class QueryAggregator(extractableFields: Collection<ExtractableField<*>>) {
26
27
  private val contactsExtractableFields = extractableFields.filterIsInstance<ExtractableField.Contacts<*>>()
@@ -33,8 +34,8 @@ class QueryAggregator(extractableFields: Collection<ExtractableField<*>>) {
33
34
  fun buildContacts(): List<ExistingContact> = contactBuilders.values.map { it.build() }
34
35
 
35
36
  fun aggregateDataRow(cursor: Cursor) {
36
- val contactId = cursor.getString(cursor.getColumnIndexOrThrow(ContactId.COLUMN_IN_DATA_TABLE))
37
- val mime = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
37
+ val contactId = cursor.getRequiredString(cursor.getColumnIndexOrThrow(ContactId.COLUMN_IN_DATA_TABLE))
38
+ val mime = cursor.getRequiredString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
38
39
  val builder = contactBuilders.getOrPut(contactId) {
39
40
  ContactModelBuilder(ContactId(contactId))
40
41
  }
@@ -45,7 +46,7 @@ class QueryAggregator(extractableFields: Collection<ExtractableField<*>>) {
45
46
  }
46
47
 
47
48
  fun aggregateContactsRow(cursor: Cursor) {
48
- val contactId = cursor.getString(cursor.getColumnIndexOrThrow(ContactId.COLUMN_IN_CONTACTS_TABLE))
49
+ val contactId = cursor.getRequiredString(cursor.getColumnIndexOrThrow(ContactId.COLUMN_IN_CONTACTS_TABLE))
49
50
  val builder = contactBuilders.getOrPut(contactId) {
50
51
  ContactModelBuilder(ContactId(contactId))
51
52
  }
@@ -50,7 +50,7 @@ suspend fun ContentResolver.getContactIdFromRawContactId(
50
50
  val contactIdString = queryOne(
51
51
  uri = ContactsContract.RawContacts.CONTENT_URI,
52
52
  column = ContactId.COLUMN_IN_RAW_CONTACTS_TABLE,
53
- extractor = Cursor::getString,
53
+ extractor = Cursor::getRequiredString,
54
54
  selection = "${ContactsContract.RawContacts._ID} = ?",
55
55
  selectionArgs = arrayOf(rawContactId.value)
56
56
  )
@@ -7,3 +7,16 @@ fun Cursor.asSequence(): Sequence<Cursor> = sequence {
7
7
  yield(this@asSequence)
8
8
  }
9
9
  }
10
+
11
+ // This wrapper enforces null-safety, because the default getString() method returns String! type
12
+ fun Cursor.getNullableString(columnIndex: Int): String? =
13
+ getString(columnIndex)
14
+
15
+ fun Cursor.getRequiredString(columnIndex: Int): String =
16
+ getNullableString(columnIndex)
17
+ ?: throw IllegalStateException("Column at index $columnIndex was null")
18
+
19
+ fun Cursor.getNullableInt(columnName: String): Int? {
20
+ val columnIndex = getColumnIndexOrThrow(columnName)
21
+ return if (isNull(columnIndex)) null else getInt(columnIndex)
22
+ }
@@ -30,6 +30,8 @@ object EmailLabelMapper {
30
30
  is EmailLabel.Mobile -> "mobile"
31
31
  is EmailLabel.Other -> "other"
32
32
  is EmailLabel.Custom -> label.label
33
+ is EmailLabel.MalformedType -> label.label
34
+ is EmailLabel.MalformedCustom -> null
33
35
  }
34
36
  }
35
37
  }
@@ -29,6 +29,8 @@ object EventLabelMapper {
29
29
  is EventLabel.Birthday -> "birthday"
30
30
  is EventLabel.Other -> "other"
31
31
  is EventLabel.Custom -> label.label
32
+ is EventLabel.MalformedType -> label.label
33
+ is EventLabel.MalformedCustom -> null
32
34
  }
33
35
  }
34
36
  }
@@ -32,6 +32,8 @@ object NicknameLabelMapper {
32
32
  is NicknameLabel.ShortName -> "shortName"
33
33
  is NicknameLabel.Initials -> "initials"
34
34
  is NicknameLabel.Custom -> label.label
35
+ is NicknameLabel.MalformedType -> label.label
36
+ is NicknameLabel.MalformedCustom -> null
35
37
  }
36
38
  }
37
39
  }
@@ -62,6 +62,8 @@ object PhoneLabelMapper {
62
62
  is PhoneLabel.Assistant -> "assistant"
63
63
  is PhoneLabel.Mms -> "mms"
64
64
  is PhoneLabel.Custom -> label.label
65
+ is PhoneLabel.MalformedType -> label.label
66
+ is PhoneLabel.MalformedCustom -> null
65
67
  }
66
68
  }
67
69
  }
@@ -50,6 +50,8 @@ object RelationshipLabelMapper {
50
50
  is RelationLabel.Sister -> "sister"
51
51
  is RelationLabel.Spouse -> "spouse"
52
52
  is RelationLabel.Custom -> label.label
53
+ is RelationLabel.MalformedType -> label.label
54
+ is RelationLabel.MalformedCustom -> null
53
55
  }
54
56
  }
55
57
  }
@@ -28,6 +28,8 @@ object StructuredPostalLabelMapper {
28
28
  is StructuredPostalLabel.Work -> "work"
29
29
  is StructuredPostalLabel.Other -> "other"
30
30
  is StructuredPostalLabel.Custom -> label.label
31
+ is StructuredPostalLabel.MalformedType -> label.label
32
+ is StructuredPostalLabel.MalformedCustom -> null
31
33
  }
32
34
  }
33
35
  }
@@ -36,6 +36,8 @@ object WebsiteLabelMapper {
36
36
  is WebsiteLabel.Other -> "other"
37
37
  is WebsiteLabel.Profile -> "profile"
38
38
  is WebsiteLabel.Custom -> label.label
39
+ is WebsiteLabel.MalformedType -> label.label
40
+ is WebsiteLabel.MalformedCustom -> null
39
41
  }
40
42
  }
41
43
  }
@@ -11,7 +11,7 @@
11
11
  "publication": {
12
12
  "groupId": "host.exp.exponent",
13
13
  "artifactId": "expo.modules.contacts",
14
- "version": "56.0.7",
14
+ "version": "56.0.8",
15
15
  "repository": "local-maven-repo"
16
16
  }
17
17
  }
@@ -0,0 +1 @@
1
+ 80eb539e6785429ea03d92b7ebdec819be4ca7c514b999d7d568f00a58b03290
@@ -0,0 +1 @@
1
+ 7450adf345f8cc6c477d612bf1850585b484201e4bcde87bf1b21139a7b07e35321e6e9f1401308b1bca4aedd7d0abce9dd49a27be408c17a220f619f4f94ad7
@@ -0,0 +1 @@
1
+ afc481c1318eb215fc236f1ee2662a96c473d3e656fe92b551f007711afe066e
@@ -0,0 +1 @@
1
+ f30c1e8acc0e5ec102e625768b01ad79175402839bb9397589d222a1c3f62a7a21078fe01657b147865fe49a9430bfa0e6950259a43ca38c005c432ae6fe12cd
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.contacts",
6
- "version": "56.0.7",
6
+ "version": "56.0.8",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.contacts-56.0.7.aar",
28
- "url": "expo.modules.contacts-56.0.7.aar",
29
- "size": 963274,
30
- "sha512": "36a86e6296630a4be87463592e576faacc2cd01f96e2ed7f4608da70e342bf23eb5b683ece3f4686c7972309f4156130d8f622f50a9ad45dbb10473ce99d7f83",
31
- "sha256": "4ca5d2229fb65c779b47161b065d3cb1d9d8fa3e9423c18289fa6a74a29b52c7",
32
- "sha1": "2cde4669442f4d40b342c74448257dfc9b34bcff",
33
- "md5": "89e0f10a2d057e862b366b72039c4308"
27
+ "name": "expo.modules.contacts-56.0.8.aar",
28
+ "url": "expo.modules.contacts-56.0.8.aar",
29
+ "size": 977544,
30
+ "sha512": "f30c1e8acc0e5ec102e625768b01ad79175402839bb9397589d222a1c3f62a7a21078fe01657b147865fe49a9430bfa0e6950259a43ca38c005c432ae6fe12cd",
31
+ "sha256": "afc481c1318eb215fc236f1ee2662a96c473d3e656fe92b551f007711afe066e",
32
+ "sha1": "2716ff5a5f8d0185cffd0fce754b5d5e9c7dd64c",
33
+ "md5": "cea1e4112eb06973606a656c03dfdb50"
34
34
  }
35
35
  ]
36
36
  },
@@ -67,13 +67,13 @@
67
67
  ],
68
68
  "files": [
69
69
  {
70
- "name": "expo.modules.contacts-56.0.7.aar",
71
- "url": "expo.modules.contacts-56.0.7.aar",
72
- "size": 963274,
73
- "sha512": "36a86e6296630a4be87463592e576faacc2cd01f96e2ed7f4608da70e342bf23eb5b683ece3f4686c7972309f4156130d8f622f50a9ad45dbb10473ce99d7f83",
74
- "sha256": "4ca5d2229fb65c779b47161b065d3cb1d9d8fa3e9423c18289fa6a74a29b52c7",
75
- "sha1": "2cde4669442f4d40b342c74448257dfc9b34bcff",
76
- "md5": "89e0f10a2d057e862b366b72039c4308"
70
+ "name": "expo.modules.contacts-56.0.8.aar",
71
+ "url": "expo.modules.contacts-56.0.8.aar",
72
+ "size": 977544,
73
+ "sha512": "f30c1e8acc0e5ec102e625768b01ad79175402839bb9397589d222a1c3f62a7a21078fe01657b147865fe49a9430bfa0e6950259a43ca38c005c432ae6fe12cd",
74
+ "sha256": "afc481c1318eb215fc236f1ee2662a96c473d3e656fe92b551f007711afe066e",
75
+ "sha1": "2716ff5a5f8d0185cffd0fce754b5d5e9c7dd64c",
76
+ "md5": "cea1e4112eb06973606a656c03dfdb50"
77
77
  }
78
78
  ]
79
79
  },
@@ -87,13 +87,13 @@
87
87
  },
88
88
  "files": [
89
89
  {
90
- "name": "expo.modules.contacts-56.0.7-sources.jar",
91
- "url": "expo.modules.contacts-56.0.7-sources.jar",
92
- "size": 123222,
93
- "sha512": "9969a12f32e6a4282613d7efd6d628d0ebea25c72d0fd6ce2ea4c3d33ae508371e877664b0b91ef4156e2b7d15d460fd76b9ea3740be7a712f015842f70e6133",
94
- "sha256": "4726bfaefc77b92270a606ae7e90c4beaea4606fa91c94b99a0f2d3d621e204d",
95
- "sha1": "57e0d7a5aef1524181967b06eeca46b69f3852b0",
96
- "md5": "44182a2e7b24015a736b94ad45415554"
90
+ "name": "expo.modules.contacts-56.0.8-sources.jar",
91
+ "url": "expo.modules.contacts-56.0.8-sources.jar",
92
+ "size": 125128,
93
+ "sha512": "7450adf345f8cc6c477d612bf1850585b484201e4bcde87bf1b21139a7b07e35321e6e9f1401308b1bca4aedd7d0abce9dd49a27be408c17a220f619f4f94ad7",
94
+ "sha256": "80eb539e6785429ea03d92b7ebdec819be4ca7c514b999d7d568f00a58b03290",
95
+ "sha1": "a401ae5d2c4113040cad86ed796831f37787d46f",
96
+ "md5": "c89216c8164fa761e5479a3ff237b1a4"
97
97
  }
98
98
  ]
99
99
  }
@@ -0,0 +1 @@
1
+ 6e157c02c2afbc9280f545c1e8ff4b9c784f3bc16cb76f2f71e65c698618aeb9
@@ -0,0 +1 @@
1
+ 94166e4aa0997658c6604c91cbcd37476f8bddb77d6385497bb017bf4c6fb6270eef865971b7bdf0cf51ff416ffc5a34e7b505bae2021603ba19fcf041a2c75a
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.contacts</artifactId>
12
- <version>56.0.7</version>
12
+ <version>56.0.8</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.contacts</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 342cd553f81391ea0b2a3e7cd1a6e75100e6b849d43290541bb5e35537050326
@@ -0,0 +1 @@
1
+ 9be87e80af2d745e8b091a598a8d415193fcf0b1943be5aa84847b2f25710fc98573636c072794a095671d58fe570fb6042ad2e8b0904e8b987b654335e3e51f
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.contacts</artifactId>
5
5
  <versioning>
6
- <latest>56.0.7</latest>
7
- <release>56.0.7</release>
6
+ <latest>56.0.8</latest>
7
+ <release>56.0.8</release>
8
8
  <versions>
9
- <version>56.0.7</version>
9
+ <version>56.0.8</version>
10
10
  </versions>
11
- <lastUpdated>20260521153959</lastUpdated>
11
+ <lastUpdated>20260605211301</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 3ce9caeace0075f340cf23d6df6d2cb7
1
+ d7c7f773771367b7af3e7a200532d0d5
@@ -1 +1 @@
1
- 69d5d43ba141092add958fd3330d21ffdce63905
1
+ eb66443ab03cbd785e05f92c823de13e6e22d8d2
@@ -1 +1 @@
1
- 36e919c6cdca3308d8ec59b437db8fccc1a67e2e7960b03e11e3dd7bd05d1448
1
+ 5f06c224e2ffcc3ee4f67ddbc7ea35e6f8cb505fee4f7a09a566411452a8e54a
@@ -1 +1 @@
1
- 85de04fc4eda4b7412eaa7dd847e09663d3770273c21ca22b754d777bb1c5d743b1a58253257611c4cd54a7e6b70f608796c2be0ab5d3f3b9a88b23d491c316e
1
+ 08de9d9acf7dc3be28a0c8f942f2f52c041c17b7da197497de28c69ab85ad382251e7c638657936dbc77524acd392f072f5ba7fae632572b333bec38e84a6820
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-contacts",
3
- "version": "56.0.7",
3
+ "version": "56.0.8",
4
4
  "description": "Provides access to the phone's system contacts.",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -45,15 +45,15 @@
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.14.0",
47
47
  "@types/react": "~19.2.0",
48
- "expo": "56.0.3",
49
- "expo-module-scripts": "56.0.2"
48
+ "expo": "56.0.9",
49
+ "expo-module-scripts": "56.0.3"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "expo": "*",
53
53
  "react": "*",
54
54
  "react-native": "*"
55
55
  },
56
- "gitHead": "125e8225bf36a4b9b2a159441d9ea724bcf1110f",
56
+ "gitHead": "175f1e78e3444ca99ddea473faea6777a0656668",
57
57
  "scripts": {
58
58
  "build": "expo-module build",
59
59
  "clean": "expo-module clean",
@@ -1 +0,0 @@
1
- 4726bfaefc77b92270a606ae7e90c4beaea4606fa91c94b99a0f2d3d621e204d
@@ -1 +0,0 @@
1
- 9969a12f32e6a4282613d7efd6d628d0ebea25c72d0fd6ce2ea4c3d33ae508371e877664b0b91ef4156e2b7d15d460fd76b9ea3740be7a712f015842f70e6133
@@ -1 +0,0 @@
1
- 4ca5d2229fb65c779b47161b065d3cb1d9d8fa3e9423c18289fa6a74a29b52c7
@@ -1 +0,0 @@
1
- 36a86e6296630a4be87463592e576faacc2cd01f96e2ed7f4608da70e342bf23eb5b683ece3f4686c7972309f4156130d8f622f50a9ad45dbb10473ce99d7f83
@@ -1 +0,0 @@
1
- f1954e229eb7fbac8bb5850d944e6604f8bca0b17f655201409ff9d752a30bd6
@@ -1 +0,0 @@
1
- 6ebb085067bbb7c10e04cfba3761173f0950ad65682671a6d5ba578f18bfd41ae494fff477bfadc4a3c04164d6f32d9980133f8cbcfc11ba52111a4655a2dc5d
@@ -1 +0,0 @@
1
- 82704bed9edc3ae05f0af3d6eb2c44d4d566da5f23d91eda3814fd481b5625b2
@@ -1 +0,0 @@
1
- de5377706e2221b77e22cbfcb947dff82783a12ace0aa7b7db3f575e0303dca81bee6b5949bd678b162beb925fff5fec54f6d2f3d158d2d54b2ba59c6ce5fc72