com_tapp_so

com_tapp_so provides the official Flutter integration for Tapp, giving Android and iOS apps a single Dart API for SDK initialization, deep-link attribution, tracked URL generation, and event reporting.

Use this package to decide whether incoming links should be processed by Tapp, retrieve resolved link payloads, and handle deferred-link callbacks from the native SDKs.

Prerequisites

  • Flutter >=3.3.0
  • Dart ^3.5.3
  • Tapp credentials for your app:
    • authToken
    • tappToken
  • Android app with minSdk 24 and compileSdk 34+
  • iOS app with deployment target 13.0+

Installation

1) Add dependency

dependencies:
  com_tapp_so: ^1.1.0
flutter pub get

2) Android setup (required)

This plugin depends on Tapp Android artifacts published through JitPack.

Add JitPack to your Android repositories:

// android/settings.gradle (or project-level repositories)
dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven { url 'https://jitpack.io' }
  }
}

Ensure your Android app build config is compatible:

// android/app/build.gradle
android {
  compileSdkVersion 34

  defaultConfig {
    minSdkVersion 24
  }

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

  kotlinOptions {
    jvmTarget = '1.8'
  }
}

3) iOS setup

Set iOS deployment target to 13.0+:

# ios/Podfile
platform :ios, '13.0'

Install and refresh pods:

cd ios
pod repo update
pod install

To enable incoming link handling, complete both setup tracks:

  1. Configure your app in the Tapp dashboard.
  2. Enable Universal Links / App Links in your native project.

Note: After your application is configured in the Tapp dashboard, Tapp manages the association files required for verification: apple-app-site-association (iOS) and assetlinks.json (Android). You do not host these files yourself.

1. Configure your application in the Tapp dashboard

General fields:

  • Application Name
  • Android App Identifier
  • iOS Bundle Identifier
  • Apple App Store ID

When Android deep linking is enabled:

  • Enable Android Deep Linking
  • SHA-256 Certificate Fingerprint
  • Android App Scheme

When iOS universal linking is enabled:

  • Enable iOS Universal Linking
  • App ID Prefix
  • iOS App Scheme

These values must match your real app configuration:

  • Android App Identifier -> Android application ID (for example com.example.app)
  • SHA-256 Certificate Fingerprint -> signing certificate fingerprint used for the distributed build
  • Android App Scheme -> custom scheme configured in your Android manifest (if used)
  • iOS Bundle Identifier -> bundle identifier configured in Xcode
  • Apple App Store ID -> published App Store app ID, when applicable
  • App ID Prefix -> Apple Team ID / App ID prefix used with Associated Domains
  • iOS App Scheme -> URL Types scheme configured in Xcode (if used)

In Xcode, open your app target and enable Associated Domains.

Add your Tapp-managed link domain:

applinks:your-tapp-link-domain.com

Ensure your iOS Bundle Identifier and App ID Prefix match dashboard values.

If custom URL schemes are part of your flow, ensure iOS App Scheme matches URL Types.

For reliable Universal Link validation, test on a physical device.

After dashboard setup, Tapp serves the required apple-app-site-association file for your configured link domain.

Add an intent filter to the receiving activity (usually the launcher activity) in android/app/src/main/AndroidManifest.xml:

<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-tapp-link-domain.com" />
</intent-filter>

If needed, restrict matching using attributes such as android:pathPrefix.

Ensure these values align with dashboard configuration:

  • Android App Identifier matches your Android application ID
  • SHA-256 Certificate Fingerprint matches the certificate used to sign your app
  • Android App Scheme matches manifest URL-scheme configuration (if used)

After dashboard setup, Tapp serves assetlinks.json for your configured link domain.

Use shouldProcess before resolving incoming links, then fetch link data when applicable:

import 'package:flutter/foundation.dart';
import 'package:com_tapp_so/com_tapp_so.dart';

final sdk = ComTappSo();

Future<void> handleIncomingLink(String incomingUrl) async {
  if (await sdk.shouldProcess(incomingUrl)) {
    final data = await sdk.fetchLinkData(incomingUrl);
    debugPrint('Resolved link data: $data');
  }

  final originData = await sdk.fetchOriginLinkData();
  debugPrint('Origin link data: $originData');
}

Integration Checklist

  • Add package dependency and run flutter pub get
  • Add JitPack to Android repositories
  • Confirm Android minSdk 24 and compileSdk 34+
  • Confirm iOS deployment target 13.0+
  • Configure dashboard application identifiers and link fields
  • Enable iOS Associated Domains and Android App Links
  • Initialize SDK once during app startup
  • Run smoke checks from Verify Integration

Minimal Integration

Use this as a minimum working startup flow.

import 'package:flutter/widgets.dart';
import 'package:com_tapp_so/com_tapp_so.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final sdk = ComTappSo();
  await sdk.start(
    authToken: 'YOUR_AUTH_TOKEN',
    env: EnvironmentType.SANDBOX,
    tappToken: 'YOUR_TAPP_TOKEN',
  );

  runApp(const Placeholder());
}

Verify Integration

After start(...), run a short smoke test:

final version = await sdk.getPlatformVersion();
final shouldHandle = await sdk.shouldProcess('https://example.com');

print('Platform version: $version');
print('shouldProcess(example): $shouldHandle');

Expected results:

  • getPlatformVersion() returns a non-empty string
  • shouldProcess(...) returns true or false without throwing

Use this pattern when you want production-friendly startup behavior instead of calling start(...) directly in main(). It centralizes initialization, attaches listeners once, supports optional cold-start link handling, and exposes loading, ready, and error state.

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:com_tapp_so/com_tapp_so.dart';

enum TappBootstrapState { idle, loading, ready, error }

class TappBootstrapController {
  TappBootstrapController({ComTappSo? sdk}) : _sdk = sdk ?? ComTappSo();

  final ComTappSo _sdk;
  final ValueNotifier<TappBootstrapState> state =
      ValueNotifier(TappBootstrapState.idle);

  Object? lastError;
  Future<void>? _inFlight;
  bool _started = false;
  StreamSubscription<DeferredDeepLinkData>? _deferredSub;
  StreamSubscription<FailResolveData>? _failSub;

  Future<void> initialize({String? initialLink}) {
    return _inFlight ??= _initializeInternal(initialLink: initialLink)
        .whenComplete(() => _inFlight = null);
  }

  Future<void> _initializeInternal({String? initialLink}) async {
    if (_started) {
      state.value = TappBootstrapState.ready;
      return;
    }

    state.value = TappBootstrapState.loading;
    try {
      await _sdk.start(
        authToken: 'YOUR_AUTH_TOKEN',
        env: EnvironmentType.PRODUCTION,
        tappToken: 'YOUR_TAPP_TOKEN',
      );

      _deferredSub ??= _sdk.onDeferredDeepLink.listen((event) {
        debugPrint('Deferred link: $event');
      });
      _failSub ??= _sdk.onFailResolvingUrl.listen((event) {
        debugPrint('Deferred-link resolve failed: $event');
      });

      if (initialLink != null && await _sdk.shouldProcess(initialLink)) {
        final data = await _sdk.fetchLinkData(initialLink);
        debugPrint('Cold-start link data: $data');
      }

      _started = true;
      state.value = TappBootstrapState.ready;
    } catch (e, st) {
      lastError = e;
      debugPrint('Tapp bootstrap failed: $e\n$st');
      state.value = TappBootstrapState.error;
      rethrow;
    }
  }

  Future<void> dispose() async {
    await _deferredSub?.cancel();
    await _failSub?.cancel();
    state.dispose();
  }
}

Minimal widget wiring for bootstrap state:

ValueListenableBuilder<TappBootstrapState>(
  valueListenable: bootstrap.state,
  builder: (context, state, _) {
    switch (state) {
      case TappBootstrapState.loading:
      case TappBootstrapState.idle:
        return const Text('Starting SDK...');
      case TappBootstrapState.error:
        return Text('Startup failed: ${bootstrap.lastError}');
      case TappBootstrapState.ready:
        return const Placeholder();
    }
  },
);

Common Flows

Initialize SDK

final sdk = ComTappSo();
await sdk.start(
  authToken: 'YOUR_AUTH_TOKEN',
  env: EnvironmentType.PRODUCTION,
  tappToken: 'YOUR_TAPP_TOKEN',
);

Generate an affiliate URL

final url = await sdk.generateUrl(
  influencer: 'influencer_id',
  adGroup: 'campaign_group',
  creative: 'creative_a',
  data: {'source': 'flutter_app'},
);
final incoming = 'https://your-tapp-link-domain.com?t=565';

if (await sdk.shouldProcess(incoming)) {
  final data = await sdk.fetchLinkData(incoming);
  print('Link data: $data');
}

final originData = await sdk.fetchOriginLinkData();
print('Origin link data: $originData');

Track events

await sdk.handleTappEvent(
  eventAction: EventAction.tapp_purchase,
  metadata: {
    'orderId': 'A-1024',
    'value': 99.95,
    'currency': 'USD',
  },
);

await sdk.handleTappEvent(
  eventAction: EventAction.custom,
  customValue: 'my_custom_event',
  metadata: {'context': 'checkout'},
);
final deferredSub = sdk.onDeferredDeepLink.listen((event) {
  print('Deferred link: ${event.tappUrl}');
});

final failSub = sdk.onFailResolvingUrl.listen((event) {
  print('Resolve failed: ${event.url} -> ${event.error}');
});

// Later:
await deferredSub.cancel();
await failSub.cancel();

Inspect SDK config

com_tapp_so currently does not expose a public Dart getConfig() API. If you need to inspect runtime configuration, keep your initialization values in your own app state and log only redacted values.

Troubleshooting

Android build fails to resolve Tapp artifacts

  • Confirm JitPack is added to Android repositories (https://jitpack.io).
  • Confirm network access to JitPack from your build environment.
  • Re-run:
flutter clean
flutter pub get

iOS compile error: Extra argument 'metadata' in call

This usually indicates a mismatch between the plugin’s iOS bridge code and resolved native Tapp pod APIs.

Refresh your pod specs and reinstall pods:

cd ios
pod repo update
pod install

If your app intentionally pins older Tapp pod APIs, align the plugin version and native dependency versions before building.

ArgumentError when sending metadata

handleTappEvent(..., metadata: ...) should contain MethodChannel-serializable values only.

Use values like String, num, bool, null, List, and Map<String, dynamic> composed of those types. Avoid passing objects such as DateTime, enums, or custom classes directly.

MissingPluginException

  • Run a full app restart after adding/updating the plugin (not only hot reload).
  • Verify the plugin is included in pubspec.yaml and flutter pub get completed.
  • For iOS, run pod install from ios/ and rebuild.
  • Ensure you test on Android/iOS targets (desktop/web are not supported by this plugin).

License

This SDK is proprietary software. You must have a Tapp.so account to use it. See LICENSE for details.