Branch.io Detailed Step-by-Step Implementation (iOS and Android Instructions)

Best Practices

Since Firebase Dynamic Links are getting deprecated by August 25th and FlutterFlow hasn’t shared an alternative solution, I wanted to walk you through the step-by-step process of implementing Branch.io.

Although the implementation is relatively easy, it’s not entirely straightforward. You will need to push your code to a GitHub repository, make some minor edits there, and deploy directly from GitHub using the FlutterFlow app. Don’t worry—this won’t interfere with the ease of using FlutterFlow for other tasks. I’m going to outline the exact changes you’ll need to implement in just 5 minutes before your final deployment to the App Store or Google Play.

Please make sure you disable Firebase Dynamic Link from your FlutterFlow project so that you don't face any conflicts in the handling of page paths, if you want to keep it, you will need to amend the code in Step 2.D to detect if the link is coming from Firebase or Branch and handle it accordingly.

Step 1: Create a GitHub Repository and Push Your Code

Follow this guide to push your code to GitHub. (The section below “Manage Custom Code on GitHub” is optional, so you don’t have to follow that part.) Ensure that your repository is private to protect your integration keys.

Step 2: Perform These File Changes

You’ll need to locate the following files and make these edits:

A) ios/Runner/Runner.entitlements

• If you’re using Sign in with Apple, this file should already exist. If not, you can create one.

• Add this block of code right before the closing </dict> tag at the end of the file:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:your-link-from-branch</string> <!-- Paste your link from the Branch dashboard -->
  <string>applinks:your-alternate-link-from-branch</string> <!-- Paste your alternate link from the Branch dashboard -->
</array>

B) ios/Runner/Info.plist

• Insert the following code block just before the <key>FlutterDeepLinkingEnabled</key> tag:

<key>branch_key</key>
<dict>
  <key>live</key>
  <string>your-branch-live-key</string> <!-- Insert your Branch live key here -->
</dict>

<key>branch_universal_link_domains</key>
<array>
  <string>your-link-from-branch</string>
  <string>your-alternate-link-from-branch</string>
</array>

<key>NSUserActivityTypes</key>
<array>
  <string>NSUserActivityTypeBrowsingWeb</string>
</array>

C) lib/branch_handler.dart

• Create a file with this name (branch_handler.dart). This file initializes Branch and utilizes existing routing settings in Flutter, so you don’t need to create a new logic for handling page paths and parameters.

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_branch_sdk/flutter_branch_sdk.dart';
import '/flutter_flow/nav/nav.dart'; // Adjust the import path if necessary
import '/app_state.dart'; // Import for FFAppState

class BranchLinksHandler extends StatefulWidget {
  const BranchLinksHandler({
    super.key, // Correct modern syntax usage
    required this.router,
    required this.child,
  });

  final GoRouter router;
  final Widget child;

  @override
  _BranchLinksHandlerState createState() => _BranchLinksHandlerState();
}

class _BranchLinksHandlerState extends State<BranchLinksHandler> {
  StreamSubscription<Map>? branchSubscription;

  @override
  void initState() {
    super.initState();
    initBranchIo();
  }

  void initBranchIo() {
    branchSubscription = FlutterBranchSdk.listSession().listen(
      (data) {
        if (Platform.isAndroid) {
          _handleBranchLinkAndroid(data);
        } else {
          _handleBranchLinkIOS(data);
        }
      },
      onError: (error) {
        print('Branch SDK Error: $error');
      },
    );

    FlutterBranchSdk.getLatestReferringParams().then((data) {
      if (data.isNotEmpty) {
        if (Platform.isAndroid) {
          _handleBranchLinkAndroid(data);
        } else {
          _handleBranchLinkIOS(data);
        }
      }
    });
  }

  void _handleBranchLinkAndroid(Map<dynamic, dynamic> data) {
    if (data.containsKey('+clicked_branch_link') &&
        data['+clicked_branch_link'] == true) {
      String? deeplinkPath = data[r'$deeplink_path'] as String?;

      if (deeplinkPath != null) {
        Map<String, String> queryParams = {};

        data.forEach((key, value) {
          if (!key.startsWith('+') &&
              !key.startsWith('~') &&
              key != r'$deeplink_path') {
            String camelCaseKey = _snakeToCamelCase(key);
            queryParams[camelCaseKey] = value.toString();
          }
        });

        Uri uri = Uri(
          path: deeplinkPath,
          queryParameters: queryParams.isNotEmpty ? queryParams : null,
        );

        log('Navigating to: $uri');
        widget.router.go(uri.toString());
      }
    }
  }

  void _handleBranchLinkIOS(Map<dynamic, dynamic> data) {
    if (data.containsKey('+clicked_branch_link') &&
        data['+clicked_branch_link'] == true) {
      String? deeplinkPath = data[r'$deeplink_path'] as String?;

      if (deeplinkPath != null) {
        Map<String, String> queryParams = {};

        data.forEach((key, value) {
          String keyString = key.toString();
          String valueString = value.toString();

          if (keyString.startsWith('+') ||
              keyString.startsWith('~') ||
              keyString == r'$deeplink_path') {
            return;
          }

          String camelCaseKey = _snakeToCamelCase(keyString);

          if (camelCaseKey.isNotEmpty) {
            queryParams[camelCaseKey] = valueString;
          }
        });

        Uri uri = Uri(
          path: deeplinkPath,
          queryParameters: queryParams.isNotEmpty ? queryParams : null,
        );

        String pathAndQuery = uri.toString();

        widget.router.push(pathAndQuery);
        if (widget.router.getCurrentLocation() != deeplinkPath) {
          log("LOCATION IS ${widget.router.getCurrentLocation()}");
          log("LOCATION IS $pathAndQuery");
          widget.router.push(pathAndQuery);
        }
      }
    }
  }

  String _snakeToCamelCase(String input) {
    if (!input.contains('_')) {
      return input;
    }

    List<String> parts = input.split('_');
    String camelCase = parts.first;

    for (int i = 1; i < parts.length; i++) {
      if (parts[i].isEmpty) continue;
      camelCase += parts[i][0].toUpperCase() + parts[i].substring(1);
    }

    return camelCase;
  }

  @override
  void dispose() {
    branchSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => widget.child;
}

• (Optional: The file includes some helper functions to convert snake_case to camelCase to handle the difference between Branch’s parameters and FlutterFlow’s constraints.)

D) lib/main.dart

1. Add this line just after your package imports:

import 'branch_handler.dart';
import 'package:flutter_branch_sdk/flutter_branch_sdk.dart';

2. under void main() async {, initialize branch SDK by adding the following line

await FlutterBranchSdk.init();

3. In the build method, do the following modifications:

• Find the @override Widget build(BuildContext context) {} block and make sure the final code looks like this (read the comments):

@override
Widget build(BuildContext context) {
  final router = _router; // Ensure the router is available

  return BranchLinksHandler( // Wrap MaterialApp with BranchLinksHandler
    router: router, // Pass the router to BranchLinksHandler
    child: MaterialApp.router( // Original MaterialApp.router wrapped inside BranchLinksHandler
      title: 'Your App Title', // Check and keep the title of your app
      localizationsDelegates: const [
        FFLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      locale: _locale,
      supportedLocales: const [
        Locale('en'),
      ],
      theme: ThemeData(
        brightness: Brightness.light,
        useMaterial3: false,
      ),
      themeMode: _themeMode,
      routerConfig: router, // Pass the router variable
    ),
  );
}

E) pubspec.yaml

• Add this line in the dependencies section (Make sure this line is alphabetically sorted with other dependencies.):

flutter_branch_sdk: ^8.2.0

F) Steps for Android: locate android/app/src/main/AndroidManifest.xml

Configure the following:

Under <meta-data android:name="flutter_deeplinking_enabled" android:value="true" />, add the following:

<!-- Updated for Branch: Updated deep link handling to include Branch URI Scheme -->
            <intent-filter>
                <data android:scheme="YOUR-BRANCH-URI" android:host="open" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
         <!-- Added for Branch: Branch App Links - Live App -->
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="https" android:host="your-link-from-branch" />
                <data android:scheme="https" android:host="our-alternate-link-from-branch" />
            </intent-filter>

And just after the last </activity> tag, add the following

 <!-- Added for Branch: Branch SDK Meta-data -->
        <meta-data android:name="io.branch.sdk.BranchKey" android:value="your-branch-live-key" />
        <meta-data android:name="io.branch.sdk.TestMode" android:value="false" />

Step 3: Deploy from GitHub

Once you’ve made the above changes, return to the FlutterFlow app and switch your deployment source to the GitHub repository by following this guide.

Remember to also update the version and build number in pubspec.yaml (the same file from Step 2, point E) before deploying your app. and to update your build type from debug to release under android/app/build.gradle if you are deploying to Google Play Store.

I hope this guide helps you transition from Firebase Dynamic Links to Branch.io smoothly. If you have any questions or run into issues, feel free to ask here!

Go to Branch.io dashboard, create a quick link and test after you deploy the app on a real device (test flight will work)

15
37 replies