Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not able to determine when form is closed after calling Contacts.presentFormAsync on iOS #3696

Closed
arnaudambro opened this issue Mar 11, 2019 · 12 comments

Comments

@arnaudambro
Copy link

Environment

Expo CLI 2.11.7 environment info:
System:
OS: macOS 10.14.3
Shell: 5.3 - /bin/zsh
Binaries:
Node: 8.11.1 - /usr/local/bin/node
Yarn: 1.13.0 - ~/.yarn/bin/yarn
npm: 6.7.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
IDEs:
Android Studio: 3.3 AI-182.5107.16.33.5199772
Xcode: 10.1/10B61 - /usr/bin/xcodebuild
npmPackages:
expo: ^32.0.0 => 32.0.2
react: ^16.5.0 => 16.7.0
react-native: https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz => 0.57.1
npmGlobalPackages:
expo-cli: 2.11.7

target: iOS on simulator

Steps to Reproduce

  1. trigger Contacts. presentFormAsync(id)
  2. edit the contact
  3. finish the editing

Expected Behavior

on Android, it is triggering the update of the AppState, then if I update my contact list based on the phonebook, I got my contact list updated every time I change a contact thanks to presentFormAsync. but not on iOS

@brentvatne
Copy link
Member

i'm not sure if i'd consider this a bug - presentFormAsync should open a view controller modally, which is not an app state change

@arnaudambro
Copy link
Author

Well I would say this : the presentFormAsync is requested to be shown when there is a need from the app to update a contact, it means that the app will make a use of the updated contact. So there is a big chance the app is counting on the updated contact to keep doing what it was doing. The user would also expect the contact to be updated in the app once he finished updating it in his phonebook.
What I want to say is that there should be a way to the App to be notified that the contact has been updated, by an AppState update or something else if you think it is not suited.

@brentvatne
Copy link
Member

it does sound like a reasonable expectation to be able to receive a signal somehow that the editing has completed. i looked into it and was hopeful that the promise would resolve from presentFormAsync upon closing the modal, but it actually just resolves once it has opened.

a workaround for this at the moment is to poll for changes to the contact after you open the form - certainly not ideal as you'd probably need to do a deep comparison on all fields. ideally, i think, we would want presentFormAsync to resolve the promise it returns upon closing the modal. are you interested in opening a pull request for this?

@brentvatne brentvatne changed the title presentFormAsync ending does not trigger any AppState change on iOS Not able to determine when form is closed after calling Contacts.presentFormAsync on iOS Mar 12, 2019
@arnaudambro
Copy link
Author

alright I'll have a look, but it would be my first PR ever on an open source project... I'll give it try

@arnaudambro
Copy link
Author

well I feel a bit lost, I don't even know where to start... I checked the file /ios/versioned-react-native/ABI32_0_0/EXContacts/ABI32_0_0EXContacts/ABI32_0_0EXContacts.m but I have no idea of how to solve the problem, so I checked the /packages/expo-contacts/src/Contacts.ts file, where the code is :

export async function presentFormAsync(
  contactId?: string | null,
  contact?: Contact | null,
  formOptions: FormOptions = {}
): Promise<any> {
  if (!ExpoContacts.presentFormAsync) {
    throw new UnavailabilityError('Contacts', 'presentFormAsync');
  }
  if (Platform.OS === 'ios') {
    let adjustedOptions = formOptions;

    if (contactId) {
      if (contact) {
        contact = undefined;
        console.log(
          'Expo.Contacts.presentFormAsync: You should define either a `contact` or a `contactId` but not both.'
        );
      }
      if (adjustedOptions.isNew !== undefined) {
        console.log(
          'Expo.Contacts.presentFormAsync: formOptions.isNew is not supported with `contactId`'
        );
      }
    }
    return await ExpoContacts.presentFormAsync(contactId, contact, adjustedOptions);
  } else {
    return await ExpoContacts.presentFormAsync(contactId, contact, formOptions);
  }
}

Problem is : to resolve the promise when the modal is closed, I need to know how to know when a modal is closed, isn't it ?

So, basically.... I really want to help and do the job, but I am really lost !

@brentvatne
Copy link
Member

hey @arnaudambro - I think that this will need to be done on the native side. the first place to look would be here:

