# In-Depth Guide to Work with Platform Channels by Integrating 3rd Party SDK: Android

This article is a continuation of [`In-Depth Guide to work with platform channels by integrating 3rd Party SDK: IOS`](https://techblog.geekyants.com/in-depth-guide-to-work-with-platform-channels-by-integrating-3rd-party-sdk-ios), where we learned how `MethodChannel` and `EventChannel` works. Also, we integrated `Comet Chat` SDK with our IOS app and created streams and method calls to invoke the methods defined on the native side. 

Here, we will integrate the same SDK on the Android side and see how we can transfer data between Dart and Android using our Platform channels.


## Let's get started

In Android, the implementation would be similar to that in IOS. Here we will be using `MethodChannel` and `EventChannel` Class to implement our Platform channels. So first let's do the setup for `Comet Chat` and then with some examples we will see the implementation of the channels.

Open the `example/android` folder with Android Studio. For writing native android code, I would suggest using Android Studio IDE as that will make it easier to debug code, also we can access the definitions of classes and interfaces without using any external plugin or package. Inside the IDE on the left side, you can see one app folder that contains the example/android app code and another `flutter_comet_chat_sdk` that has the android plugin code. So if we go to -

**flutter_comet_chat_sdk -> java -> com.example.flutter_comet_chat_sdk**

There we can find the class `FlutterCometChatSdkPlugin.java` file. In this file, we will implement our platform channel. To set up the `Comet Chat` SDK, in the root directory `android/build.gradle` add the Gradle dependencies.

```
. . .
rootProject.allprojects {
    repositories {
        google()
        mavenCentral()

        maven {
        url "https://dl.cloudsmith.io/public/cometchat/cometchat-pro-android/maven/"
        }
    }
}

. . .

android {
    compileSdkVersion 30

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        minSdkVersion 21
    }
}
. . .
. . .


dependencies {
    implementation 'com.cometchat:pro-android-chat-sdk:3.0.4'
}

```

Since the `minSdkVersion` required for `Comet Chat` is 21 we need to add that in our `example/android/app/build.gradle` also.

```
  defaultConfig {
        applicationId "com.example.flutter_comet_chat_sdk_example"
        minSdkVersion 21
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
```

Everything is set, now sync the project with `Gradle` files. 
**File -> Sync Project with Gradle Files.**

Our setup is ready, you can run the app on an android device or emulator. Now, we will use some methods of `Comet Chat` SDK to see the implementation of how the `Platform` channel works.

### Creating MethodChannel

To define our `MethodChannel` we will create a class `MethodChannelHandler` and for `EventChannel`, `EventChannelHelper`. And in `FlutterCometChatSdkPlugin` class we will register our `PlatformChannels`. As we did in IOS, here also `FlutterCometChatSdkPlugin` class would be implementing `FlutterPlugin` interface. It provides a method to set up and register the plugin. 

In android we have to override two methods - `onAttachedToEngine` and `onDetachedFromEngine`.
Then, `onAttachedToEngine` is invoked when `FlutterPlugin` is added to an instance of `FlutterEngine`. And if the `FlutterPlugin` is removed or `FlutterEngine` is destroyed then `onDetachedFromEngine` is invoked.

For registering the `MethodChannel` we need the `BinaryMessenger` that is provided by `onAttachedToEngine`, and `FlutterPlugin.FlutterPluginBinding`. The `BinaryMessenger` is used to communicate with Dart code through the specific `EventChannel` or `MethodChannel`. So, in the `onAttachedToEngine` method, we will define our `PlatformChannel` and in `onDetachedFromEngine`, we will set the method channel to nil.

```
package com.example.flutter_comet_chat_sdk;

import android.content.Context;

import androidx.annotation.NonNull;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;

/** FlutterCometChatSdkPlugin */
public class FlutterCometChatSdkPlugin implements FlutterPlugin {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private MethodChannel channel;
  private EventChannelHelper loginEventTrigger;
  private EventChannelHelper loginEventListener;

  @Override
  public void onAttachedToEngine(@NonNull FlutterPlugin.FlutterPluginBinding flutterPluginBinding) {
    setupChannels(flutterPluginBinding.getBinaryMessenger(), flutterPluginBinding.getApplicationContext());
  }


  private void setupChannels(BinaryMessenger messenger, Context context) {
    channel = new MethodChannel(messenger, "plugins.flutter.io/comet_chat_dart");
    loginEventTrigger = new EventChannelHelper(messenger, "plugins.flutter.io/login_event_trigger");
    loginEventListener = new EventChannelHelper(messenger, "plugins.flutter.io/login_event_listener");

    MethodChannelHandler methodChannelHandler = new MethodChannelHandler(context, loginEventTrigger,loginEventListener);
    channel.setMethodCallHandler(methodChannelHandler);
  }


  @Override
  public void onDetachedFromEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }
}

```

Here, we have registered our event trigger and event listeners along with the method channel. Let's create a handler for the `MethodChannel`.

Our `MethodChannelHandler` class will implement interface `MethodChannel.MethodCallHandler`. This interface provider method `onMethodCall` that will be used to receive method call from Flutter.
In the `onMethodCall` function, we will add the method call for our initialize `Comet Chat` SDK method. Also, in the same class, we have defined our initialize method and we are sending results back to Flutter using the `MethodChannel.Result`.

```
public class MethodChannelHandler implements MethodChannel.MethodCallHandler {
    private Context applicationContext;
    private EventChannelHelper loginEventTrigger;
    private EventChannelHelper loginEventListener;

    private LoginLogoutHelper loginHandler;

    private GetValue getValue;

    public MethodChannelHandler(Context context, EventChannelHelper loginEventTrigger, EventChannelHelper loginEventListener) {
        this.getValue = new GetValue();

        this.applicationContext = context;
        this.loginEventTrigger = loginEventTrigger;
        this.loginEventListener = loginEventListener;
        this.loginHandler = new LoginLogoutHelper(loginEventTrigger, loginEventListener);
    }

    @Override
    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
        switch (call.method) {
            case "initialize":
                initializeApp(result, getValue.getValue(call, "appId"), getValue.getAppSettings(call, "appSettings"));
                break;
            default:
                result.notImplemented();
        }
    }

    private void initializeApp(MethodChannel.Result result, String appID, AppSettings appSettings) {
        CometChat.init(applicationContext, appID, appSettings, new CometChat.CallbackListener<String>() {
            @Override
            public void onSuccess(String successMessage) {
                Log.d("Initilisation", "Initialization completed successfully");
                result.success(true);
            }

            @Override
            public void onError(CometChatException e) {
                Log.d("Initilisation", "Initialization failed with exception: " + e.getMessage());
                result.success(e.toString());
            }
        });
    }
}

```

The class `GetValue` has methods that check if the data coming from the Dart side is null or not for common data types like String, int, Boolean, etc. And for JSON it converts the data into the `Comet Chat` class objects by mapping the keys and values.

In the above code, we have used the `getValue` method that checks if the call argument is null or not, and if not null it gives us the `String` value.
```
public String getValue(@NonNull MethodCall call, String argument) {
        return call.argument(argument) != null ? call.argument(argument).toString() : null;
    }
```

The other method which is `getAppSettings` maps out the values for `AppSettings` and using `AppSettingsBuilder` creates an object of the `AppSettings` class which is then passed to the initialize method. Check [`this`](https://github.com/vishalsh2299/flutter_comet_chat_sdk/blob/main/android/src/main/java/com/example/flutter_comet_chat_sdk/Helper/GetValue.java) GitHub code for the complete code of GetValue class.

On the Dart side, we can use this method with the `initialize` key and pass arguments with their respective keys. Also as we have seen in the IOS part, we have to create `AppSettings` class on the Dart side too. For doing that we will create an `AppSettingsBuilder` class that will set the values of `AppSettings` class attributes and then the build method will return the object of `AppSettings` class which we are passing to the native side.

```
init(String appId, AppSettings appSettings,
      CallbackListener callbackListener) async {
    assert(appId != '');
    assert(appSettings.region != '');

    try {
      dynamic res = await FlutterCometChatDart.channel.invokeMethod(
          'initialize', {'appId': appId, 'appSettings': appSettings.toJson()});

      if (res == true) {
        callbackListener.onSuccess(true);
      } else {
        callbackListener.onError(res);
      }
    } on PlatformException catch (e) {
      throw FlutterCometChatDart.convertException(e);
    }
  }
```

### Creating EventChannel

We will create an `EventChannelHelper` class that will handle all our `EventChannels`. In this class first, we will create an object of `EventChannel.EventSink`. Using that object we can access `error` and `success` methods. The `error` method consumes an error event with params `error code`, `error message`, and an object of error details. On the other hand, the `success` method takes a success event object.

After that, we will define a `Handler` that will be attached to the main thread of the application using `Looper.getMainLooper()`. Using the `post` method we will add the `Runnable` (Event sink error or success in our case) to the message queue, which will allow us to send and process `Message` and `Runnable`.

Then in the `EventChannelHelper` constructor, we will create an object of `EventChannel` class (used to communicate with Flutter using asynchronous event streams). That `EventChannel` class will take the binary messenger for event encoding and the name of the specific `EventChannel` we want to set up. Once the object is created we will use `setStreamHandler` to register a stream handler on our `EventChannel`.

The `setStreamHandler` will provide `EventChannel.StreamHandler` which will give two methods `onListen(Object, EventChannel.EventSink)` and `onCancel(Object)`.  Under `onListen `we will set the event in the event sink and in `onCancel` we will deregister or set the event sink to nil.
The `synchronized` keyword is used to make sure that only one thread at a time uses the `synchronized` methods. The synchronized blocks will be synchronized on the same object i.e., object of `EventSink` interface.

```
public class EventChannelHelper {
    public Handler handler;
    private EventChannel.EventSink eventSink;

    public EventChannelHelper(BinaryMessenger messenger, String id) {
        handler = new Handler(Looper.getMainLooper());
        EventChannel eventChannel = new EventChannel(messenger, id);

        eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(final Object arguments, final EventChannel.EventSink eventSink) {
                synchronized (EventChannelHelper.this) {
                    EventChannelHelper.this.eventSink = eventSink;
                }
            }

            @Override
            public void onCancel(final Object arguments) {
                synchronized (EventChannelHelper.this) {
                    eventSink = null;
                }
            }
        });
    }

    public synchronized void error(String errorCode, String errorMessage, Object errorDetails) {
        if (eventSink == null)
            return;
        handler.post(() -> eventSink.error(errorCode, errorMessage, errorDetails));
    }

    public synchronized void success(Object event) {
        if (eventSink == null)
            return;
        handler.post(() -> eventSink.success(event));
    }
}

```

Now our `Event` Channel is set up, let's implement the `Comet Chat` login method to see how it works.


### Transfering data through EventChannel

First, in the `MethodChannelHandler` class, we will define our method call for the login method.

```
. . .
 @Override
    public void onMethodCall(MethodCall call, MethodChannel.Result result) {
        switch (call.method) {
            case "initialize":
                initializeApp(result, getValue.getValue(call, "appId"), getValue.getAppSettings(call, "appSettings"));
                break;
            case "login":
                loginHandler.selectLoginMethod(getValue.getValue(call, "authToken"), getValue.getValue(call, "authKey"),
                        getValue.getValue(call, "UID"));
                result.success("Success");
                break;
. . .
```

Here the `selectLoginMethod` will decide whether the user wants to log in using `authKey` or `authToken`. (The authKey and authToken can be generated by creating an app on [`Comet Chat Pro website`](https://app.cometchat.com/app/20568047e8d91d52/quick-start), use v3 version for creating the app as we are using that as a reference in this project).

In this demonstration, I will use the login method with `authToken` to show the flow for transferring data through our event channel. For full code, you can refer to [`this repo`](https://github.com/vishalsh2299/flutter_comet_chat_sdk).

We will create a class `LoginLogoutHelper` where we will define the authentication methods for `Comet Chat` SDK. The `LoginLogoutHelper` class will take `EventChannelHelper` objects that will be used to pass data to the streams.

```
public class LoginLogoutHelper {
    private String UID;
    private String authKey;
    private String authToken;
    private String uniqueLoginListenerID;
    private static final String TAG = "CometChat";
    private EventChannelHelper loginEventTrigger;
    private EventChannelHelper loginEventListener;

    public LoginLogoutHelper(EventChannelHelper loginEventTrigger, EventChannelHelper loginEventListener) {
        this.loginEventTrigger = loginEventTrigger;
        this.loginEventListener = loginEventListener;
    }
. . .
```

Now we will define our `login` method. In that, we will create a `HashMap` variable that will store the key and value based on success or error. Under the `onSuccess`, we are getting the `User` object that we will convert to JSON string and pass to the `success` method of our `EventChannelHelper` class. In the `onError`, we will pass the error object to the event stream.

```
 public void loginWithAuthToken() {
        Map<String, Object> map = new HashMap<>();

        CometChat.login(authToken, new CometChat.CallbackListener<User>() {
            @Override
            public void onSuccess(User user) {
                Log.d(TAG, "Login Successful : " + user);
                map.put("loginSuccess", user.toJson().toString());
                loginEventTrigger.success(map);
            }

            @Override
            public void onError(CometChatException e) {
                Log.d(TAG, "Login failed with exception: " + e.getMessage());
                map.put("loginFailure", e.toString());
                loginEventTrigger.error(e.getCode(), e.getMessage(), map);
            }
        });
    }
```

On the Dart side, we will use these keys to fetch the User data and convert it using the `fromJson` method defined on the `User` class.

Defining `EventChannel` and `StreamSubscription` on the Dart side.
```
static const EventChannel loginEventTrigger =
      EventChannel('plugins.flutter.io/login_event_trigger');

late StreamSubscription _loginEventsubscription;
```
Subscribing to the event stream to receive data.
```
Stream<dynamic> loginEventTrigger() {
    return FlutterCometChatDart.loginEventTrigger.receiveBroadcastStream();
  }
```

Defining the login method with `CallbackListener` where we are decoding the JSON string coming in the event stream and serializing it to the object on the Dart side.

```
login(
      {String? authKey,
      String? uid,
      String? authToken,
      required CallbackListener callbackListener}) {
    assert((uid != '' && authKey != '') || (authToken != ''));

    _auth.login(authKey: authKey, uid: uid, authToken: authToken);

    _loginEventsubscription = _auth.loginEventTrigger().listen((event) {
      if (event.containsKey(CometConstantsKeys.LOGIN_SUCCESS)) {
        var data = jsonDecode(event[CometConstantsKeys.LOGIN_SUCCESS]);
        callbackListener.onSuccess(User.fromJson(data));
      }
      if (event.containsKey(CometConstantsKeys.LOGIN_FAILURE)) {
        callbackListener.onError(event);
      }
    });

    cancelSubscription(_loginEventsubscription);
  }
``` 

Finally, we have completed the setup for `Platform` channels in Android and IOS both. In summary, we have learned the implementation of `Platform` channels by integrating `Comet Chat` SDK. Also, we explored how we can pass params in our method calls and share stream data between Dart and native sides. Then we structured our app in such a way that we can use the implemented SDK as a plugin in our example app or any other Flutter app.

### What to do next?

For practicing the concept of Platform channels you can try implementing other methods of `Comet Chat` SDK. Also, you can explore other concepts such as [`Platform Views`](https://github.com/flutter/flutter/wiki/Android-Platform-Views) to implement native UI views in Flutter.

### References
- [`Github code link`](https://github.com/vishalsh2299/flutter_comet_chat_sdk)
- [`Reference to integrate 3rd part android SDK`](https://github.com/flutter/flutter/issues/16259)
- [`FlutterPlugin Docs`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html)
- [`Platform channel Flutter docs`](https://docs.flutter.dev/development/platform-integration/platform-channels#architecture)
- [`Docs for creating a plugin in Flutter`](https://docs.flutter.dev/development/packages-and-plugins/developing-packages)
- [`Docs for classes and interface used on native side to define platform channel`](https://api.flutter.dev/objcdoc/Protocols/FlutterPlugin.html)
- [`Comet Chat docs for classes used for demonstration in this article`](https://docs.cometchat.io/ios/v2.0/swiftdocs/Classes/AppSettings/AppSettingsBuilder.html)
- [`Comet Chat android docs`](https://www.cometchat.com/docs/v3/android-chat-sdk/overview)





