NeuroAgent

Branch.io React Native iOS: Subscribe Inconsistency Fix

Troubleshoot and fix inconsistent Branch.io React Native iOS deep linking. Learn solutions for branch.subscribe not firing properly in cold/warm starts with proper initialization and universal links configuration.

Branch.io React Native iOS — branch.subscribe not firing consistently (sometimes gets params, sometimes not)

I’m using Branch.io in a React Native (0.81.0) iOS app, and my deep links only work sometimes.

  • Occasionally, branch.subscribe returns params correctly. Other times, it never fires at all — both cold start and warm start.
  • There’s no clear pattern; it randomly works or doesn’t.
  • I’ve verified the SDK setup, bundle ID, keys, and domains are correct — so it doesn’t look like a configuration issue.

Environment:

  • React Native: 0.81.0
  • Library: react-native-branch (latest)
  • iOS: 15+
  • Branch SDK: installed via CocoaPods (shows BranchSDK in Pods)

Info.plist:

xml
<key>branch_universal_link_domains</key>
<array>
    <string>myapp.app.link</string>
    <string>myapp-alternate.app.link</string>
    <string>myapp.test-app.link</string>
    <string>myapp-alternate.test-app.link</string>
</array>
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>myapp.app</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>
<key>branch_key</key>
<dict>
    <key>live</key>
    <string>{live key}</string>
    <key>test</key>
    <string>{test key}</string>
</dict>

Associated Domains (Capabilities):

applinks:myapp.app.link
applinks:myapp-alternate.app.link
applinks:myapp.test-app.link
applinks:myapp-alternate.test-app.link

AppDelegate.swift:

swift
import RNBranch

// ======= At top ====== //
// Optional: Uncomment next line to use test instead of live key
// RNBranch.useTestInstance()
RNBranch.initSession(launchOptions: launchOptions, isReferrable: true)

// URL Schemes
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    RNBranch.application(app, open:url, options:options)
    return true
}

// Universal Links
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    RNBranch.continue(userActivity)
    return true
}
NeuroAgent

The branch.subscribe callback inconsistency you’re experiencing in your React Native iOS app is a common issue that typically stems from initialization timing, universal links configuration, or background/foreground state handling. This problem often occurs because Branch’s native layer may cache initial deep link events that aren’t properly retrieved when your JavaScript code subscribes.


Contents


Common Causes of Inconsistent Branch.subscribe

Based on the research findings, the inconsistent behavior typically stems from several key issues:

  1. Timing Problems: The Branch native layer caches initial deep link events that may not be properly retrieved when your JavaScript code subscribes, especially during cold starts
  2. Universal Links Configuration: Issues with your universal links setup can cause inconsistent behavior between deep link attempts
  3. Background/Foreground State: When your app is running in the background, deep links can be interrupted by the iOS system’s communication layer
  4. Resubscription Patterns: Unsubscribe/resubscribe cycles can cause cached events to be missed

Proper Initialization and Timing

Key Issue: Your branch.subscribe call timing relative to app launch is critical.

javascript
// ✅ Correct approach - Subscribe immediately after Branch initialization
import { Branch } from 'react-native-branch';

const initBranch = async () => {
  try {
    await Branch.initSession();
    
    // Subscribe immediately after initialization
    Branch.subscribe(({ error, params, uri }) => {
      if (error) {
        console.error('Error from Branch:', error);
        return;
      }
      
      // ✅ Always check for clicked_branch_link before processing
      if (!params['+clicked_branch_link']) {
        return; // Exit if not a Branch link click
      }
      
      console.log('Deep link params:', params);
      // Handle your deep link logic here
    });
    
  } catch (error) {
    console.error('Branch initialization error:', error);
  }
};

// Call this as early as possible in your app startup
initBranch();

Important: According to Branch’s documentation, any initial link cached by the native layer will be returned to the callback immediately if the JavaScript method is called within a certain time from app launch source.


Universal Links Configuration

Your Info.plist and Associated Domains look correct, but there are several critical considerations:

1. Universal Links vs Custom URL Schemes: Universal links (applinks:) should take precedence over custom URL schemes, but ensure proper handling:

swift
// In AppDelegate.swift - Check which type of link you're receiving
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        // This is a universal link
        let handled = RNBranch.continue(userActivity)
        return handled
    }
    return false
}

2. Link Verification: Test your universal links using Apple’s Link Debugger and ensure your branch_universal_link_domains exactly match what’s configured in your Branch dashboard source.


Background/Foreground State Handling

Critical Issue: When the app is running in the background, links can be interrupted. The solution involves using React Native’s Linking module as a fallback:

javascript
import { Linking } from 'react-native';
import { Branch } from 'react-native-branch';

