Integrating iOS Contact Picker into Flutter Applications using MethodChannel
Published: 2023-02-02 16:03:45

During the development of a project, I was tasked with accessing the user's contact information. In an effort to find a suitable solution, I conducted a search on the [pub.dev ](pub.dev)repository for any available packages that could help me resolve this issue. While I came across some great packages that provided a solution, most of them did not utilize the native contact picker. Although these packages offered a viable solution, I found it more professional to implement the functionality using the native contact picker. This not only provides a more seamless experience for the user but also demonstrates a higher level of technical proficiency.

However, after conducting thorough research, I was unable to find a resource that provided a clear and straightforward explanation on how to achieve this functionality. As a result, I decided to take it upon myself to create a demonstration on the implementation of accessing user contacts using the native contact picker. This not only serves as a solution to my own challenge but also serves as a valuable resource for others who may encounter the same issue in the future.

By the end of this article we will be able to have the following function.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cy16fibjo0zkw7bhbi7u.gif)

**Writing Custom Platform-Specific Code to access contact picker**

In order to accomplish this function we will need to write native code for iOS.

Flutter uses a flexible system that allows you to call platform-specific APIs in a language that works directly with those APIs:

- Kotlin or Java on Android
- Swift or Objective-C on iOS
- C++ on Windows
- Objective-C on macOS
- C on Linux

To achieve the goal of accessing user contacts, we will utilize a channel named `MethodChannel` as a means of communication between the Dart portion of the application and its native counterpart. The Dart part will send messages through the channel and the native part will listen for these messages and take appropriate actions in response. This approach allows for seamless and efficient communication between the two parts of the application, resulting in the successful implementation of the desired functionality.

**MethodChannel** facilitates the invocation of named methods, which can include or exclude arguments, between the Dart and native code. It provides a means for sending a one-time send-and-reply message. It is important to note that this channel does not support the continuous transmission of data streams.

The following explanation is from flutter official docs to explain how method channel works

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8rb8aincy657r11pbsfc.png)

**Let's start by creating a flutter app**
Clear Flutter Starter app and create a new dart file home.dart

```
import 'package:contact_specific/home.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}
```


**Setting up Method Channel on iOS With Swift**

To receive method calls on iOS, it is necessary to have the channel's name and a closure. The channel's name serves as an identifier for the channel and must be consistent across iOS, Flutter, and Android. When a method is invoked on the MethodChannel from the Dart side, the Flutter Platform Engine will call the assigned closure. Within this closure, the name and parameters of the invoked method can be retrieved and consumed. This mechanism enables seamless communication between the Dart and native portions of the application.

To implement communication between the Dart and native code in iOS, it is necessary to open the `AppDelegate.swift` file located in the `iOS` folder. If you are not familiar with Swift or Kotlin, there is no need to worry as you can easily find the necessary information on Stack Overflow. The key requirement is to establish a channel of communication between the two codebases. With this in mind, you can proceed to implement the required functionality.

Add the following imports in `AppDelegate.swift` file.

```
import ContactsUI
import Foundation
```

These are two import statements in Swift, which bring in the functionality of two framework libraries.

1. `import ContactsUI`: This statement imports the Contacts User Interface framework, which provides UI components for displaying contact information and allows users to pick and select contacts.

2. `import Foundation`: This statement imports the Foundation framework, which provides a base layer of functionality for iOS and macOS apps, including data types, collections, and utility classes for networking, file systems, and more.

Before `AppDelegate` class add the following class

```
class ContactPickerDelegate: NSObject, CNContactPickerDelegate {
    public var onSelectContact: (CNContact) -> Void
    public var onCancel: () -> Void

    init(onSelectContact: @escaping (CNContact) -> Void,
        onCancel: @escaping () -> Void) {
        self.onSelectContact = onSelectContact
        self.onCancel = onCancel
        super.init()
    }
    
    func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
        picker.presentingViewController?.dismiss(animated: true, completion: nil)
        
        onSelectContact(contact)
    }
    
    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
        picker.presentingViewController?.dismiss(animated: true, completion: nil)
        onCancel()
    }
}
```

This is a Swift class `ContactPickerDelegate`, which conforms to the `CNContactPickerDelegate` protocol. The purpose of this class is to handle the selection and cancellation events when a user interacts with the native contact picker view.

1. `onSelectContact`: This is a closure that takes a `CNContact` object as a parameter and is executed when a user selects a contact from the picker view.

2. `onCancel`: This is a closure that is executed when a user cancels the contact picker view.

The class has two initializers which takes two closures as parameters, `onSelectContact` and `onCancel`, and stores them as class properties.

The class implements two delegate methods of the `CNContactPickerDelegate` protocol: 

1. `contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact)`: This method is called when a user selects a contact from the picker view. It dismisses the picker view and calls the `onSelectContact` closure with the selected `CNContact` object.

2. `contactPickerDidCancel(_ picker: CNContactPickerViewController)`: This method is called when a user cancels the picker view. It dismisses the picker view and calls the `onCancel` closure.

Next, add the following code above `GeneratedPluginRegistrant.register(with: self)` inside `application()`:

```
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let contactChannel = FlutterMethodChannel(name: "com.prosper.specific", binaryMessenger: controller.binaryMessenger)

contactChannel.setMethodCallHandler({
    (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

    guard call.method == "getAContact" else {
        result(FlutterMethodNotImplemented)
        return
    }
    self.getAContact(withResult: result)
})
```

