Documentation

BetaFlox Documentation

Welcome to the official BetaFlox documentation. Whether you are a developer looking to meet Google Play's testing requirements or a tester looking to earn rewards, you'll find everything you need here.


For Developers

As an indie developer, launching an app on Google Play is fraught with challenges. BetaFlox is specifically designed to solve the two biggest developer pain points: Google Play's rigorous "closed testing" requirements and post-launch app discovery.

The 14-Day Testing Hurdle

Google Play requires developers to have their apps tested in a closed beta test by 12 real testers for 14 consecutive days before the app can be published to production.

Why is this so difficult? Without a tangible reward, friends and generic testers usually abandon the app after a few days and rarely provide constructive criticism. BetaFlox solves this by providing engaged testers bound by an incentivized structure.

Campaign Setup Workflow

1. Create Your Campaign

Start by creating a campaign in the BetaFlox dashboard for the specific application you need tested. Provide the app icon, app name, and exact package name accurately.

2. Draft Mode & IDs

Your campaign will remain in "draft mode" initially. The platform will automatically generate a unique Developer ID and Campaign ID for you.

3. Setup Google Play Closed Beta

Upload your app (with the integrated BetaFlox SDK) to the Google Play Console to create a closed beta test track. Crucially, you must add the BetaFlox Google Group email address to your testers list in the Play Console.

4. Publish

Once Google approves your closed beta release, return to the BetaFlox dashboard and click Publish Campaign. Our testers will immediately be notified and join the 14-day cycle.

The Growth Program Edge

Alternatively, joining the Growth Program gives you access to an expanded pool of testers, allowing your app to achieve 100 or 200 real app downloads.

More than just meeting the Google Play 14-day requirement, the higher user volume ensures genuine engagement for a month or more. This influx of engagement signals positive velocity to Google Play's discovery algorithm right out of the gate.


Android SDK Integration Guide

📦 Version 1.0.23

Ready-to-Use AI Agent Prompt (Android)

Copy & paste into Antigravity, Claude, ChatGPT, or Cursor to auto-integrate

Please integrate the BetaFlox SDK into this native Android project using the Developer API Key and Campaign ID provided below:

1. Repository Configuration (settings.gradle.kts or root build.gradle.kts):
   - Ensure the JitPack repository is present in dependencyResolutionManagement.repositories:
     maven { url = uri("https://jitpack.io") }

2. Add Dependency (app/build.gradle.kts):
   - Add the BetaFlox SDK dependency to dependencies block:
     implementation("com.github.skihal43:BetaFlox-SDK:1.0.23")

3. Android Manifest & Jetifier (AndroidManifest.xml & gradle.properties):
   - Register your custom Application class in AndroidManifest.xml: <application android:name=".InitApp">
   - Ensure AndroidX and Jetifier are enabled in gradle.properties:
     android.useAndroidX=true
     android.enableJetifier=true

4. Initialize SDK in Application Class (InitApp.kt):
   - Import com.betaflox.sdk.BetaFloxSDK
   - Initialize BetaFloxSDK inside Application.onCreate():

     package com.temtem.app

     import android.app.Application
     import com.betaflox.sdk.BetaFloxSDK

     class InitApp : Application() {
         override fun onCreate() {
             super.onCreate()
             BetaFloxSDK.initialize(
                 context = this,
                 apiKey = "YOUR_DEVELOPER_API_KEY",
                 campaignId = "YOUR_CAMPAIGN_ID"
             )
         }
     }

Note: Replace "YOUR_DEVELOPER_API_KEY" and "YOUR_CAMPAIGN_ID" with your actual Developer API Key and Campaign ID from the BetaFlox Dashboard.
1

Configure Repository

In your application's settings.gradle.kts (or root build.gradle.kts), ensure JitPack is added to your dependency resolution management:

settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

JitPack Repository Line

maven { url = uri("https://jitpack.io") }
2

Add Dependency

Add the dependency to your app module's build configuration:

build.gradle.kts (App Module)
dependencies {
    implementation("com.github.skihal43:BetaFlox-SDK:1.0.23")
}

SDK Dependency Line

implementation("com.github.skihal43:BetaFlox-SDK:1.0.23")
3

Initialization

Initialize the SDK in your custom Application class. No manual lifecycle tracking required!

InitApp.kt
import com.betaflox.sdk.BetaFloxSDK