const handleDeepLinks = () => {
  // Handle initial deep link
  Branch.subscribe(({ error, params, uri }) => {
    if (error) console.error('Branch error:', error);
    if (params && params['+clicked_branch_link']) {
      handleBranchDeepLink(params);
    }
  });

  // ✅ IMPORTANT: Add Linking listener for background/foreground state
  const subscription = Linking.addEventListener('url', (event) => {
    if (event.url) {
      // Check if this is a Branch link
      if (event.url.includes('myapp.app.link') || event.url.includes('myapp.test-app.link')) {
        // Parse the Branch link
        Branch.parseUniversalLink(event.url).then(params => {
          if (params && params['+clicked_branch_link']) {
            handleBranchDeepLink(params);
          }
        });
      }
    }
  });

  return () => {
    subscription.remove();
  };
};

const handleBranchDeepLink = (params) => {
  console.log('Processing deep link:', params);
  // Your deep link logic here
};

According to Stack Overflow discussions, this approach resolves the issue where Branch fetches the first call when the app is opened, but when the app is running in the background, the links are interrupted by different communication layers source.


Resubscription and Caching Issues

Problem: Unsubscribe/resubscribe patterns can cause cached events to be missed.

Solution: Implement a robust initialization pattern:

javascript
class BranchManager {
  constructor() {
    this.isInitialized = false;
    this.subscription = null;
  }

  async initialize() {
    if (this.isInitialized) return;
    
    try {
      await Branch.initSession();
      this.setupSubscription();
      this.isInitialized = true;
    } catch (error) {
      console.error('Branch initialization failed:', error);
    }
  }

  setupSubscription() {
    // Clean up existing subscription
    if (this.subscription) {
      this.subscription.remove();
    }

    this.subscription = Branch.subscribe(({ error, params, uri }) => {
      if (error) {
        console.error('Branch error:', error);
        return;
      }

      if (!params || !params['+clicked_branch_link']) {
        return;
      }

      console.log('Received deep link:', params);
      // Handle your deep link logic
    });
  }

  // Call this when you need to resubscribe (e.g., after navigation changes)
  resubscribe() {
    if (this.isInitialized) {
      this.setupSubscription();
    }
  }
}

// Usage
const branchManager = new BranchManager();
branchManager.initialize();

Debugging and Verification

1. Enable Branch Debug Logging:

swift
// In AppDelegate.swift, add:
import RNBranch

RNBranch.enableLogging()

2. Test Different Scenarios:

  • Cold start (app not running)
  • Warm start (app in background)
  • Hot start (app in foreground)
  • Different iOS versions (15, 16, 17)

3. Check Branch Dashboard: Verify that your links are being clicked and attributed correctly in the Branch dashboard source.

4. Use Branch’s Debug Tools: The Branch dashboard provides debugging tools to test deep links and see what parameters are being passed.


Alternative Approaches

If the above solutions don’t fully resolve your issues, consider these alternatives:

1. Deferred Deep Linking: Use Branch’s deferred deep linking features:

javascript
// Check for existing deep links on app start
const checkExistingDeepLinks = async () => {
  const latestParams = await Branch.getLatestReferringParams();
  const installParams = await Branch.getFirstReferringParams();
  
  if (latestParams && latestParams['+clicked_branch_link']) {
    handleBranchDeepLink(latestParams);
  }
};

2. Event Listeners: Implement comprehensive event listening:

javascript
const setupBranchListeners = () => {
  // Subscribe to Branch events
  Branch.subscribe(handleBranchEvent);
  
  // Listen for app state changes
  AppState.addEventListener('change', (nextAppState) => {
    if (nextAppState === 'active') {
      // App came to foreground - check for pending deep links
      Branch.getLatestReferringParams().then(params => {
        if (params && params['+clicked_branch_link']) {
          handleBranchDeepLink(params);
        }
      });
    }
  });
};

3. iOS-Specific Configuration: Ensure you’re using the latest Branch SDK and consider using Branch NativeLink™ for better iOS matching source.


Sources

  1. React Native Advanced Features - Branch Help Center
  2. React Native Branch Deep Linking GitHub - Resubscription Fix
  3. Branch Subscribe Not Firing - Stack Overflow
  4. Deep Linking React Native - Medium
  5. Troubleshooting Branch Click Tracking
  6. React Native Branch Overview

Conclusion

The inconsistent behavior of branch.subscribe in your React Native iOS app can be resolved by:

  1. Proper initialization timing - Subscribe immediately after Branch.initSession()
  2. Universal links configuration - Ensure proper handling of both universal links and custom URL schemes
  3. Background/foreground state handling - Use React Native’s Linking module as a fallback
  4. Robust resubscription patterns - Implement proper cleanup and recreation of subscriptions
  5. Comprehensive debugging - Enable logging and test various scenarios

The most critical fix is adding the Linking event listener as a fallback for when your app is in the background, as this addresses the core issue where Branch cannot reliably reach your app through iOS’s communication layers. Combined with proper initialization timing and universal links configuration, this should resolve the inconsistent deep link behavior you’re experiencing.