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.
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:
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) {}
}
}
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);
}
}
}
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:
export type OnErrorEvent = {
errorMessage: string;
};
export type OnSuccessEvent = {
successMessage: string;
};
export type MyModuleViewProps = {
// the module view props
};
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} />;
}
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.