class InitApp : Application() {
    override fun onCreate() {
        super.onCreate()
        
        BetaFloxSDK.initialize(
            context = this,
            apiKey = "YOUR_API_KEY",
            campaignId = "YOUR_CAMPAIGN_ID"
         )
    }
}

SDK Import

import com.betaflox.sdk.BetaFloxSDK

SDK Initialization

BetaFloxSDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
campaignId = "YOUR_CAMPAIGN_ID"
)

Manifest Merge Conflicts

If you encounter a manifest merge error (`Attribute application@appComponentFactory`), it means your project needs to migrate older support libraries to AndroidX using Jetifier.

Enable Jetifier in your gradle.properties:

gradle.properties
android.useAndroidX=true
# Enable Jetifier to migrate 3rd party libraries
android.enableJetifier=true

AndroidX Setting

android.useAndroidX=true

Jetifier Setting

android.enableJetifier=true

Flutter SDK Integration Guide

📦 Version 1.0.23 (Flutter Bridge)⚡ Deferred Post-Render Engine

Seamlessly integrate the BetaFlox SDK into any Flutter project with zero logo freeze, accurate session tracking, zero data corruption, and maximum performance across all Android devices (including Huawei/EMUI).

Analysis of Potential Integration Issues & Solutions

Logo Screen Freeze (8–19s Hang)

Prevented

Synchronous Play Integrity initialization during process start or ContentProvider IPC calls on main thread to frozen companion app.

Solution: Defer BetaFloxSDK.initialize() until onFlutterUiDisplayed() and pre-inject tester ID into launch intent.

Missing sdk_events (0 Heartbeats)

Prevented

Initializing SDK after Android lifecycle events causes the internal lifecycle tracker to miss the initial foreground transition.

Solution: Manually invoke onActivityStarted & onActivityResumed via reflection after post-render initialization.

Tester ID Mismatches

Prevented

Async device-hash resolution queries overwrite valid tester ID with an old/anonymous ID 500ms after boot.

Solution: Enforce valid testerId pre-binding before starting lifecycle timers with delayed override protection.

Unbound Launch Pollution

Prevented

Unbound user opening app directly creates anonymous session documents and pollutes Firestore database.

Solution: Keep SDK dormant on direct launches until a valid betaflox_tester_id is present in extras/prefs.

SDK Stops Sending Events After Switching to BetaFlox App ⚠️

Prevented

Switching to BetaFlox companion app fires onActivityStopped on the SDK lifecycle tracker, permanently stopping heartbeats because resume-from-background only fires onActivityResumed (not onActivityStarted).

Solution: Override onResume() to call reactivateSdkLifecycleTracker() which manually fires onActivityStarted + onActivityResumed on the tracker.

Simple 5-Step Integration Summary

1

Step 1: Add Repository & Dependency

Add JitPack to android/settings.gradle.kts and add implementation("com.github.skihal43:BetaFlox-SDK:1.0.23") to android/app/build.gradle.kts.

2

Step 2: Configure AndroidManifest.xml