UM_EXPORT_METHOD_AS(presentFormAsync,
presentFormAsync:(NSString *)identifier
data:(NSDictionary *)data
options:(NSDictionary *)options
resolver:(UMPromiseResolveBlock)resolve
rejecter:(UMPromiseRejectBlock)reject)
{
CNContactStore *contactStore = [self _getContactStoreOrReject:reject];
if(!contactStore) return;
[UMUtilities performSynchronouslyOnMainThread:^{
EXContactsViewController *controller;
CNMutableContact *contact;
if (identifier) {
// Must be full contact from device
contact = [[self _contactWithId:identifier contactStore:contactStore rejecter:reject] mutableCopy];
if (!contact) return;
controller = [EXContactsViewController viewControllerForContact:contact];
} else {
contact = [[CNMutableContact alloc] init];
[self _mutateContact:contact withData:data resolver:resolve rejecter:reject];
BOOL isNew = (options[@"isNew"] != nil && [options[@"isNew"] boolValue]);
if (isNew) {
controller = [EXContactsViewController viewControllerForNewContact:contact];
} else {
controller = [EXContactsViewController viewControllerForUnknownContact:contact];
}
}
if (!controller) {
[EXContacts rejectWithError:@"Could not build controller, invalid props" error:nil rejecter:reject];
return;
}
NSString *cancelButtonTitle = options[@"cancelButtonTitle"];
if (![self _fieldHasValue:cancelButtonTitle])
cancelButtonTitle = @"Cancel";
[controller setCloseButton:cancelButtonTitle];
controller.contactStore = contactStore;
controller.delegate = self;
if ([options[@"displayedPropertyKeys"] isKindOfClass:[NSArray class]])
controller.displayedPropertyKeys = [self _contactKeysToFetchFromFields:options[@"displayedPropertyKeys"]];
if (options[@"allowsEditing"] != nil && [options[@"allowsEditing"] boolValue])
controller.allowsEditing = [options[@"allowsEditing"] boolValue];
if (options[@"allowsActions"] != nil && [options[@"allowsActions"] boolValue])
controller.allowsActions = [options[@"allowsActions"] boolValue];
if (options[@"shouldShowLinkedContacts"] != nil && [options[@"shouldShowLinkedContacts"] boolValue])
controller.shouldShowLinkedContacts = [options[@"shouldShowLinkedContacts"] boolValue];
if (options[@"message"] != nil && [options[@"message"] stringValue])
controller.message = [options[@"message"] stringValue];
if (options[@"alternateName"] != nil && [options[@"alternateName"] stringValue])
controller.alternateName = [options[@"alternateName"] stringValue];
if (options[EXContactsOptionGroupId] != nil && [options[EXContactsOptionGroupId] stringValue])
controller.parentGroup = [self _groupWithId:[options[EXContactsOptionGroupId] stringValue] contactStore:contactStore rejecter:reject];
BOOL isAnimated = true;
if (options[@"preventAnimation"] != nil && [options[@"preventAnimation"] boolValue])
isAnimated = [options[@"preventAnimation"] boolValue];
UIViewController *parent = self->_utilities.currentViewController;
// We need to wrap our contact view controller in UINavigationController.
// See: https://stackoverflow.com/questions/38748969/cnui-error-contact-view-delayed-appearance-timed-out
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
navigationController.modalPresentationStyle = UIModalPresentationFullScreen;
self.presentingViewController = navigationController;
[parent presentViewController:navigationController animated:isAnimated completion:^{
resolve(nil);
}];
}];
}
- but if you're not comfortable making native changes then you may need to wait for someone on the team or in the community to have bandwidth to do this for you. if it's important to your business to get it done quickly, you can hire someone from our recommended consultants page to help you out: https://expo.io/consultants

@sebhs
Copy link

sebhs commented Nov 8, 2019

+1

I also believe it makes sense to resolve promise after the module is closed or have some callback property. Especially when creating a new contact there is no way in telling what the contact ID is after the contact was created.

@arnaudambro
Copy link
Author

That's why I had to go for react-native-contacts.
With all due respect to the expo team, I think it's a bit sad that this is not seen as a bug to resolve, because it makes the feature almost not usable.
Anyway, I tried to reach some consultants, but no answer, and I tried to modify myself, without success ! So I ejected expo and went for react-native-contacts, works like a charm.

@brentvatne
Copy link
Member

we have a lot to work on here, it is an open source project and you're welcome to open a pr. given that you have the implementation for what you want already in react-native-contacts it shouldn't be too tough :)

@arnaudambro
Copy link
Author

That's actually really something I want to do, because expo has been a great tool to me. I'll do as soon as I can

@sebhs
Copy link

sebhs commented Nov 19, 2019

That would be amazing - I would offer it to try it too but I'm only a student and started using react native a month ago...

@arnaudambro
Copy link
Author

problem solved in the next SDK
#5883 (comment)

@lock lock bot added the outdated label May 20, 2020
@lock lock bot locked and limited conversation to collaborators May 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants