- Print
- DarkLight
Tickets SDK
Tickets SDK React Native (iOS)
Create a new .swift file, the name in this guide is "TicketsSdkViewController" but call it whatever you like
In React Native iOS there are two ways to present the Tickets SDK which are Embedded (inside a React Native View) or a Modal above the current screen, discussed later on.
If it is the first time making a .swift file, Xcode will prompt you to make a Bridging-Header.h file, press "Create Bridging Header"
A Bridging-Header.h will expose Objective-C public headers to Swift
In [PROJECT-NAME]-Bridging-Header.h file add
#import <React/RCTBridgeModule.h>
In your TicketsViewController.swift create a class and extend UIViewController like below (you can remove the auto-generated Foundation import):
class TicketsViewController: UIViewController {
}
import TicketmasterAuthentication and TicketmasterTickets which should have been made available after adding the Ticketmaster Frameworks
import TicketmasterAuthentication
import TicketmasterTickets
Override a viewDidLoad function
override func viewDidLoad() {
super.viewDidLoad()
}
Presenting the TicketsSDK
In React Native iOS there are two ways to present the Tickets SDK which are Embedded (inside a React Native View) or a Modal above the current screen
Embedded View Approach
Declare TMTicketsView at the top of the class
class TicketsSdkViewController: UIViewController {
var ticketsView: TMTicketsView!
In viewDidLoad() under super.viewDidLoad(), present the Embedded Tickets SDK View Controller by pasting in the following code
TMTickets.shared.configure {
print(" - Tickets SDK Configured")
self.ticketsView = TMTicketsView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: self.view.frame.height))
self.view.addSubview(self.ticketsView)
TMTickets.shared.start(ticketsView: self.ticketsView)
} failure: { error in
print(" - Tickets SDK Configuration Error: \(error.localizedDescription)")
}
Modal View Approach
In viewDidLoad() under super.viewDidLoad(), present the Embedded Tickets SDK View Controller by pasting in the following code
TMTickets.shared.configure {
print(" - Tickets SDK Configured")
let ticketsVC = TMTicketsViewController()
ticketsVC.modalPresentationStyle = .fullScreen
self.present(ticketsVC, animated: false, completion: nil)
} failure: { error in
print(" - Tickets SDK Configuration Error: \(error.localizedDescription)")
}
You can remove the line which sets the modal to fullscreen if you want a modal in which user can swipe down to dismiss
Exposing Native View to React Native
Create a new .m file, the name in this guide is "RNTTicketsSdkViewManager" you can change the name but note prefixing with "RNT" and appending "Manager" are common conventions and you could run into issues if you don't follow them.
Paste in the below inside RNTTicketsSdkViewManager.m:
#import <React/RCTViewManager.h>
// Below import is eeded to use Swift files in Objective-C files i.e. TicketsSdkViewController
#import “[PROJECT-NAME]-Swift.h"
@interface RNTTicketsSdkViewManager : RCTViewManager
@end
@implementation RNTTicketsSdkViewManager
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
+ (BOOL)requiresMainQueueSetup {
return true;
}
RCT_EXPORT_MODULE(RNTTicketsSdkView)
- (UIView *)view
{
TicketsSdkViewController *vc = [[TicketsSdkViewController alloc] init];
return vc.view;
}
@end
Show the View in React Native
In your React Native project, create a file called TicketsSdk.tsx and paste in the below:
import {requireNativeComponent} from 'react-native'
module.exports = requireNativeComponent('RNTTicketsSdkView')
Embedded View approach
Create a file called MyEvents.tsx and paste in the below:
import React, {useCallback, useState} from 'react'
import {useFocusEffect} from '@react-navigation/native'
import TicketsSdk from '../components/TicketsSdk'
const MyTickets = () => {
return <TicketsSdk style={{flex: 1}} />
}
export default MyTickets
Modal View Approach
import React, {useCallback, useState} from 'react'
import {View, Text, TouchableOpacity} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import TicketsSdk from '../components/TicketsSdk'
const MyTickets = () => {
const [initialFocus, setInitialFocus] = useState(false)
const [showTicketsSdk, setShowTicketsSdk] = useState(false)
useFocusEffect(
useCallback(() => {
// Show SDK's when screen focuses
setInitialFocus(true)
// unmount the RN components after the Native SDK's are launched,
// so that the SDK can be reinitialized via RN's button onPress after the SDK's Native close button is pressed
setTimeout(() => {
setInitialFocus(false)
}, 500)
console.log('useFocusEffect mount called')
return () => {
setInitialFocus(false)
console.log('useFocusEffect unmount called')
}
}, []),
)
const onShowTicketsSdk = () => {
setShowTicketsSdk(true)
setTimeout(() => {
setShowTicketsSdk(false)
}, 500)
}
return (
<View style={{flex: 1}}>
<View style={{margin: 4, width: 170, alignItems: 'center'}}>
<Text>Tickets SDK</Text>
</View>
<TouchableOpacity
onPress={() => onShowTicketsSdk()}
style={{
backgroundColor: '#07a',
width: 170,
height: 30,
borderRadius: 4,
alignItems: 'center',
margin: 4,
justifyContent: 'center',
}}>
<Text>Show Tickets SDK:</Text>
</TouchableOpacity>
{(initialFocus || showTicketsSdk) && (
// Wrapper the SDK modal view in an additional view prevents the main view from being unresponsive after the SDK is presented
<View>
<TicketsSdk />
</View>
)}
</View>
)
}
export default MyTickets
React Navigation (Embedded View Approach)
If you are using React Navigation you will have varying Header and Bottom Tab heights. in this case you can use height instead of flex and do the below:
const [initialFocus, setInitialFocus] = useState(true);
useEffect(() => {
// Initially, the altered Bottom Tabs View frame height is not available for Native code on iOS, this becomes available after a rerender.
// If needed an additional delay can be used before setting to false
setInitialFocus(false);
}, []);
return <>{!initialFocus && <TicketsSdkEmbedded style={{height: '90%'}} />}</>;
You can then set a height on your bottom tabs so that height of the embedded view takes into account the height of your bottom tabs:
const BottomTab = createBottomTabNavigator()
...
<BottomTab.Navigator
screenOptions={{
tabBarStyle: {
...
height: '10%',
...
},
}}>
Another approach would be to set the height in the .swift file instead of React Native. In this case, keep the changes to BottomTab.Navigator but instead of the solution involving initial focus include an extra line in TicketsSdkViewController.swift:
self.ticketsView = TMTicketsView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: self.view.frame.height))
// extra line below
self.ticketsView.frame.size.height *= 0.9
// extra line above
self.view.addSubview(self.ticketsView)
Analytics
To setup delegates in React Native iOS see: https://ignite.ticketmaster.com/docs/react-native-accounts-information
After setting up Delegates in your Accounts SDK NSObject/RCTEventEmitter class you can follow: https://ignite.ticketmaster.com/v1/docs/analytics-ios-1
React Native Tickets Page (Android)
Create a custom view that extends FrameLayout. content of this view can be anything you want to render
class CustomView(context: Context) : FrameLayout(context) {
init {
// set padding and background color
setPadding(16,16,16,16)
setBackgroundColor(Color.parseColor("#5FD3F3"))
// add default text view
addView(TextView(context).apply {
text = "Welcome to Retail SDK with React Native."
})
}
}
Create a fragment with FrameLayout
class TicketsFragment : Fragment() {
private val mProgressDialog: AlertDialog by lazy {
AlertDialog
.Builder(this@MyFragment.requireActivity())
.setView(LayoutInflater.from(this@MyFragment.requireActivity()).inflate(R.layout.layout_loading_view, null, false))
.setCancelable(false)
.create()
.apply {
setCanceledOnTouchOutside(false)
}
}
private lateinit var customView: CustomView
private fun createTicketsColors(color: Int): TicketsColors =
TicketsColors(
lightColors(primary = Color(color), primaryVariant = Color(color), secondary = Color(color)),
darkColors(primary = Color(color), primaryVariant = Color(color), secondary = Color(color))
)
private suspend fun validateAuthToken(authentication: TMAuthentication): Map<AuthSource, String> {
val tokenMap = mutableMapOf<AuthSource, String>()
AuthSource.values().forEach {
//Validate if there is an active token for the AuthSource, if not it returns null.
authentication.getToken(it)?.let { token ->
tokenMap[it] = token
}
}
return tokenMap
}
private fun launchTicketsView() {
mProgressDialog.dismiss()
//Retrieve an EventFragment
TicketsSDKSingleton.getEventsFragment(requireContext())?.let {
childFragmentManager.beginTransaction().add(R.id.container, it).commit()
}
}
private val resultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
AppCompatActivity.RESULT_OK -> launchTicketsView()
AppCompatActivity.RESULT_CANCELED -> {
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mProgressDialog.show()
val coroutineScope = CoroutineScope(Dispatchers.IO)
coroutineScope.launch {
val authentication = TMAuthentication.Builder()
.apiKey("[API_KEY]")
.clientName("[TEAM_NAME]") // Team name to be displayed
.colors(TMAuthentication.ColorTheme())
.environment(TMXDeploymentEnvironment.Production) // Environment that the SDK will use. Default is Production
.region(TMXDeploymentRegion.US) // Region that the SDK will use. Default is US
.build(this@MyFragment.requireActivity())
val tokenMap = validateAuthToken(authentication)
TicketsSDKClient
.Builder()
.authenticationSDKClient(authentication) //Authentication object
//Optional value to define the colors for the Tickets page
.colors(createTicketsColors(android.graphics.Color.parseColor("#231F20")))
//Function that generates a TicketsSDKClient object
.build(this@MyFragment.requireActivity())
.apply {
//After creating the TicketsSDKClient object, add it into the TicketsSDKSingleton
TicketsSDKSingleton.setTicketsSdkClient(this)
//Validate if there is an active token.
if (tokenMap.isNotEmpty()) {
//If there is an active token, it launches the event fragment
launchTicketsView()
} else {
//If there is no active token, it launches a login intent. Launch an ActivityForResult, if result
//is RESULT_OK, there is an active token to be retrieved.
val activity = this@MyFragment.activity // Use activity property instead of requireActivity()
//It is important to null check activity because
//The method getLoginIntent(context: FragmentActivity) requires a non-null FragmentActivity as its parameter
if (activity != null) {
resultLauncher.launch(TicketsSDKSingleton.getLoginIntent(activity))
} else {
// Handle the case where the activity is null
Log.e("MyFragment", "Activity is null, unable to launch login intent.")
}
}
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
customView = CustomView(requireNotNull(context))
return customView // this CustomView could be any view that you want to render
}
}
Create a ViewManager, this is used to bring the fragment to react native side.
class TicketsViewManager(
private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {
private var propWidth: Int? = null
private var propHeight: Int? = null
override fun getName() = REACT_CLASS
/**
* Return a FrameLayout which will later hold the Fragment
*/
override fun createViewInstance(reactContext: ThemedReactContext) =
FrameLayout(reactContext)
/**
* Map the "create" command to an integer
*/
override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)
/**
* Handle "create" command (called from JS) and call createFragment method
*/
override fun receiveCommand(
root: FrameLayout,
commandId: String,
args: ReadableArray?
) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)
when (commandId.toInt()) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
}
}
@ReactPropGroup(names = ["width", "height"], customType = "Style")
fun setStyle(view: FrameLayout, index: Int, value: Int) {
if (index == 0) propWidth = value
if (index == 1) propHeight = value
}
/**
* Replace your React Native view with a custom fragment
*/
fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)
val myFragment = TicketsFragment()
val activity = reactContext.currentActivity as FragmentActivity
activity.supportFragmentManager
.beginTransaction()
.replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
.commit()
}
fun setupLayout(view: View) {
Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}
/**
* Layout all children properly
*/
private fun manuallyLayoutChildren(view: View) {
// propWidth and propHeight coming from react-native props
val width = requireNotNull(propWidth)
val height = requireNotNull(propHeight)
view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
view.layout(0, 0, width, height)
}
companion object {
private const val REACT_CLASS = "TicketsViewManager"
private const val COMMAND_CREATE = 1
}
}
Create a React Package and add your view manager
class MyPackage : ReactPackage {
override fun createViewManagers(
reactContext: ReactApplicationContext
) = listOf(TicketsViewManager(reactContext))
}
Register the Package in your MainApplication.kt
override fun getPackages() = PackageList(this).packages.apply {
add(MyPackage())
}
Setup Javascript module
import {requireNativeComponent} from 'react-native'
export const TicketsViewManager = requireNativeComponent('TicketsViewManager')
Create a custom View
import React, {useEffect, useRef} from 'react';
import {
PixelRatio,
UIManager,
findNodeHandle,
} from 'react-native';
import {TicketsViewManager} from './tickets-view-manager';
const createFragment = viewId =>
UIManager.dispatchViewManagerCommand(
viewId,
// we are calling the 'create' command
UIManager.TicketsViewManager.Commands.create.toString(),
[viewId],
);
export const TicketsView = () => {
const ref = useRef(null);
useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId);
}, []);
return (
<TicketsViewManager
style={{
// converts dpi to px, provide desired height
height: PixelRatio.getPixelSizeForLayoutSize(200),
// converts dpi to px, provide desired width
width: PixelRatio.getPixelSizeForLayoutSize(200),
}}
ref={ref}
/>
);
};