Set android:name=".InitApp" on <application> and add deep link intent filters (betaflox:// and https://betaflox.app/join).

3

Step 3: Create Clean InitApp.kt

Create InitApp.kt extending Application. Keep onCreate() 100% clean with zero SDK calls so cold boot is instant (<20ms).

4

Step 4: Create MainActivity.kt & BetaFloxChannelHost.kt

Defer SDK initialization to onFlutterUiDisplayed(), activate lifecycleTracker manually when bound to valid tester ID, override onResume() to reactivate tracker after app switching, and register Method Channel bridge com.yourcompany.yourapp/betaflox.

5

Step 5: Add betaflox_service.dart & Bind Login

Add BetaFloxService in Dart and call BetaFloxService.instance.setTesterId(user.uid) when the user logs in.

Ready-to-Use AI Agent Prompt

Copy & paste into Antigravity, Claude, ChatGPT, or Cursor to auto-integrate

Please integrate the BetaFlox SDK into this Flutter project following these exact technical rules to prevent logo screen freezes, process hangs, tester ID mismatches, and SDK heartbeat interruptions when switching between apps:

1. Dependencies:
   - Ensure JitPack repository (`maven { url = uri("https://jitpack.io") }`) is present in `android/settings.gradle.kts`.
   - Add `implementation("com.github.skihal43:BetaFlox-SDK:1.0.23")` in `android/app/build.gradle.kts`.

2. Android Manifest (`android/app/src/main/AndroidManifest.xml`):
   - Set `android:name=".InitApp"` in `<application>`.
   - Add intent filters for `betaflox://` custom scheme and `https://betaflox.app/join` URLs.

3. Deferred Initialization (`InitApp.kt`):
   - Create `InitApp : Application()`. Keep `onCreate()` completely free of SDK calls so Android process startup is 100% clean (<20ms).

4. Post-Render SDK Hook (`MainActivity.kt`):
   - Override `onFlutterUiDisplayed()`. Initialize `BetaFloxSDK` inside a background coroutine AFTER Flutter has rendered its first frame on screen.
   - Resolve `validTesterId` from launch intent extras, deep links, or saved preferences.
   - If a valid `testerId` exists: initialize SDK, call `BetaFloxSDK.setTesterId(validTesterId)`, and manually notify `lifecycleTracker` (`onActivityStarted` & `onActivityResumed`) via reflection so heartbeat timers start logging `sdk_events`.
   - If no valid `testerId` exists, keep the SDK dormant to prevent anonymous Firestore writes.
   - IMPORTANT: Override `onResume()` to call `reactivateSdkLifecycleTracker()` every time the user returns to the app. This fixes the issue where switching to the BetaFlox companion app stops the SDK's heartbeat manager permanently. The helper must manually call `onActivityStarted` and `onActivityResumed` on the SDK's internal lifecycle tracker via reflection.

5. Method Channel Host (`BetaFloxChannelHost.kt`):
   - Register MethodChannel `com.yourcompany.yourapp/betaflox` and EventChannel `com.yourcompany.yourapp/betaflox_verification`.
   - Implement `setTesterId` to save `tester_id` in `betaflox_prefs` SharedPreferences and trigger `BetaFloxSDK.syncNow()`.

6. Dart Service (`lib/core/services/betaflox_service.dart`):
   - Create singleton service exposing `setTesterId(uid)`, `getTesterId()`, `syncNow()`, and `verificationStateStream`.
   - Call `BetaFloxService.instance.setTesterId(user.uid)` in your authentication flow whenever a user logs in or resumes session.

Complete Ready-to-Copy Source Code Files

Click "Extend Code View" to expand long files
android/app/src/main/kotlin/.../InitApp.kt
package com.temtem.app

import android.app.Application
import android.util.Log
import kotlinx.coroutines.CompletableDeferred

class InitApp : Application() {
    companion object {
        val sdkReady = CompletableDeferred<Unit>()
    }

    override fun onCreate() {
        super.onCreate()
        Log.i("InitApp", "Application startup clean (SDK initialization deferred to post-Flutter UI display).")
    }
}
android/app/src/main/kotlin/.../MainActivity.kt
package com.temtem.app

import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import com.betaflox.sdk.BetaFloxSDK
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class MainActivity : FlutterActivity() {

    private var betaFloxHost: BetaFloxChannelHost? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        injectTesterIdIntoIntent(intent)
        super.onCreate(savedInstanceState)
    }

    override fun onFlutterUiDisplayed() {
        super.onFlutterUiDisplayed()
        Log.i("BFX", "onFlutterUiDisplayed: Flutter UI rendered!")

        CoroutineScope(Dispatchers.IO).launch {
            try {
                val validTesterId = resolveValidTesterId(intent)

                if (validTesterId != null) {
                    val prefs = getSharedPreferences("betaflox_prefs", Context.MODE_PRIVATE)
                    prefs.edit().putString("tester_id", validTesterId).apply()

                    if (!InitApp.sdkReady.isCompleted) {
                        BetaFloxSDK.initialize(
                            context    = applicationContext,
                            apiKey     = "YOUR_API_KEY",
                            campaignId = "YOUR_CAMPAIGN_ID"
                        )
                        InitApp.sdkReady.complete(Unit)
                    }

                    BetaFloxSDK.setTesterId(validTesterId)
                    intent.putExtra("betaflox_tester_id", validTesterId)

                    // Manually notify lifecycleTracker so heartbeatManager starts logging sdk_events
                    try {
                        val field = BetaFloxSDK::class.java.getDeclaredField("lifecycleTracker")
                        field.isAccessible = true
                        val tracker = field.get(BetaFloxSDK) as? android.app.Application.ActivityLifecycleCallbacks
                        tracker?.onActivityStarted(this@MainActivity)
                        tracker?.onActivityResumed(this@MainActivity)
                    } catch (_: Exception) {}

                    BetaFloxSDK.handleLaunchIntent(intent)
                    BetaFloxSDK.handleDeepLink(intent)
                } else {
                    Log.i("BFX", "Direct launch before BetaFlox binding -> SDK remains dormant.")
                }
            } catch (e: Exception) {
                Log.e("BFX", "onFlutterUiDisplayed error: ${e.message}", e)
            }
        }

        tryBindFromClipboard()
    }

    override fun onResume() {
        super.onResume()
        // Re-activate SDK lifecycle tracker each time the user returns to the app.
        // Without this, switching to the BetaFlox companion app fires onActivityStopped
        // on the SDK tracker, permanently stopping heartbeats. Calling reactivateSdkLifecycleTracker()
        // here ensures sdk_events resume immediately every time the user comes back.
        reactivateSdkLifecycleTracker()
        tryBindFromClipboard()
    }

    override fun onStop() {
        super.onStop()
        Log.i("BFX", "onStop: User switched away (e.g., to BetaFlox). Heartbeats will resume on return.")
    }

    /**
     * Re-activates the SDK's internal AppLifecycleTracker.
     *
     * Problem (Issue #5): When the user switches from the tested app to the BetaFlox companion
     * app (or any other app), Android fires onActivityStopped on the SDK's registered
     * ActivityLifecycleCallbacks, which calls heartbeatManager.stop() + sessionManager.endSession().
     * When the user returns, Android only fires onActivityResumed — but heartbeatManager.start()
     * is only triggered from onActivityStarted, which is never re-fired for a resume-from-background.
     *
     * Solution: Manually invoke onActivityStarted + onActivityResumed on the tracker on every
     * onResume() so heartbeats restart immediately whenever the user returns to the app.
     */
    private fun reactivateSdkLifecycleTracker() {
        if (!InitApp.sdkReady.isCompleted) return
        val boundId = resolveValidTesterId(intent)
        if (boundId.isNullOrBlank()) return
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val field = BetaFloxSDK::class.java.getDeclaredField("lifecycleTracker")
                field.isAccessible = true
                val tracker = field.get(BetaFloxSDK) as? android.app.Application.ActivityLifecycleCallbacks
                tracker?.onActivityStarted(this@MainActivity)
                tracker?.onActivityResumed(this@MainActivity)
                Log.i("BFX", "reactivateSdkLifecycleTracker: heartbeats resumed for testerId=${BetaFloxSDK.getTesterId()}")
            } catch (e: Exception) {
                Log.w("BFX", "reactivateSdkLifecycleTracker: ${e.message}")
            }
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        betaFloxHost = BetaFloxChannelHost(flutterEngine, applicationContext).apply { register() }
    }

    private fun resolveValidTesterId(intent: Intent?): String? {
        if (intent != null && intent.hasExtra("betaflox_tester_id")) {
            val extraId = intent.getStringExtra("betaflox_tester_id")
            if (!extraId.isNullOrBlank() && extraId != "pending_binding") return extraId
        }
        val prefs = getSharedPreferences("betaflox_prefs", Context.MODE_PRIVATE)
        val storedId = prefs.getString("tester_id", null)
        if (!storedId.isNullOrBlank() && storedId != "pending_binding") return storedId
        return null
    }

    private fun injectTesterIdIntoIntent(intent: Intent?) {
        if (intent == null) return
        val validId = resolveValidTesterId(intent)
        if (!validId.isNullOrBlank()) intent.putExtra("betaflox_tester_id", validId)
    }

    private fun tryBindFromClipboard() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                InitApp.sdkReady.await()
                if (!BetaFloxSDK.getTesterId().isNullOrBlank()) return@launch
                val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return@launch
                val text = clipboard.primaryClip?.getItemAt(0)?.coerceToText(this@MainActivity)?.toString()?.trim() ?: return@launch
                if (!text.contains("token=")) return@launch
                BetaFloxSDK.handleDeepLink(Intent(Intent.ACTION_VIEW, Uri.parse(text)))
            } catch (_: Exception) {}
        }
    }
}
android/app/src/main/kotlin/.../BetaFloxChannelHost.kt
package com.temtem.app

