1 package io.branch.rnbranch; 2 3 import android.app.Activity; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.IntentFilter; 7 import android.content.BroadcastReceiver; 8 import android.net.Uri; 9 10 import androidx.annotation.Nullable; 11 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 12 import android.util.Log; 13 import android.os.Handler; 14 15 import com.facebook.react.bridge.*; 16 import com.facebook.react.bridge.Promise; 17 import com.facebook.react.modules.core.*; 18 import com.facebook.react.bridge.ReadableMap; 19 20 import io.branch.referral.*; 21 import io.branch.referral.Branch.BranchLinkCreateListener; 22 import io.branch.referral.BuildConfig; 23 import io.branch.referral.util.*; 24 import io.branch.referral.Branch; 25 import io.branch.indexing.*; 26 27 import org.json.*; 28 29 import java.text.ParseException; 30 import java.text.SimpleDateFormat; 31 import java.util.*; 32 33 public class RNBranchModule extends ReactContextBaseJavaModule { 34 public static final String REACT_CLASS = "RNBranch"; 35 public static final String REACT_MODULE_NAME = "RNBranch"; 36 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT = "io.branch.rnbranch.RNBranchModule.onInitSessionFinished"; 37 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT = "branch_universal_object"; 38 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES = "link_properties"; 39 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS = "params"; 40 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR = "error"; 41 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_URI = "uri"; 42 public static final String NATIVE_INIT_SESSION_STARTED_EVENT = "io.branch.rnbranch.RNBranchModule.onInitSessionStarted"; 43 public static final String NATIVE_INIT_SESSION_STARTED_EVENT_URI = "uri"; 44 private static final String RN_INIT_SESSION_SUCCESS_EVENT = "RNBranch.initSessionSuccess"; 45 private static final String RN_INIT_SESSION_ERROR_EVENT = "RNBranch.initSessionError"; 46 private static final String RN_INIT_SESSION_START_EVENT = "RNBranch.initSessionStart"; 47 private static final String INIT_SESSION_SUCCESS = "INIT_SESSION_SUCCESS"; 48 private static final String INIT_SESSION_ERROR = "INIT_SESSION_ERROR"; 49 private static final String INIT_SESSION_START = "INIT_SESSION_START"; 50 51 private static final String STANDARD_EVENT_ADD_TO_CART = "STANDARD_EVENT_ADD_TO_CART"; 52 private static final String STANDARD_EVENT_ADD_TO_WISHLIST = "STANDARD_EVENT_ADD_TO_WISHLIST"; 53 private static final String STANDARD_EVENT_VIEW_CART = "STANDARD_EVENT_VIEW_CART"; 54 private static final String STANDARD_EVENT_INITIATE_PURCHASE = "STANDARD_EVENT_INITIATE_PURCHASE"; 55 private static final String STANDARD_EVENT_ADD_PAYMENT_INFO = "STANDARD_EVENT_ADD_PAYMENT_INFO"; 56 private static final String STANDARD_EVENT_PURCHASE = "STANDARD_EVENT_PURCHASE"; 57 private static final String STANDARD_EVENT_SPEND_CREDITS = "STANDARD_EVENT_SPEND_CREDITS"; 58 private static final String STANDARD_EVENT_VIEW_AD = "STANDARD_EVENT_VIEW_AD"; 59 private static final String STANDARD_EVENT_CLICK_AD = "STANDARD_EVENT_CLICK_AD"; 60 61 private static final String STANDARD_EVENT_SEARCH = "STANDARD_EVENT_SEARCH"; 62 private static final String STANDARD_EVENT_VIEW_ITEM = "STANDARD_EVENT_VIEW_ITEM"; 63 private static final String STANDARD_EVENT_VIEW_ITEMS = "STANDARD_EVENT_VIEW_ITEMS"; 64 private static final String STANDARD_EVENT_RATE = "STANDARD_EVENT_RATE"; 65 private static final String STANDARD_EVENT_SHARE = "STANDARD_EVENT_SHARE"; 66 67 private static final String STANDARD_EVENT_COMPLETE_REGISTRATION = "STANDARD_EVENT_COMPLETE_REGISTRATION"; 68 private static final String STANDARD_EVENT_COMPLETE_TUTORIAL = "STANDARD_EVENT_COMPLETE_TUTORIAL"; 69 private static final String STANDARD_EVENT_ACHIEVE_LEVEL = "STANDARD_EVENT_ACHIEVE_LEVEL"; 70 private static final String STANDARD_EVENT_UNLOCK_ACHIEVEMENT = "STANDARD_EVENT_UNLOCK_ACHIEVEMENT"; 71 private static final String STANDARD_EVENT_INVITE = "STANDARD_EVENT_INVITE"; 72 private static final String STANDARD_EVENT_LOGIN = "STANDARD_EVENT_LOGIN"; 73 private static final String STANDARD_EVENT_RESERVE = "STANDARD_EVENT_RESERVE"; 74 private static final String STANDARD_EVENT_SUBSCRIBE = "STANDARD_EVENT_SUBSCRIBE"; 75 private static final String STANDARD_EVENT_START_TRIAL = "STANDARD_EVENT_START_TRIAL"; 76 77 private static final String IDENT_FIELD_NAME = "ident"; 78 public static final String UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE = "RNBranch::Error::BUONotFound"; 79 public static final String GENERIC_ERROR = "RNBranch::Error"; 80 private static final long AGING_HASH_TTL = 3600000; 81 private static final String PLUGIN_NAME = "ReactNative"; 82 83 private static JSONObject initSessionResult = null; 84 private BroadcastReceiver mInitSessionFinishedEventReceiver = null; 85 private BroadcastReceiver mInitSessionStartedEventReceiver = null; 86 private static Branch.BranchUniversalReferralInitListener initListener = null; 87 88 private static Activity mActivity = null; 89 private static boolean mUseDebug = false; 90 private static boolean mInitialized = false; 91 private static JSONObject mRequestMetadata = new JSONObject(); 92 93 private AgingHash<String, BranchUniversalObject> mUniversalObjectMap = new AgingHash<>(AGING_HASH_TTL); 94 95 private static Branch.BranchReferralInitListener referralInitListener = null; 96 97 public static void getAutoInstance(Context context) { 98 RNBranchConfig config = new RNBranchConfig(context); 99 String branchKey = config.getBranchKey(); 100 String liveKey = config.getLiveKey(); 101 String testKey = config.getTestKey(); 102 boolean useTest = config.getUseTestInstance(); 103 104 Branch.registerPlugin(PLUGIN_NAME, expo.modules.branch.BuildConfig.RNBRANCH_VERSION); 105 106 if (branchKey != null) { 107 Branch.getAutoInstance(context, branchKey); 108 } 109 else if (useTest && testKey != null) { 110 Branch.getAutoInstance(context, testKey); 111 } 112 else if (!useTest && liveKey != null) { 113 Branch.getAutoInstance(context, liveKey); 114 } 115 else { 116 Branch.getAutoInstance(context); 117 } 118 } 119 120 public static void reInitSession(Activity reactActivity) { 121 Branch branch = Branch.getInstance(); 122 Intent intent = reactActivity.getIntent(); 123 if (intent != null) { 124 intent.putExtra("branch_force_new_session", true); 125 notifyJSOfInitSessionStart(reactActivity, intent.getData()); 126 Branch.sessionBuilder(reactActivity).withCallback(referralInitListener).reInit(); 127 } else { 128 Log.w(REACT_CLASS, "reInitSession was called but the Intent is null"); 129 } 130 } 131 132 public static void initSession(final Uri uri, Activity reactActivity, Branch.BranchUniversalReferralInitListener anInitListener) { 133 initListener = anInitListener; 134 initSession(uri, reactActivity); 135 } 136 137 public static void initSession(final Uri uri, Activity reactActivity) { 138 Branch branch = setupBranch(reactActivity.getApplicationContext()); 139 140 mActivity = reactActivity; 141 referralInitListener = new Branch.BranchReferralInitListener(){ 142 143 private Activity mmActivity = null; 144 145 @Override 146 public void onInitFinished(JSONObject referringParams, BranchError error) { 147 // react native currently expects this to never be null 148 if (referringParams == null) { 149 referringParams = new JSONObject(); 150 } 151 152 Log.d(REACT_CLASS, "onInitFinished"); 153 JSONObject result = new JSONObject(); 154 155 try { 156 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams); 157 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error != null ? error.getMessage() : JSONObject.NULL); 158 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, uri != null ? uri.toString() : JSONObject.NULL); 159 } 160 catch (JSONException e) { 161 162 } 163 initSessionResult = result; 164 165 BranchUniversalObject branchUniversalObject = BranchUniversalObject.getReferredBranchUniversalObject(); 166 LinkProperties linkProperties = LinkProperties.getReferredLinkProperties(); 167 168 if (initListener != null) { 169 initListener.onInitFinished(branchUniversalObject, linkProperties, error); 170 } 171 generateLocalBroadcast(referringParams, uri, branchUniversalObject, linkProperties, error); 172 } 173 174 private Branch.BranchReferralInitListener init(Activity activity) { 175 mmActivity = activity; 176 return this; 177 } 178 179 private void generateLocalBroadcast(JSONObject referringParams, 180 Uri uri, 181 BranchUniversalObject branchUniversalObject, 182 LinkProperties linkProperties, 183 BranchError error) { 184 Intent broadcastIntent = new Intent(NATIVE_INIT_SESSION_FINISHED_EVENT); 185 186 if (referringParams != null) { 187 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams.toString()); 188 } 189 190 if (branchUniversalObject != null) { 191 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT, branchUniversalObject); 192 } 193 194 if (linkProperties != null) { 195 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES, linkProperties); 196 } 197 198 if (uri != null) { 199 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, uri.toString()); 200 } 201 202 if (error != null) { 203 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error.getMessage()); 204 } 205 206 LocalBroadcastManager.getInstance(mmActivity).sendBroadcast(broadcastIntent); 207 } 208 }.init(reactActivity); 209 210 notifyJSOfInitSessionStart(reactActivity, uri); 211 Branch.sessionBuilder(reactActivity).withCallback(referralInitListener).withData(uri).init(); 212 } 213 214 /** 215 * Notify JavaScript of init session start. This generates an RNBranch.initSessionStart 216 * event to JS via the RN native event emitter. 217 * @param context a Context for the LocalBroadcastManager 218 * @param uri the URI to include in the notification or null 219 */ 220 private static void notifyJSOfInitSessionStart(Context context, Uri uri) { 221 Intent broadcastIntent = new Intent(NATIVE_INIT_SESSION_STARTED_EVENT); 222 if (uri != null) { 223 broadcastIntent.putExtra(NATIVE_INIT_SESSION_STARTED_EVENT_URI, uri); 224 } 225 226 LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent); 227 Log.d(REACT_CLASS, "Sent session start broadcast for " + uri); 228 } 229 230 public static void setDebug() { 231 mUseDebug = true; 232 } 233 234 public static void setRequestMetadata(String key, String val) { 235 if (key == null) { 236 return; 237 } 238 239 if (mRequestMetadata.has(key) && val == null) { 240 mRequestMetadata.remove(key); 241 } 242 243 try { 244 mRequestMetadata.put(key, val); 245 } catch (JSONException e) { 246 // no-op 247 } 248 } 249 250 public RNBranchModule(ReactApplicationContext reactContext) { 251 super(reactContext); 252 listenForInitSessionEventsToReactNative(reactContext); 253 } 254 255 @javax.annotation.Nullable 256 @Override 257 public Map<String, Object> getConstants() { 258 final Map<String, Object> constants = new HashMap<>(); 259 // RN events transmitted to JS 260 261 constants.put(INIT_SESSION_SUCCESS, RN_INIT_SESSION_SUCCESS_EVENT); 262 constants.put(INIT_SESSION_ERROR, RN_INIT_SESSION_ERROR_EVENT); 263 constants.put(INIT_SESSION_START, RN_INIT_SESSION_START_EVENT); 264 265 // constants for use with BranchEvent 266 267 // Commerce events 268 269 constants.put(STANDARD_EVENT_ADD_TO_CART, BRANCH_STANDARD_EVENT.ADD_TO_CART.getName()); 270 constants.put(STANDARD_EVENT_ADD_TO_WISHLIST, BRANCH_STANDARD_EVENT.ADD_TO_WISHLIST.getName()); 271 constants.put(STANDARD_EVENT_VIEW_CART, BRANCH_STANDARD_EVENT.VIEW_CART.getName()); 272 constants.put(STANDARD_EVENT_INITIATE_PURCHASE, BRANCH_STANDARD_EVENT.INITIATE_PURCHASE.getName()); 273 constants.put(STANDARD_EVENT_ADD_PAYMENT_INFO, BRANCH_STANDARD_EVENT.ADD_PAYMENT_INFO.getName()); 274 constants.put(STANDARD_EVENT_PURCHASE, BRANCH_STANDARD_EVENT.PURCHASE.getName()); 275 constants.put(STANDARD_EVENT_SPEND_CREDITS, BRANCH_STANDARD_EVENT.SPEND_CREDITS.getName()); 276 constants.put(STANDARD_EVENT_VIEW_AD, BRANCH_STANDARD_EVENT.VIEW_AD.getName()); 277 constants.put(STANDARD_EVENT_CLICK_AD, BRANCH_STANDARD_EVENT.CLICK_AD.getName()); 278 279 // Content Events 280 281 constants.put(STANDARD_EVENT_SEARCH, BRANCH_STANDARD_EVENT.SEARCH.getName()); 282 constants.put(STANDARD_EVENT_VIEW_ITEM, BRANCH_STANDARD_EVENT.VIEW_ITEM.getName()); 283 constants.put(STANDARD_EVENT_VIEW_ITEMS , BRANCH_STANDARD_EVENT.VIEW_ITEMS.getName()); 284 constants.put(STANDARD_EVENT_RATE, BRANCH_STANDARD_EVENT.RATE.getName()); 285 constants.put(STANDARD_EVENT_SHARE, BRANCH_STANDARD_EVENT.SHARE.getName()); 286 287 // User Lifecycle Events 288 289 constants.put(STANDARD_EVENT_COMPLETE_REGISTRATION, BRANCH_STANDARD_EVENT.COMPLETE_REGISTRATION.getName()); 290 constants.put(STANDARD_EVENT_COMPLETE_TUTORIAL , BRANCH_STANDARD_EVENT.COMPLETE_TUTORIAL.getName()); 291 constants.put(STANDARD_EVENT_ACHIEVE_LEVEL, BRANCH_STANDARD_EVENT.ACHIEVE_LEVEL.getName()); 292 constants.put(STANDARD_EVENT_UNLOCK_ACHIEVEMENT, BRANCH_STANDARD_EVENT.UNLOCK_ACHIEVEMENT.getName()); 293 constants.put(STANDARD_EVENT_INVITE, BRANCH_STANDARD_EVENT.INVITE.getName()); 294 constants.put(STANDARD_EVENT_LOGIN , BRANCH_STANDARD_EVENT.LOGIN.getName()); 295 constants.put(STANDARD_EVENT_RESERVE, BRANCH_STANDARD_EVENT.RESERVE.getName()); 296 constants.put(STANDARD_EVENT_SUBSCRIBE, BRANCH_STANDARD_EVENT.SUBSCRIBE.getName()); 297 constants.put(STANDARD_EVENT_START_TRIAL, BRANCH_STANDARD_EVENT.START_TRIAL.getName()); 298 299 return constants; 300 } 301 302 private void listenForInitSessionEventsToReactNative(ReactApplicationContext reactContext) { 303 mInitSessionFinishedEventReceiver = new BroadcastReceiver() { 304 RNBranchModule mBranchModule; 305 306 @Override 307 public void onReceive(Context context, Intent intent) { 308 final boolean hasError = (initSessionResult.has("error") && !initSessionResult.isNull("error")); 309 final String eventName = hasError ? RN_INIT_SESSION_ERROR_EVENT : RN_INIT_SESSION_SUCCESS_EVENT; 310 mBranchModule.sendRNEvent(eventName, convertJsonToMap(initSessionResult)); 311 } 312 313 private BroadcastReceiver init(RNBranchModule branchModule) { 314 mBranchModule = branchModule; 315 return this; 316 } 317 }.init(this); 318 319 LocalBroadcastManager.getInstance(reactContext).registerReceiver(mInitSessionFinishedEventReceiver, new IntentFilter(NATIVE_INIT_SESSION_FINISHED_EVENT)); 320 321 mInitSessionStartedEventReceiver = new BroadcastReceiver() { 322 RNBranchModule mBranchModule; 323 324 @Override 325 public void onReceive(Context context, Intent intent) { 326 Uri uri = intent.getParcelableExtra(NATIVE_INIT_SESSION_STARTED_EVENT_URI); 327 WritableMap payload = new WritableNativeMap(); 328 if (uri != null) { 329 payload.putString(NATIVE_INIT_SESSION_STARTED_EVENT_URI, uri.toString()); 330 } 331 else { 332 payload.putNull(NATIVE_INIT_SESSION_STARTED_EVENT_URI); 333 } 334 mBranchModule.sendRNEvent(RN_INIT_SESSION_START_EVENT, payload); 335 } 336 337 private BroadcastReceiver init(RNBranchModule branchModule) { 338 mBranchModule = branchModule; 339 return this; 340 } 341 }.init(this); 342 343 LocalBroadcastManager.getInstance(reactContext).registerReceiver(mInitSessionStartedEventReceiver, new IntentFilter(NATIVE_INIT_SESSION_STARTED_EVENT)); 344 } 345 346 @Override 347 public void onCatalystInstanceDestroy() { 348 LocalBroadcastManager.getInstance(getReactApplicationContext()).unregisterReceiver(mInitSessionFinishedEventReceiver); 349 LocalBroadcastManager.getInstance(getReactApplicationContext()).unregisterReceiver(mInitSessionStartedEventReceiver); 350 } 351 352 @Override 353 public String getName() { 354 return REACT_MODULE_NAME; 355 } 356 357 @ReactMethod 358 public void disableTracking(boolean disable) { 359 Branch branch = Branch.getInstance(); 360 branch.disableTracking(disable); 361 } 362 363 @ReactMethod 364 public void isTrackingDisabled(Promise promise) { 365 Branch branch = Branch.getInstance(); 366 promise.resolve(branch.isTrackingDisabled()); 367 } 368 369 @ReactMethod 370 public void createUniversalObject(ReadableMap universalObjectMap, Promise promise) { 371 String ident = UUID.randomUUID().toString(); 372 BranchUniversalObject universalObject = createBranchUniversalObject(universalObjectMap); 373 mUniversalObjectMap.put(ident, universalObject); 374 375 WritableMap response = new WritableNativeMap(); 376 response.putString(IDENT_FIELD_NAME, ident); 377 promise.resolve(response); 378 } 379 380 @ReactMethod 381 public void releaseUniversalObject(String ident) { 382 mUniversalObjectMap.remove(ident); 383 } 384 385 @ReactMethod 386 public void redeemInitSessionResult(Promise promise) { 387 promise.resolve(convertJsonToMap(initSessionResult)); 388 } 389 390 @ReactMethod 391 public void getLatestReferringParams(boolean synchronous, Promise promise) { 392 Branch branch = Branch.getInstance(); 393 if (synchronous) 394 promise.resolve(convertJsonToMap(branch.getLatestReferringParamsSync())); 395 else 396 promise.resolve(convertJsonToMap(branch.getLatestReferringParams())); 397 } 398 399 @ReactMethod 400 public void getFirstReferringParams(Promise promise) { 401 Branch branch = Branch.getInstance(); 402 promise.resolve(convertJsonToMap(branch.getFirstReferringParams())); 403 } 404 405 @ReactMethod 406 public void setIdentity(String identity) { 407 Branch branch = Branch.getInstance(); 408 branch.setIdentity(identity); 409 } 410 411 @ReactMethod 412 public void setRequestMetadataKey(String key, String value) { 413 // setRequestMetadata does not do what it appears to do. Call directly to the native code. 414 Branch branch = Branch.getInstance(); 415 branch.setRequestMetadata(key, value); 416 } 417 418 @ReactMethod 419 public void logout() { 420 Branch branch = Branch.getInstance(); 421 branch.logout(); 422 } 423 424 @ReactMethod 425 public void logEvent(ReadableArray contentItems, String eventName, ReadableMap params, Promise promise) { 426 List<BranchUniversalObject> buos = new ArrayList<>(); 427 for (int i = 0; i < contentItems.size(); ++ i) { 428 String ident = contentItems.getString(i); 429 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 430 if (universalObject == null) return; 431 buos.add(universalObject); 432 } 433 434 BranchEvent event = createBranchEvent(eventName, params); 435 event.addContentItems(buos); 436 event.logEvent(mActivity); 437 promise.resolve(null); 438 } 439 440 @ReactMethod 441 public void userCompletedAction(String event, ReadableMap appState) throws JSONException { 442 Branch branch = Branch.getInstance(); 443 branch.userCompletedAction(event, convertMapToJson(appState)); 444 } 445 446 @ReactMethod 447 public void userCompletedActionOnUniversalObject(String ident, String event, ReadableMap state, Promise promise) { 448 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 449 if (universalObject == null) return; 450 451 universalObject.userCompletedAction(event, convertMapToParams(state)); 452 promise.resolve(null); 453 } 454 455 @ReactMethod 456 public void sendCommerceEvent(String revenue, ReadableMap metadata, final Promise promise) throws JSONException { 457 Branch branch = Branch.getInstance(); 458 459 CommerceEvent commerceEvent = new CommerceEvent(); 460 commerceEvent.setRevenue(Double.parseDouble(revenue)); 461 462 JSONObject jsonMetadata = null; 463 if (metadata != null) { 464 jsonMetadata = convertMapToJson(metadata); 465 } 466 467 branch.sendCommerceEvent(commerceEvent, jsonMetadata, null); 468 promise.resolve(null); 469 } 470 471 @ReactMethod 472 public void showShareSheet(String ident, ReadableMap shareOptionsMap, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, Promise promise) { 473 Context context = getReactApplicationContext(); 474 475 Handler mainHandler = new Handler(context.getMainLooper()); 476 477 Runnable myRunnable = new Runnable() { 478 Promise mPm; 479 Context mContext; 480 ReadableMap shareOptionsMap, linkPropertiesMap, controlParamsMap; 481 String ident; 482 483 private Runnable init(ReadableMap _shareOptionsMap, String _ident, ReadableMap _linkPropertiesMap, ReadableMap _controlParamsMap, Promise promise, Context context) { 484 mPm = promise; 485 mContext = context; 486 shareOptionsMap = _shareOptionsMap; 487 ident = _ident; 488 linkPropertiesMap = _linkPropertiesMap; 489 controlParamsMap = _controlParamsMap; 490 return this; 491 } 492 493 @Override 494 public void run() { 495 String messageHeader = shareOptionsMap.hasKey("messageHeader") ? shareOptionsMap.getString("messageHeader") : ""; 496 String messageBody = shareOptionsMap.hasKey("messageBody") ? shareOptionsMap.getString("messageBody") : ""; 497 ShareSheetStyle shareSheetStyle = new ShareSheetStyle(mContext, messageHeader, messageBody) 498 .setCopyUrlStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_send), "Copy", "Added to clipboard") 499 .setMoreOptionStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_search), "Show more") 500 .addPreferredSharingOption(SharingHelper.SHARE_WITH.EMAIL) 501 .addPreferredSharingOption(SharingHelper.SHARE_WITH.TWITTER) 502 .addPreferredSharingOption(SharingHelper.SHARE_WITH.MESSAGE) 503 .addPreferredSharingOption(SharingHelper.SHARE_WITH.FACEBOOK); 504 505 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, mPm); 506 if (branchUniversalObject == null) { 507 return; 508 } 509 510 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 511 512 branchUniversalObject.showShareSheet( 513 getCurrentActivity(), 514 linkProperties, 515 shareSheetStyle, 516 new Branch.BranchLinkShareListener() { 517 private Promise mPromise = null; 518 519 @Override 520 public void onShareLinkDialogLaunched() { 521 } 522 523 @Override 524 public void onShareLinkDialogDismissed() { 525 if(mPromise == null) { 526 return; 527 } 528 529 WritableMap map = new WritableNativeMap(); 530 map.putString("channel", null); 531 map.putBoolean("completed", false); 532 map.putString("error", null); 533 mPromise.resolve(map); 534 mPromise = null; 535 } 536 537 @Override 538 public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchError error) { 539 if(mPromise == null) { 540 return; 541 } 542 543 WritableMap map = new WritableNativeMap(); 544 map.putString("channel", sharedChannel); 545 map.putBoolean("completed", true); 546 map.putString("error", (error != null ? error.getMessage() : null)); 547 mPromise.resolve(map); 548 mPromise = null; 549 } 550 @Override 551 public void onChannelSelected(String channelName) { 552 } 553 554 private Branch.BranchLinkShareListener init(Promise promise) { 555 mPromise = promise; 556 return this; 557 } 558 }.init(mPm)); 559 } 560 }.init(shareOptionsMap, ident, linkPropertiesMap, controlParamsMap, promise, context); 561 562 mainHandler.post(myRunnable); 563 } 564 565 @ReactMethod 566 public void registerView(String ident, Promise promise) { 567 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 568 if (branchUniversalObject == null) { 569 return; 570 } 571 572 branchUniversalObject.registerView(); 573 promise.resolve(null); 574 } 575 576 @ReactMethod 577 public void generateShortUrl(String ident, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, final Promise promise) { 578 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 579 580 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 581 if (branchUniversalObject == null) { 582 return; 583 } 584 585 branchUniversalObject.generateShortUrl(mActivity, linkProperties, new BranchLinkCreateListener() { 586 @Override 587 public void onLinkCreate(String url, BranchError error) { 588 Log.d(REACT_CLASS, "onLinkCreate " + url); 589 if (error != null) { 590 if (error.getErrorCode() == BranchError.ERR_BRANCH_DUPLICATE_URL) { 591 promise.reject("RNBranch::Error::DuplicateResourceError", error.getMessage()); 592 } 593 else { 594 promise.reject(GENERIC_ERROR, error.getMessage()); 595 } 596 return; 597 } 598 599 WritableMap map = new WritableNativeMap(); 600 map.putString("url", url); 601 promise.resolve(map); 602 } 603 }); 604 } 605 606 @ReactMethod 607 public void openURL(String url, ReadableMap options) { 608 if (mActivity == null) { 609 // initSession is called before JS loads. This probably indicates failure to call initSession 610 // in an activity. 611 Log.e(REACT_CLASS, "Branch native Android SDK not initialized in openURL"); 612 return; 613 } 614 615 Intent intent = new Intent(mActivity, mActivity.getClass()); 616 intent.putExtra("branch", url); 617 intent.putExtra("branch_force_new_session", true); 618 619 if (options.hasKey("newActivity") && options.getBoolean("newActivity")) mActivity.finish(); 620 mActivity.startActivity(intent); 621 } 622 623 public static BranchEvent createBranchEvent(String eventName, ReadableMap params) { 624 BranchEvent event; 625 try { 626 BRANCH_STANDARD_EVENT standardEvent = BRANCH_STANDARD_EVENT.valueOf(eventName); 627 // valueOf on BRANCH_STANDARD_EVENT Enum has succeeded, so this is a standard event. 628 event = new BranchEvent(standardEvent); 629 } catch (IllegalArgumentException e) { 630 // The event name is not found in standard events. 631 // So use custom event mode. 632 event = new BranchEvent(eventName); 633 } 634 635 if (params.hasKey("currency")) { 636 String currencyString = params.getString("currency"); 637 CurrencyType currency = CurrencyType.getValue(currencyString); 638 if (currency != null) { 639 event.setCurrency(currency); 640 } 641 else { 642 Log.w(REACT_CLASS, "Invalid currency " + currencyString); 643 } 644 } 645 646 if (params.hasKey("transactionID")) event.setTransactionID(params.getString("transactionID")); 647 if (params.hasKey("revenue")) event.setRevenue(Double.parseDouble(params.getString("revenue"))); 648 if (params.hasKey("shipping")) event.setShipping(Double.parseDouble(params.getString("shipping"))); 649 if (params.hasKey("tax")) event.setTax(Double.parseDouble(params.getString("tax"))); 650 if (params.hasKey("coupon")) event.setCoupon(params.getString("coupon")); 651 if (params.hasKey("affiliation")) event.setAffiliation(params.getString("affiliation")); 652 if (params.hasKey("description")) event.setDescription(params.getString("description")); 653 if (params.hasKey("searchQuery")) event.setSearchQuery(params.getString("searchQuery")); 654 if (params.hasKey("alias")) event.setCustomerEventAlias(params.getString("alias")); 655 656 if (params.hasKey("customData")) { 657 ReadableMap customData = params.getMap("customData"); 658 ReadableMapKeySetIterator it = customData.keySetIterator(); 659 while (it.hasNextKey()) { 660 String key = it.nextKey(); 661 event.addCustomDataProperty(key, customData.getString(key)); 662 } 663 } 664 665 return event; 666 } 667 668 public static LinkProperties createLinkProperties(ReadableMap linkPropertiesMap, @Nullable ReadableMap controlParams){ 669 LinkProperties linkProperties = new LinkProperties(); 670 if (linkPropertiesMap.hasKey("alias")) linkProperties.setAlias(linkPropertiesMap.getString("alias")); 671 if (linkPropertiesMap.hasKey("campaign")) linkProperties.setCampaign(linkPropertiesMap.getString("campaign")); 672 if (linkPropertiesMap.hasKey("channel")) linkProperties.setChannel(linkPropertiesMap.getString("channel")); 673 if (linkPropertiesMap.hasKey("feature")) linkProperties.setFeature(linkPropertiesMap.getString("feature")); 674 if (linkPropertiesMap.hasKey("stage")) linkProperties.setStage(linkPropertiesMap.getString("stage")); 675 676 if (linkPropertiesMap.hasKey("tags")) { 677 ReadableArray tags = linkPropertiesMap.getArray("tags"); 678 for (int i=0; i<tags.size(); ++i) { 679 String tag = tags.getString(i); 680 linkProperties.addTag(tag); 681 } 682 } 683 684 if (controlParams != null) { 685 ReadableMapKeySetIterator iterator = controlParams.keySetIterator(); 686 while (iterator.hasNextKey()) { 687 String key = iterator.nextKey(); 688 Object value = getReadableMapObjectForKey(controlParams, key); 689 linkProperties.addControlParameter(key, value.toString()); 690 } 691 } 692 693 return linkProperties; 694 } 695 696 private static Branch setupBranch(Context context) { 697 Branch branch = Branch.getInstance(context); 698 699 if (!mInitialized) { 700 Log.i(REACT_CLASS, "Initializing Branch SDK v. " + BuildConfig.VERSION_NAME); 701 702 RNBranchConfig config = new RNBranchConfig(context); 703 704 if (mUseDebug || config.getDebugMode()) branch.setDebug(); 705 706 if (config.getEnableFacebookLinkCheck()) branch.enableFacebookAppLinkCheck(); 707 708 if (mRequestMetadata != null) { 709 Iterator keys = mRequestMetadata.keys(); 710 while (keys.hasNext()) { 711 String key = (String) keys.next(); 712 try { 713 branch.setRequestMetadata(key, mRequestMetadata.getString(key)); 714 } catch (JSONException e) { 715 // no-op 716 } 717 } 718 } 719 720 mInitialized = true; 721 } 722 723 return branch; 724 } 725 726 private BranchUniversalObject findUniversalObjectOrReject(final String ident, final Promise promise) { 727 BranchUniversalObject universalObject = mUniversalObjectMap.get(ident); 728 729 if (universalObject == null) { 730 final String errorMessage = "BranchUniversalObject not found for ident " + ident + "."; 731 promise.reject(UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE, errorMessage); 732 } 733 734 return universalObject; 735 } 736 737 public ContentMetadata createContentMetadata(ReadableMap map) { 738 ContentMetadata metadata = new ContentMetadata(); 739 740 if (map.hasKey("contentSchema")) { 741 BranchContentSchema schema = BranchContentSchema.valueOf(map.getString("contentSchema")); 742 metadata.setContentSchema(schema); 743 } 744 745 if (map.hasKey("quantity")) { 746 metadata.setQuantity(map.getDouble("quantity")); 747 } 748 749 if (map.hasKey("price")) { 750 double price = Double.parseDouble(map.getString("price")); 751 CurrencyType currency = null; 752 if (map.hasKey("currency")) currency = CurrencyType.valueOf(map.getString("currency")); 753 metadata.setPrice(price, currency); 754 } 755 756 if (map.hasKey("sku")) { 757 metadata.setSku(map.getString("sku")); 758 } 759 760 if (map.hasKey("productName")) { 761 metadata.setProductName(map.getString("productName")); 762 } 763 764 if (map.hasKey("productBrand")) { 765 metadata.setProductBrand(map.getString("productBrand")); 766 } 767 768 if (map.hasKey("productCategory")) { 769 ProductCategory category = getProductCategory(map.getString("productCategory")); 770 if (category != null) metadata.setProductCategory(category); 771 } 772 773 if (map.hasKey("productVariant")) { 774 metadata.setProductVariant(map.getString("productVariant")); 775 } 776 777 if (map.hasKey("condition")) { 778 ContentMetadata.CONDITION condition = ContentMetadata.CONDITION.valueOf(map.getString("condition")); 779 metadata.setProductCondition(condition); 780 } 781 782 if (map.hasKey("ratingAverage") || map.hasKey("ratingMax") || map.hasKey("ratingCount")) { 783 Double average = null, max = null; 784 Integer count = null; 785 if (map.hasKey("ratingAverage")) average = map.getDouble("ratingAverage"); 786 if (map.hasKey("ratingCount")) count = map.getInt("ratingCount"); 787 if (map.hasKey("ratingMax")) max = map.getDouble("ratingMax"); 788 metadata.setRating(average, max, count); 789 } 790 791 if (map.hasKey("addressStreet") || 792 map.hasKey("addressCity") || 793 map.hasKey("addressRegion") || 794 map.hasKey("addressCountry") || 795 map.hasKey("addressPostalCode")) { 796 String street = null, city = null, region = null, country = null, postalCode = null; 797 if (map.hasKey("addressStreet")) street = map.getString("addressStreet"); 798 if (map.hasKey("addressCity")) street = map.getString("addressCity"); 799 if (map.hasKey("addressRegion")) street = map.getString("addressRegion"); 800 if (map.hasKey("addressCountry")) street = map.getString("addressCountry"); 801 if (map.hasKey("addressPostalCode")) street = map.getString("addressPostalCode"); 802 metadata.setAddress(street, city, region, country, postalCode); 803 } 804 805 if (map.hasKey("latitude") || map.hasKey("longitude")) { 806 Double latitude = null, longitude = null; 807 if (map.hasKey("latitude")) latitude = map.getDouble("latitude"); 808 if (map.hasKey("longitude")) longitude = map.getDouble("longitude"); 809 metadata.setLocation(latitude, longitude); 810 } 811 812 if (map.hasKey("imageCaptions")) { 813 ReadableArray captions = map.getArray("imageCaptions"); 814 for (int j=0; j < captions.size(); ++j) { 815 metadata.addImageCaptions(captions.getString(j)); 816 } 817 } 818 819 if (map.hasKey("customMetadata")) { 820 ReadableMap customMetadata = map.getMap("customMetadata"); 821 ReadableMapKeySetIterator it = customMetadata.keySetIterator(); 822 while (it.hasNextKey()) { 823 String key = it.nextKey(); 824 metadata.addCustomMetadata(key, customMetadata.getString(key)); 825 } 826 } 827 828 return metadata; 829 } 830 831 public BranchUniversalObject createBranchUniversalObject(ReadableMap branchUniversalObjectMap) { 832 BranchUniversalObject branchUniversalObject = new BranchUniversalObject() 833 .setCanonicalIdentifier(branchUniversalObjectMap.getString("canonicalIdentifier")); 834 835 if (branchUniversalObjectMap.hasKey("title")) branchUniversalObject.setTitle(branchUniversalObjectMap.getString("title")); 836 if (branchUniversalObjectMap.hasKey("canonicalUrl")) branchUniversalObject.setCanonicalUrl(branchUniversalObjectMap.getString("canonicalUrl")); 837 if (branchUniversalObjectMap.hasKey("contentDescription")) branchUniversalObject.setContentDescription(branchUniversalObjectMap.getString("contentDescription")); 838 if (branchUniversalObjectMap.hasKey("contentImageUrl")) branchUniversalObject.setContentImageUrl(branchUniversalObjectMap.getString("contentImageUrl")); 839 840 if (branchUniversalObjectMap.hasKey("locallyIndex")) { 841 if (branchUniversalObjectMap.getBoolean("locallyIndex")) { 842 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 843 } 844 else { 845 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 846 } 847 } 848 849 if (branchUniversalObjectMap.hasKey("publiclyIndex")) { 850 if (branchUniversalObjectMap.getBoolean("publiclyIndex")) { 851 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 852 } 853 else { 854 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 855 } 856 } 857 858 if (branchUniversalObjectMap.hasKey("contentIndexingMode")) { 859 switch (branchUniversalObjectMap.getType("contentIndexingMode")) { 860 case String: 861 String mode = branchUniversalObjectMap.getString("contentIndexingMode"); 862 863 if (mode.equals("private")) 864 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 865 else if (mode.equals("public")) 866 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 867 else 868 Log.w(REACT_CLASS, "Unsupported value for contentIndexingMode: " + mode + 869 ". Supported values are \"public\" and \"private\""); 870 break; 871 default: 872 Log.w(REACT_CLASS, "contentIndexingMode must be a String"); 873 break; 874 } 875 } 876 877 if (branchUniversalObjectMap.hasKey("currency") && branchUniversalObjectMap.hasKey("price")) { 878 String currencyString = branchUniversalObjectMap.getString("currency"); 879 CurrencyType currency = CurrencyType.valueOf(currencyString); 880 branchUniversalObject.setPrice(branchUniversalObjectMap.getDouble("price"), currency); 881 } 882 883 if (branchUniversalObjectMap.hasKey("expirationDate")) { 884 String expirationString = branchUniversalObjectMap.getString("expirationDate"); 885 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 886 format.setTimeZone(TimeZone.getTimeZone("UTC")); 887 try { 888 Date date = format.parse(expirationString); 889 Log.d(REACT_CLASS, "Expiration date is " + date.toString()); 890 branchUniversalObject.setContentExpiration(date); 891 } 892 catch (ParseException e) { 893 Log.w(REACT_CLASS, "Invalid expiration date format. Valid format is YYYY-mm-ddTHH:MM:SS, e.g. 2017-02-01T00:00:00. All times UTC."); 894 } 895 } 896 897 if (branchUniversalObjectMap.hasKey("keywords")) { 898 ReadableArray keywords = branchUniversalObjectMap.getArray("keywords"); 899 for (int i=0; i<keywords.size(); ++i) { 900 branchUniversalObject.addKeyWord(keywords.getString(i)); 901 } 902 } 903 904 if(branchUniversalObjectMap.hasKey("metadata")) { 905 ReadableMap metadataMap = branchUniversalObjectMap.getMap("metadata"); 906 ReadableMapKeySetIterator iterator = metadataMap.keySetIterator(); 907 while (iterator.hasNextKey()) { 908 String metadataKey = iterator.nextKey(); 909 Object metadataObject = getReadableMapObjectForKey(metadataMap, metadataKey); 910 branchUniversalObject.addContentMetadata(metadataKey, metadataObject.toString()); 911 HashMap<String, String> metadata = branchUniversalObject.getMetadata(); 912 } 913 } 914 915 if (branchUniversalObjectMap.hasKey("type")) branchUniversalObject.setContentType(branchUniversalObjectMap.getString("type")); 916 917 if (branchUniversalObjectMap.hasKey("contentMetadata")) { 918 branchUniversalObject.setContentMetadata(createContentMetadata(branchUniversalObjectMap.getMap("contentMetadata"))); 919 } 920 921 return branchUniversalObject; 922 } 923 924 @Nullable 925 public ProductCategory getProductCategory(final String stringValue) { 926 ProductCategory[] possibleValues = ProductCategory.class.getEnumConstants(); 927 for (ProductCategory value: possibleValues) { 928 if (stringValue.equals(value.getName())) { 929 return value; 930 } 931 } 932 Log.w(REACT_CLASS, "Could not find product category " + stringValue); 933 return null; 934 } 935 936 @ReactMethod 937 public void redeemRewards(int value, String bucket, Promise promise) 938 { 939 if (bucket == null) { 940 Branch.getInstance().redeemRewards(value, new RedeemRewardsListener(promise)); 941 } else { 942 Branch.getInstance().redeemRewards(bucket, value, new RedeemRewardsListener(promise)); 943 } 944 } 945 946 @ReactMethod 947 public void loadRewards(String bucket, Promise promise) 948 { 949 Branch.getInstance().loadRewards(new LoadRewardsListener(bucket, promise)); 950 } 951 952 @ReactMethod 953 public void getCreditHistory(Promise promise) 954 { 955 Branch.getInstance().getCreditHistory(new CreditHistoryListener(promise)); 956 } 957 958 protected class CreditHistoryListener implements Branch.BranchListResponseListener 959 { 960 private Promise _promise; 961 962 // Constructor that takes in a required callbackContext object 963 public CreditHistoryListener(Promise promise) { 964 this._promise = promise; 965 } 966 967 // Listener that implements BranchListResponseListener for getCreditHistory() 968 @Override 969 public void onReceivingResponse(JSONArray list, BranchError error) { 970 ArrayList<String> errors = new ArrayList<String>(); 971 if (error == null) { 972 try { 973 ReadableArray result = convertJsonToArray(list); 974 this._promise.resolve(result); 975 } catch (JSONException err) { 976 this._promise.reject(GENERIC_ERROR, err.getMessage()); 977 } 978 } else { 979 String errorMessage = error.getMessage(); 980 Log.d(REACT_CLASS, errorMessage); 981 this._promise.reject(GENERIC_ERROR, errorMessage); 982 } 983 } 984 } 985 986 protected class RedeemRewardsListener implements Branch.BranchReferralStateChangedListener 987 { 988 private Promise _promise; 989 990 public RedeemRewardsListener(Promise promise) { 991 this._promise = promise; 992 } 993 994 @Override 995 public void onStateChanged(boolean changed, BranchError error) { 996 if (error == null) { 997 WritableMap map = new WritableNativeMap(); 998 map.putBoolean("changed", changed); 999 this._promise.resolve(map); 1000 } else { 1001 String errorMessage = error.getMessage(); 1002 Log.d(REACT_CLASS, errorMessage); 1003 this._promise.reject(GENERIC_ERROR, errorMessage); 1004 } 1005 } 1006 } 1007 1008 protected class LoadRewardsListener implements Branch.BranchReferralStateChangedListener 1009 { 1010 private String _bucket; 1011 private Promise _promise; 1012 1013 public LoadRewardsListener(String bucket, Promise promise) { 1014 this._bucket = bucket; 1015 this._promise = promise; 1016 } 1017 1018 @Override 1019 public void onStateChanged(boolean changed, BranchError error) { 1020 if (error == null) { 1021 int credits = 0; 1022 if (this._bucket == null) { 1023 credits = Branch.getInstance().getCredits(); 1024 } else { 1025 credits = Branch.getInstance().getCreditsForBucket(this._bucket); 1026 } 1027 WritableMap map = new WritableNativeMap(); 1028 map.putInt("credits", credits); 1029 this._promise.resolve(map); 1030 } else { 1031 String errorMessage = error.getMessage(); 1032 Log.d(REACT_CLASS, errorMessage); 1033 this._promise.reject(GENERIC_ERROR, errorMessage); 1034 } 1035 } 1036 } 1037 1038 public void sendRNEvent(String eventName, @Nullable WritableMap params) { 1039 // This should avoid the crash in getJSModule() at startup 1040 // See also: https://github.com/walmartreact/react-native-orientation-listener/issues/8 1041 1042 ReactApplicationContext context = getReactApplicationContext(); 1043 Handler mainHandler = new Handler(context.getMainLooper()); 1044 1045 Runnable poller = new Runnable() { 1046 1047 private Runnable init(ReactApplicationContext _context, Handler _mainHandler, String _eventName, WritableMap _params) { 1048 mMainHandler = _mainHandler; 1049 mEventName = _eventName; 1050 mContext = _context; 1051 mParams = _params; 1052 return this; 1053 } 1054 1055 final int pollDelayInMs = 100; 1056 final int maxTries = 300; 1057 1058 int tries = 1; 1059 String mEventName; 1060 WritableMap mParams; 1061 Handler mMainHandler; 1062 ReactApplicationContext mContext; 1063 1064 @Override 1065 public void run() { 1066 try { 1067 Log.d(REACT_CLASS, "Catalyst instance poller try " + Integer.toString(tries)); 1068 if (mContext.hasActiveCatalystInstance()) { 1069 Log.d(REACT_CLASS, "Catalyst instance active"); 1070 mContext 1071 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 1072 .emit(mEventName, mParams); 1073 } else { 1074 tries++; 1075 if (tries <= maxTries) { 1076 mMainHandler.postDelayed(this, pollDelayInMs); 1077 } else { 1078 Log.e(REACT_CLASS, "Could not get Catalyst instance"); 1079 } 1080 } 1081 } 1082 catch (Exception e) { 1083 e.printStackTrace(); 1084 } 1085 } 1086 }.init(context, mainHandler, eventName, params); 1087 1088 Log.d(REACT_CLASS, "sendRNEvent"); 1089 1090 mainHandler.post(poller); 1091 } 1092 1093 private static Object getReadableMapObjectForKey(ReadableMap readableMap, String key) { 1094 switch (readableMap.getType(key)) { 1095 case Null: 1096 return "Null"; 1097 case Boolean: 1098 return readableMap.getBoolean(key); 1099 case Number: 1100 if (readableMap.getDouble(key) % 1 == 0) { 1101 return readableMap.getInt(key); 1102 } else { 1103 return readableMap.getDouble(key); 1104 } 1105 case String: 1106 return readableMap.getString(key); 1107 default: 1108 return "Unsupported Type"; 1109 } 1110 } 1111 1112 private static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException { 1113 JSONObject object = new JSONObject(); 1114 ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 1115 while (iterator.hasNextKey()) { 1116 String key = iterator.nextKey(); 1117 switch (readableMap.getType(key)) { 1118 case Null: 1119 object.put(key, JSONObject.NULL); 1120 break; 1121 case Boolean: 1122 object.put(key, readableMap.getBoolean(key)); 1123 break; 1124 case Number: 1125 object.put(key, readableMap.getDouble(key)); 1126 break; 1127 case String: 1128 object.put(key, readableMap.getString(key)); 1129 break; 1130 case Map: 1131 object.put(key, convertMapToJson(readableMap.getMap(key))); 1132 break; 1133 case Array: 1134 object.put(key, convertArrayToJson(readableMap.getArray(key))); 1135 break; 1136 } 1137 } 1138 return object; 1139 } 1140 1141 private static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException { 1142 JSONArray array = new JSONArray(); 1143 for (int i = 0; i < readableArray.size(); i++) { 1144 switch (readableArray.getType(i)) { 1145 case Null: 1146 break; 1147 case Boolean: 1148 array.put(readableArray.getBoolean(i)); 1149 break; 1150 case Number: 1151 array.put(readableArray.getDouble(i)); 1152 break; 1153 case String: 1154 array.put(readableArray.getString(i)); 1155 break; 1156 case Map: 1157 array.put(convertMapToJson(readableArray.getMap(i))); 1158 break; 1159 case Array: 1160 array.put(convertArrayToJson(readableArray.getArray(i))); 1161 break; 1162 } 1163 } 1164 return array; 1165 } 1166 1167 private static WritableMap convertJsonToMap(JSONObject jsonObject) { 1168 if(jsonObject == null) { 1169 return null; 1170 } 1171 1172 WritableMap map = new WritableNativeMap(); 1173 1174 try { 1175 Iterator<String> iterator = jsonObject.keys(); 1176 while (iterator.hasNext()) { 1177 String key = iterator.next(); 1178 Object value = jsonObject.get(key); 1179 if (value instanceof JSONObject) { 1180 map.putMap(key, convertJsonToMap((JSONObject) value)); 1181 } else if (value instanceof JSONArray) { 1182 map.putArray(key, convertJsonToArray((JSONArray) value)); 1183 } else if (value instanceof Boolean) { 1184 map.putBoolean(key, (Boolean) value); 1185 } else if (value instanceof Integer) { 1186 map.putInt(key, (Integer) value); 1187 } else if (value instanceof Double) { 1188 map.putDouble(key, (Double) value); 1189 } else if (value instanceof String) { 1190 map.putString(key, (String) value); 1191 } else if (value == null || value == JSONObject.NULL) { 1192 map.putNull(key); 1193 } else { 1194 map.putString(key, value.toString()); 1195 } 1196 } 1197 } catch(JSONException ex) { 1198 map.putString("error", "Failed to convert JSONObject to WriteableMap: " + ex.getMessage()); 1199 } 1200 1201 return map; 1202 } 1203 1204 private static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException { 1205 WritableArray array = new WritableNativeArray(); 1206 1207 for (int i = 0; i < jsonArray.length(); i++) { 1208 Object value = jsonArray.get(i); 1209 if (value instanceof JSONObject) { 1210 array.pushMap(convertJsonToMap((JSONObject) value)); 1211 } else if (value instanceof JSONArray) { 1212 array.pushArray(convertJsonToArray((JSONArray) value)); 1213 } else if (value instanceof Boolean) { 1214 array.pushBoolean((Boolean) value); 1215 } else if (value instanceof Integer) { 1216 array.pushInt((Integer) value); 1217 } else if (value instanceof Double) { 1218 array.pushDouble((Double) value); 1219 } else if (value instanceof String) { 1220 array.pushString((String) value); 1221 } else { 1222 array.pushString(value.toString()); 1223 } 1224 } 1225 return array; 1226 } 1227 1228 // Convert an arbitrary ReadableMap to a string-string hash of custom params for userCompletedAction. 1229 private static HashMap<String, String> convertMapToParams(ReadableMap map) { 1230 if (map == null) return null; 1231 1232 HashMap<String, String> hash = new HashMap<>(); 1233 1234 ReadableMapKeySetIterator iterator = map.keySetIterator(); 1235 while (iterator.hasNextKey()) { 1236 String key = iterator.nextKey(); 1237 switch (map.getType(key)) { 1238 case String: 1239 hash.put(key, map.getString(key)); 1240 case Boolean: 1241 hash.put(key, "" + map.getBoolean(key)); 1242 case Number: 1243 hash.put(key, "" + map.getDouble(key)); 1244 default: 1245 Log.w(REACT_CLASS, "Unsupported data type in params, ignoring"); 1246 } 1247 } 1248 1249 return hash; 1250 } 1251 } 1252