androidreact-nativeexpobraintree

Starting custom activity in React Native with Expo


I am currently working at a React Native application built with Expo and I wish to integrate the Braintree SDK Drop-in UI component to create a check-out section. Unfortunately, I can not figure out a way to start on button click an activity that opens the native drop-in UI with parameters passed from the React Native application.

I have tried multiple alternatives, first of which was writing an Expo module. I managed to pass data to the Expo module, but I was unable to start an activity from within the Expo module. Another alternative I tried was writing an activity and adding it to the AndroidManifest.xml file, and trying to then start said activity using the startAsyncActivity method from the Expo Intent-Launcher, but I couldn't get that to work either.


Solution

  • I actually managed to solve this by creating a local Expo module (using npx create-expo-module@latest --local), adding a view with a button, and when the button is clicked, I create a new intent using the given context, and start the custom activity. Below you can find some code samples:

    MyModule.kt

    package expo.modules.mymodule
    
    import expo.modules.kotlin.modules.Module
    import expo.modules.kotlin.modules.ModuleDefinition
    
    class MyModule : Module() {
      override fun definition() = ModuleDefinition {
        Name("MyModule")
    
        View(MyModuleView::class) {}
      }
    }
    

    MyModuleView.kt

    package expo.modules.mymodule
    
    import android.util.Log
    import android.content.Context
    import android.content.Intent
    import android.util.TypedValue
    import android.view.Gravity
    import android.widget.Button
    import expo.modules.mymodule.MyActivity
    import expo.modules.kotlin.AppContext
    import expo.modules.kotlin.views.ExpoView
    
    class MyModuleView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
      internal val helloButton = Button(context).also {
        addView(it)
        it.text = "Checkout"
        it.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        it.setOnClickListener {
          val myIntent = Intent(context, MyActivity::class.java)
          context.startActivity(myIntent);
        }
      }
    }
    

    MyActivity.kt

    package expo.modules.mymodule
    
    import android.os.Bundle
    import android.util.Log
    import android.widget.Toast
    import androidx.fragment.app.FragmentActivity
    import com.braintreepayments.api.DropInClient
    import com.braintreepayments.api.DropInListener
    import com.braintreepayments.api.DropInRequest
    import com.braintreepayments.api.DropInResult
    
    import java.lang.Exception
    
    class MyActivity : FragmentActivity(), DropInListener {
        private var dropInClient: DropInClient? = null;
        private var dropInRequest: DropInRequest? = null;
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            dropInRequest = DropInRequest();
            dropInClient = DropInClient(this@MyActivity,"authorization_token");
            dropInClient!!.setListener(this@MyActivity);
        }
    
        override fun onStart() {
            super.onStart()
            dropInClient!!.launchDropIn(dropInRequest)
        }
    
        override fun onDropInSuccess(dropInResult: DropInResult) {
            val token = dropInResult.paymentMethodNonce!!.string;
            Log.d("TestApplication",token);
        }
    
        override fun onDropInFailure(error: Exception) {
            Log.d("TestApplication",error.toString());
        }
    }
    

    Also, in order to integrate the local module in the React Native application, I added the following lines to my package.json:

      "private": true,
      "expo": {
        "autolinking": {
          "nativeModulesDir": "./modules"
        }
      }
    

    In addition, in my modules/mymodule/src file I have a types file, a file containing the React Native view definition and an index.ts file. Below you can find some code samples:

    MyModule.types.ts

    export type OnErrorEvent = {
      errorMessage: string;
    };
    
    export type OnSuccessEvent = {
      successMessage: string;
    };
    
    export type MyModuleViewProps = {
      // the module view props
    };
    

    MyModuleView.ts

    import * as React from "react";
    import { ViewProps } from "react-native";
    import { requireNativeViewManager } from "expo-modules-core";
    
    import { MyModuleViewProps } from "./MyModule.types";
    
    export type Props = ViewProps & MyModuleViewProps;
    
    const NativeView: React.ComponentType<Props> =
      requireNativeViewManager("MyModule");
    
    export default function MyModuleView(props: Props) {
      return <NativeView {...props} />;
    }
    

    index.ts

    export {
      default as MyModuleView,
      Props as MyModuleViewProps,
    } from "./MyModuleView";
    
    

    In the React Native application I use the component and the declared types by importing them like this:

    import { BraintreeModuleView } from "<relative path to modules folder>/modules/mymodule/src";
    import {
      OnErrorEvent,
      OnSuccessEvent,
    } from "<relative path to modules folder>/modules/mymodule/src/BraintreeModule.types";
    

    You may pass props to this component as you see fit, as you would with any regular React component.