import android.util.Log
import com.betaflox.sdk.BetaFloxSDK
import com.betaflox.sdk.VerificationState
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class BetaFloxChannelHost(
    private val flutterEngine: FlutterEngine,
    private val context: android.content.Context
) {
    companion object {
        const val CHANNEL = "com.yourcompany.yourapp/betaflox"
        const val EVENT_CHANNEL = "com.yourcompany.yourapp/betaflox_verification"
    }

    private var verificationSink: EventChannel.EventSink? = null

    fun register() {
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result -> handle(call, result) }

        EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
            .setStreamHandler(object : EventChannel.StreamHandler {
                override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                    verificationSink = events
                    collectVerificationState()
                }
                override fun onCancel(arguments: Any?) { verificationSink = null }
            })
    }

    private fun collectVerificationState() {
        CoroutineScope(Dispatchers.Main).launch {
            try {
                BetaFloxSDK.verificationState.collectLatest { state ->
                    val map = when (state) {
                        is VerificationState.Pending  -> mapOf("state" to "pending")
                        is VerificationState.Complete -> mapOf("state" to "complete", "verdict" to state.verdict.verdict, "riskScore" to state.verdict.riskScore)
                        is VerificationState.Failed   -> mapOf("state" to "failed", "reason" to state.reason)
                    }
                    verificationSink?.success(map)
                }
            } catch (_: Exception) {}
        }
    }

    private fun handle(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "initialize" -> {
                val apiKey     = call.argument<String>("apiKey")     ?: "YOUR_API_KEY"
                val campaignId = call.argument<String>("campaignId") ?: "YOUR_CAMPAIGN_ID"
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        BetaFloxSDK.initialize(context.applicationContext, apiKey, campaignId)
                        InitApp.sdkReady.complete(Unit)
                        withContext(Dispatchers.Main) { result.success(null) }
                    } catch (e: Exception) {
                        withContext(Dispatchers.Main) { result.error("INIT_FAILED", e.message, null) }
                    }
                }
            }

            "setTesterId" -> {
                val id = call.argument<String>("testerId") ?: return result.error("ARGS", "testerId required", null)
                if (id.isNotBlank() && id != "pending_binding") {
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            if (!InitApp.sdkReady.isCompleted) {
                                BetaFloxSDK.initialize(context.applicationContext, "YOUR_API_KEY", "YOUR_CAMPAIGN_ID")
                                InitApp.sdkReady.complete(Unit)
                            }
                            BetaFloxSDK.setTesterId(id)
                            context.getSharedPreferences("betaflox_prefs", android.content.Context.MODE_PRIVATE).edit().putString("tester_id", id).apply()
                            BetaFloxSDK.syncNow()
                            withContext(Dispatchers.Main) { result.success(null) }
                        } catch (e: Exception) {
                            withContext(Dispatchers.Main) { result.error("SET_TESTER_FAILED", e.message, null) }
                        }
                    }
                } else {
                    result.success(null)
                }
            }

            "getTesterId" -> result.success(BetaFloxSDK.getTesterId())
            "syncNow" -> {
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        BetaFloxSDK.syncNow()
                        withContext(Dispatchers.Main) { result.success(null) }
                    } catch (e: Exception) {
                        withContext(Dispatchers.Main) { result.error("SYNC_FAILED", e.message, null) }
                    }
                }
            }
            else -> result.notImplemented()
        }
    }
}
lib/core/services/betaflox_service.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

sealed class VerificationState {
  const VerificationState();
}

class VerificationPending extends VerificationState {
  const VerificationPending();
}

class VerificationComplete extends VerificationState {
  final String verdict;
  final double riskScore;
  const VerificationComplete({required this.verdict, required this.riskScore});
}

class VerificationFailed extends VerificationState {
  final String reason;
  const VerificationFailed({required this.reason});
}

class BetaFloxService {
  static const MethodChannel _channel = MethodChannel('com.yourcompany.yourapp/betaflox');
  static const EventChannel _eventChannel = EventChannel('com.yourcompany.yourapp/betaflox_verification');

  static final BetaFloxService instance = BetaFloxService._();
  BetaFloxService._();

  Stream<VerificationState>? _verificationStream;

  Future<void> setTesterId(String testerId) async {
    try {
      await _channel.invokeMethod('setTesterId', {'testerId': testerId});
    } on PlatformException catch (e) {
      debugPrint('BetaFlox setTesterId error: ${e.message}');
    }
  }

  Future<String?> getTesterId() async {
    try {
      return await _channel.invokeMethod<String>('getTesterId');
    } on PlatformException catch (e) {
      return null;
    }
  }

  Future<void> syncNow() async {
    try {
      await _channel.invokeMethod('syncNow');
    } on PlatformException catch (_) {}
  }