Here’s what the code above does:

This code creates an instance of `FlutterMethodChannel` class and sets its name to "com.prosper.specific". `FlutterMethodChannel` class is used to communicate between the Flutter framework (in Dart) and native platform code. The `binaryMessenger` property of the `FlutterMethodChannel` is set to the `binaryMessenger` property of a `FlutterViewController` object, which allows the channel to send messages between the Flutter and native parts of the application.

The code then sets the `setMethodCallHandler` method of the `contactChannel` object. This method takes a closure as a parameter which is called whenever a method call is received from the Flutter side. In the closure, it checks if the method received is equal to "getAContact". If the method received is not "getAContact", the closure returns a `FlutterMethodNotImplemented` result. If the method received is "getAContact", the closure calls a function `getAContact` with the `result` parameter.

What we need to do now is to display the contacts picker when the `getAContact` message is received, so let's add this to our class:

Add the following code below `application()`

```
var contactPickerDelegate: ContactPickerDelegate?
private func getAContact(withResult result: @escaping FlutterResult) {
    let contactPicker = CNContactPickerViewController()
    contactPickerDelegate = ContactPickerDelegate(onSelectContact: { contact in
        result(contact.phoneNumbers[0].value.stringValue)
        self.contactPickerDelegate = nil
    },
    onCancel: {
        result(nil)
        self.contactPickerDelegate = nil
    })
    contactPicker.delegate = contactPickerDelegate
    let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
    let rootViewController = keyWindow?.rootViewController
    DispatchQueue.main.async {
        rootViewController?.present(contactPicker, animated: true)
    }
}
```

The code sets up the contact picker view controller and its delegate, `ContactPickerDelegate`. The method `getAContact` is called when the Flutter side sends a message through the MethodChannel with the method name "getAContact". When this method is called, the contact picker view controller is presented to the user.

The delegate, `ContactPickerDelegate`, handles the user's interaction with the contact picker. When a user selects a contact, the `onSelectContact` closure is executed, and the phone number of the selected contact is returned as the result. If the user cancels the selection, the `onCancel` closure is executed, and `nil` is returned as the result.

The iOS part is done!

**Setting up the Method Channel on Flutter**

Back to home.dart file, add the following import.

```
import 'package:flutter/services.dart';
```
below `HomaPage` class add the following class,

```
class ContactPicker {
  static const MethodChannel _channel = MethodChannel('com.prosper.specific');
  static Future<String> getAContact() async {
    final String contact = await _channel.invokeMethod('getAContact');
    return contact;
  }
}
```
This code defines a Dart class named `ContactPicker`. The class contains a static MethodChannel instance named `_channel` with a hardcoded identifier of 'com.prosper.specific'.

The class also contains a static method named `getAContact` that retrieves a contact information from the native platform (iOS) using the invokeMethod function on the _channel instance.

The `invokeMethod` function is used to call a native platform code from Dart and the string argument "getAContact" represents the name of the method that needs to be executed in the native platform. The function returns the contact information as a string, which is the result of the method call.

Add the following function inside the `HomePage` class just after `build`

```
_getAContact() async {
    String contact;
    try {
      contact = await ContactPicker.getAContact();
    } on PlatformException {
      contact = 'Failed to get contact.';
    }
    if (!mounted) return;
    setState(() {
      _contact = contact;
    });
  }
}
```

This code defines an asynchronous method named _getAContact. The method retrieves a contact information using the getAContact method from the ContactPicker class.

The method uses a try-catch block to handle exceptions that may occur during the retrieval of the contact information. If the getAContact method throws a PlatformException, the catch block sets the contact variable to a string "Failed to get contact.".

After retrieving the contact information, the method checks if the widget is still mounted using the mounted property. If the widget is not mounted, the method returns immediately.

Finally, the method uses the setState method to update the _contact variable with the retrieved contact information. The setState method is used to trigger a rebuild of the widget's UI.

In the `HomePage` class define the following variable for storing contact data.

```
String _contact = 'Unknown';
```

Finally in your `build` function return the following code.

```
Scaffold(
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        MaterialButton(
          color: Colors.blue,
          textColor: Colors.white,
          child: const Text('Pick a contact'),
          onPressed: () => _getAContact(),
        ),
        const SizedBox(
          height: 10,
        ),
        Text(_contact)
      ],
    ),
  ),
);
```

This code calls `_getContact` function when the user presses the button, and displays the value of `_contact` variable, which have the contact information.

You may need to stop your app and run again for the function to run properly.

You may find the full code in our GitHub repo [here](https://github.com/FlutterTanzania/native-conctact-picker-ios)

In conclusion, this article has shown how to access the user's contacts using the native contact picker in an iOS app developed using Flutter. This approach uses MethodChannel to communicate between the Dart part and the native part of the app. The channel's name and a closure are used to receive method calls on iOS. The Flutter Platform Engine calls the closure when a method on the Method Channel is invoked from the Dart side. The code example demonstrated how to display the contacts picker when the `getAContact` message is received, retrieve the selected contact, and pass it back to the Dart side of the app. This approach provides a professional and native experience for accessing user contacts in Flutter-based iOS apps.