Skip to content

Commit e0bd9c6

Browse files
committed
Release 3.1.1
1 parent 72bf567 commit e0bd9c6

File tree

12 files changed

+95
-54
lines changed

12 files changed

+95
-54
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.1.1 - 19 Sep 2025
2+
3+
- Fixes an issue where the `setLicenseKey` API does not run synchronously on Android. (J#HYB-881)
4+
- Fixes an issue where the `FormFieldEvent.VALUES_UPDATED` event returned incomplete objects. (J#HYB-884)
5+
16
## 3.1.0 - 10 Sep 2025
27

38
- Adds the `getOverlappingSignature` API to `SignatureFormElement` objects to retrieve overlapping signature annotations. (J#HYB-867)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ See our [Getting Started on React Native guide](https://www.nutrient.io/getting-
6666
1. Create the React Native project by running the following command:
6767

6868
```bash
69-
npx react-native init NutrientDemo
69+
npx @react-native-community/cli init NutrientDemo
7070
```
7171

7272
1. In the terminal app, change the location of the current working directory inside the newly created project:

android/src/main/java/com/pspdfkit/react/PSPDFKitModule.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,17 @@ public void run() {
249249
}
250250
}
251251

252-
@ReactMethod
253-
public void setLicenseKey(@Nullable String licenseKey, @Nullable Promise promise) {
254-
try {
255-
InitializationOptions options = new InitializationOptions(licenseKey, emptyList(), CrossPlatformTechnology.ReactNative, null);
256-
Nutrient.initialize(getReactApplicationContext(), options);
257-
promise.resolve("Initialised Nutrient");
252+
@ReactMethod(isBlockingSynchronousMethod = true)
253+
public boolean setLicenseKey(@Nullable String licenseKey) {
254+
try {
255+
if (licenseKey == null) {
256+
licenseKey = "";
257+
}
258+
InitializationOptions options = new InitializationOptions(licenseKey, emptyList(), CrossPlatformTechnology.ReactNative, null);
259+
Nutrient.initialize(getReactApplicationContext(), options);
260+
return true;
258261
} catch (InvalidNutrientLicenseException e) {
259-
promise.reject(e);
262+
return false;
260263
}
261264
}
262265

@@ -293,16 +296,19 @@ public WritableMap getDocumentProperties(@Nullable String documentPath) {
293296
return properties;
294297
}
295298

296-
@ReactMethod
297-
public void setLicenseKeys(@Nullable String androidLicenseKey, @Nullable String iOSLicenseKey, @Nullable Promise promise) {
299+
@ReactMethod(isBlockingSynchronousMethod = true)
300+
public boolean setLicenseKeys(@Nullable String androidLicenseKey, @Nullable String iOSLicenseKey) {
298301
// Here, we ignore the `iOSLicenseKey` parameter and only care about `androidLicenseKey`.
299302
// `iOSLicenseKey` will be used to activate the license on iOS.
300303
try {
304+
if (androidLicenseKey == null) {
305+
androidLicenseKey = "";
306+
}
301307
InitializationOptions options = new InitializationOptions(androidLicenseKey, emptyList(), CrossPlatformTechnology.ReactNative, null);
302308
Nutrient.initialize(getReactApplicationContext(), options);
303-
promise.resolve("Initialised Nutrient");
309+
return true;
304310
} catch (InvalidNutrientLicenseException e) {
305-
promise.reject(e);
311+
return false;
306312
}
307313
}
308314

android/src/main/java/com/pspdfkit/react/helper/FormUtils.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ object FormUtils {
6969
}
7070
is ChoiceFormElement -> {
7171
elementJSON["formTypeName"] = "choice"
72+
formElement.options.let { options ->
73+
elementJSON["options"] = options.map { option ->
74+
mapOf(
75+
"value" to option.value,
76+
"label" to option.label
77+
)
78+
}
79+
}
7280
elementJSON["selectedIndices"] = formElement.selectedIndexes
7381
if (formElement is ComboBoxFormElement && formElement.isCustomTextSet) {
7482
elementJSON["value"] = formElement.customText ?: ""

index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,7 +1781,7 @@ export class Nutrient {
17811781
* @method setLicenseKey
17821782
* @memberof Nutrient
17831783
* @param { string | null } [key] Your Nutrient for React Native iOS or Nutrient for React Native Android license key.
1784-
* @returns { Promise<boolean> } A promise returning ```true``` if the license key was set, and ```false``` if not.
1784+
* @returns { boolean } A boolean returning ```true``` if the license key was set, and ```false``` if not.
17851785
* @example
17861786
* Nutrient.setLicenseKey('YOUR_LICENSE_KEY');
17871787
*/
@@ -1796,7 +1796,7 @@ export class Nutrient {
17961796
* @memberof Nutrient
17971797
* @param { string | null } [androidKey] Your Nutrient for React Native Android license key.
17981798
* @param { string | null } [iosKey] Your Nutrient for React Native iOS license key.
1799-
* @returns { Promise<boolean> } A promise returning ```true``` if the license keys were set, and ```false``` if not.
1799+
* @returns { boolean } A boolean returning ```true``` if the license key was set, and ```false``` if not.
18001800
* @example
18011801
* Nutrient.setLicenseKeys('YOUR_ANDROID_LICENSE_KEY', 'YOUR_IOS_LICENSE_KEY');
18021802
*/

ios/RCTPSPDFKit/Converters/RCTConvert+FormElement.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import React
1212

1313
@objc extension RCTConvert {
1414

15-
@objc public static func formElementToJSON(_ formElement: FormElement) -> Dictionary<String, Any> {
15+
@objc public static func baseFormElementToJSON(_ formElement: FormElement) -> Dictionary<String, Any> {
1616

1717
var formElementDictionary = Dictionary<String, Any>()
1818

@@ -36,7 +36,7 @@ import React
3636
}
3737

3838
@objc public static func buttonFormElementToJSON(_ formElement: ButtonFormElement) -> Dictionary<String, Any> {
39-
var json = formElementToJSON(formElement)
39+
var json = baseFormElementToJSON(formElement)
4040

4141
json["selected"] = formElement.isSelected
4242
if let options = formElement.options {
@@ -53,7 +53,7 @@ import React
5353
}
5454

5555
@objc public static func choiceFormElementToJSON(_ formElement: ChoiceFormElement) -> Dictionary<String, Any> {
56-
var json = formElementToJSON(formElement)
56+
var json = baseFormElementToJSON(formElement)
5757

5858
if let options = formElement.options {
5959
json["options"] = options.map { option in
@@ -63,14 +63,14 @@ import React
6363
]
6464
}
6565
}
66-
json["selectedIndices"] = formElement.selectedIndices
66+
json["selectedIndices"] = formElement.selectedIndices?.sorted()
6767
json["isEditable"] = formElement.isEditable
6868

6969
return json
7070
}
7171

7272
@objc public static func signatureFormElementToJSON(_ formElement: SignatureFormElement) -> Dictionary<String, Any> {
73-
var json = formElementToJSON(formElement)
73+
var json = baseFormElementToJSON(formElement)
7474

7575
if let signatureInfo = formElement.signatureInfo {
7676
json["signatureInfo"] = [
@@ -86,7 +86,7 @@ import React
8686
}
8787

8888
@objc public static func textFieldFormElementToJSON(_ formElement: TextFieldFormElement) -> Dictionary<String, Any> {
89-
var json = formElementToJSON(formElement)
89+
var json = baseFormElementToJSON(formElement)
9090

9191
json["value"] = formElement.value
9292
json["isPassword"] = formElement.isPassword

ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFAnnotation.m

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,10 @@ @implementation RCTConvert (PSPDFAnnotation)
3030
if ([annotation isKindOfClass:[PSPDFFormElement class]]) {
3131
PSPDFFormElement *formElement = (PSPDFFormElement *)annotation;
3232
annotationDictionary[@"isRequired"] = @(formElement.isRequired);
33-
NSDictionary *formElementJSON;
34-
35-
if ([formElement isKindOfClass:[PSPDFButtonFormElement class]]) {
36-
formElementJSON = [RCTConvert buttonFormElementToJSON:(PSPDFButtonFormElement *)formElement];
37-
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
38-
mutableJSON[@"type"] = @"button";
39-
formElementJSON = [mutableJSON copy];
40-
} else if ([formElement isKindOfClass:[PSPDFChoiceFormElement class]]) {
41-
formElementJSON = [RCTConvert choiceFormElementToJSON:(PSPDFChoiceFormElement *)formElement];
42-
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
43-
mutableJSON[@"type"] = @"choice";
44-
formElementJSON = [mutableJSON copy];
45-
} else if ([formElement isKindOfClass:[PSPDFSignatureFormElement class]]) {
46-
formElementJSON = [RCTConvert signatureFormElementToJSON:(PSPDFSignatureFormElement *)formElement];
47-
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
48-
mutableJSON[@"type"] = @"signature";
49-
formElementJSON = [mutableJSON copy];
50-
} else if ([formElement isKindOfClass:[PSPDFTextFieldFormElement class]]) {
51-
formElementJSON = [RCTConvert textFieldFormElementToJSON:(PSPDFTextFieldFormElement *)formElement];
52-
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
53-
mutableJSON[@"type"] = @"textField";
54-
formElementJSON = [mutableJSON copy];
55-
} else {
56-
// Skip unknown form element types
57-
continue;
33+
NSDictionary *formElementJSON = [RCTConvert formElementToJSON:formElement];
34+
if (formElementJSON != nil) {
35+
annotationDictionary[@"formElement"] = formElementJSON;
5836
}
59-
60-
annotationDictionary[@"formElement"] = formElementJSON;
6137
}
6238

6339
if (annotationDictionary) {
@@ -72,6 +48,36 @@ @implementation RCTConvert (PSPDFAnnotation)
7248
return [annotationsJSON copy];
7349
}
7450

51+
+ (NSDictionary *)formElementToJSON:(PSPDFFormElement *)formElement {
52+
NSDictionary *formElementJSON;
53+
54+
if ([formElement isKindOfClass:[PSPDFButtonFormElement class]]) {
55+
formElementJSON = [RCTConvert buttonFormElementToJSON:(PSPDFButtonFormElement *)formElement];
56+
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
57+
mutableJSON[@"type"] = @"button";
58+
formElementJSON = [mutableJSON copy];
59+
} else if ([formElement isKindOfClass:[PSPDFChoiceFormElement class]]) {
60+
formElementJSON = [RCTConvert choiceFormElementToJSON:(PSPDFChoiceFormElement *)formElement];
61+
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
62+
mutableJSON[@"type"] = @"choice";
63+
formElementJSON = [mutableJSON copy];
64+
} else if ([formElement isKindOfClass:[PSPDFSignatureFormElement class]]) {
65+
formElementJSON = [RCTConvert signatureFormElementToJSON:(PSPDFSignatureFormElement *)formElement];
66+
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
67+
mutableJSON[@"type"] = @"signature";
68+
formElementJSON = [mutableJSON copy];
69+
} else if ([formElement isKindOfClass:[PSPDFTextFieldFormElement class]]) {
70+
formElementJSON = [RCTConvert textFieldFormElementToJSON:(PSPDFTextFieldFormElement *)formElement];
71+
NSMutableDictionary *mutableJSON = [formElementJSON mutableCopy];
72+
mutableJSON[@"type"] = @"textField";
73+
formElementJSON = [mutableJSON copy];
74+
} else {
75+
return nil; // Return nil if no conditions match
76+
}
77+
78+
return formElementJSON;
79+
}
80+
7581
+ (NSDictionary *)instantJSONFromFormElement:(PSPDFFormElement *)formElement error:(NSError **)error {
7682
NSMutableDictionary *formElementJSON = [NSMutableDictionary new];
7783

@@ -83,6 +89,16 @@ + (NSDictionary *)instantJSONFromFormElement:(PSPDFFormElement *)formElement err
8389
if (formElementData) {
8490
NSMutableDictionary *formElementDictionary = [[NSJSONSerialization JSONObjectWithData:formElementData options:kNilOptions error:error] mutableCopy];
8591
[formElementJSON addEntriesFromDictionary:additionalInfo];
92+
93+
// If this is a Widget annotation, add the FormElement
94+
if ([formElement isKindOfClass:[PSPDFFormElement class]]) {
95+
formElementDictionary[@"isRequired"] = @(formElement.isRequired);
96+
NSDictionary *formElementJSON = [RCTConvert formElementToJSON:formElement];
97+
if (formElementJSON != nil) {
98+
formElementDictionary[@"formElement"] = formElementJSON;
99+
}
100+
}
101+
86102
if (formElementDictionary) {
87103
[formElementJSON addEntriesFromDictionary:formElementDictionary];
88104
}

ios/RCTPSPDFKit/NutrientNotificationCenter.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import PSPDFKit
128128
private var internalEventEmitter: RCTEventEmitter? = nil
129129
@objc static public let shared = NutrientNotificationCenter()
130130
@objc public var isInUse = false
131+
private var lastProcessedId: String?
131132

132133
private override init() {}
133134

@@ -200,14 +201,21 @@ import PSPDFKit
200201
case NSNotification.Name.PSPDFAnnotationChanged:
201202
if let annotation = notification.object as? Annotation {
202203
if (annotation is FormElement) {
203-
if let annotationJSON = try? RCTConvert.instantJSON(from: annotation as? FormElement) {
204+
guard let eventId = annotation.uuid as String? ,
205+
lastProcessedId != eventId else { return }
206+
if let annotationJSON = try? RCTConvert.instantJSON(from: (annotation as! FormElement)) {
204207
let jsonData = ["event" : NotificationEvent.formFieldValuesUpdated.rawValue, "formField" : annotationJSON, "documentID" : documentID] as [String : Any]
205208
let payload = createEventPayload(jsonData: jsonData, componentID: componentID)
206209
eventEmitter?.sendEvent(withName:NotificationEvent.formFieldValuesUpdated.rawValue,
207210
body: payload)
208211
} else {
209212
// Could not decode annotation data
210213
}
214+
lastProcessedId = eventId
215+
// Clear the ID after 100ms
216+
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.1) {
217+
self.lastProcessedId = nil
218+
}
211219
} else {
212220
if let annotationJSON = try? RCTConvert.instantJSON(from: [annotation]) {
213221
let jsonData = ["event" : NotificationEvent.annotationChanged.rawValue, "annotations" : annotationJSON, "documentID" : documentID] as [String : Any]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nutrient-sdk/react-native",
3-
"version": "3.1.0",
3+
"version": "3.1.1",
44
"description": "Nutrient React Native SDK",
55
"keywords": [
66
"react native",

samples/Catalog/examples/InstantSynchronization.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ export default class InstantSynchronization extends BaseExampleAutoHidingHeaderC
7373
delay: this.state.delay ?? '1',
7474
syncAnnotations: this.state.syncAnnotations ?? true,
7575
}).then(async () => {
76-
Nutrient.setLicenseKey(null);
77-
7876
// You can change properties after the document is presented programmatically like in this examples:
7977
await Nutrient.setDelayForSyncingLocalChanges(
8078
parseFloat(this.state.delay),

0 commit comments

Comments
 (0)