  Stream<VerificationState> get verificationStateStream {
    _verificationStream ??= _eventChannel.receiveBroadcastStream().map((dynamic event) {
      final map = Map<String, dynamic>.from(event as Map);
      final stateStr = map['state'] as String?;
      return switch (stateStr) {
        'complete' => VerificationComplete(
            verdict: (map['verdict'] as String?) ?? 'VALID',
            riskScore: ((map['riskScore'] as num?) ?? 0.0).toDouble(),
          ),
        'failed' => VerificationFailed(
            reason: (map['reason'] as String?) ?? 'Unknown error',
          ),
        _ => const VerificationPending(),
      };
    });
    return _verificationStream!;
  }
}

Unity SDK Integration

Download Unity SDK
📦 Version 1.0.23

Welcome to the BetaFlox SDK integration guide for Unity! This tutorial will walk you through the process of adding the BetaFlox SDK to your Unity game or application.

Required: The minimum API Level is 24 for the Unity BetaFlox SDK to work without errors.

1

Import the SDK into Unity

  1. Open your project in the Unity Editor (2021.3 LTS or newer recommended).
  2. In the top menu, navigate to Assets > Import Package > Custom Package...
  3. Locate and select the downloaded BetaFloxSDK.unitypackage.
  4. The Import Unity Package window will appear. Leave all files selected and click Import.

This will add the BetaFlox folder to your Assets directory, containing the necessary C# wrappers, native plugins, and editor scripts.

2

Resolve Android Dependencies

The BetaFlox SDK relies on certain native Android libraries. To ensure these are correctly included in your build, use the External Dependency Manager for Unity (EDM4U):

  1. Go to Assets > External Dependency Manager > Android Resolver > Resolve.
  2. Wait for the resolution process to complete.
3

Initialize the BetaFlox SDK

You must initialize the SDK before using any of its features. The easiest way is using the built-in BetaFloxManager component.

  1. Create an empty GameObject in your first scene (e.g., name it [BetaFlox]).
  2. Add the BetaFloxManager component to it via Add Component → Scripts → BetaFloxManager.
  3. In the Inspector, fill in your Developer ID and Campaign ID from the BetaFlox Dashboard.

Alternatively, initialize it in code:

MyGameBootstrap.cs
using BetaFlox;

var config = new BetaFloxConfig.Builder("YOUR_DEVELOPER_ID", "YOUR_CAMPAIGN_ID")
    .SetLogLevel(BetaFloxLogLevel.Debug)
    .Build();

BetaFloxSDK.Initialize(config);
4

Test the Integration

  1. Build your project for Android (File > Build Settings > Android > Build and Run).
  2. Check the Unity logcat output for the success message: BetaFlox SDK Initialized Successfully!

If initialization fails, ensure your Developer ID and Campaign ID are correct and that the device has internet access.


For Testers

As a BetaFlox Tester, you get paid to help fix applications. The process requires commitment and consistency.

14 Days

The Commitment

You must test the application for at least 4 continuous minutes every single day for 14 consecutive days. Missing even one day instantly disqualifies you from the campaign.

Points

Reward Structure

Earn 10 points for every day you complete the test. Furthermore, earn an extra 2 points for every helpful, constructive comment you submit to the developer.

Strict Rules & Anti-Fraud

We guarantee developers real, actionable feedback. Therefore, any attempt to farm rewards or automate testing will result in an immediate and permanent ban.

No VPNs or Proxies

Using a VPN to mask location, claim points across regions, or bypass daily limits falsifies data and violates our terms.

No Emulators

Apps must be tested on real mobile devices to provide real performance data. BlueStacks, Nox Player, etc. are strictly prohibited.

No Multiple Accounts

You are permitted one BetaFlox account per person and per device. Bot-netting alt accounts triggers immediate flagging.

No Auto-Clickers

Scripting gameplay or automating daily 4-minute sessions ruins test integrity. Our SDK detects machine-like interactions instantly.

No Spam Feedback

Providing gibberish, copy-pasting Play Store reviews, or giving single-word "good app" responses to get points will result in rejection.

The Golden Rule

Test exactly as a normal user would, provide genuine and actionable feedback, check in daily for 14 days, and you will thrive securely on BetaFlox.