Skip to content

Android SBeacon Framework Initialization

Aigars Mališevs edited this page Feb 2, 2026 · 3 revisions

Initialization

SBeacon SDK v1.6.13

This guide covers SDK initialization, Application class setup, and permission handling.


Application Class Setup

Create an Application subclass to initialize the SDK:

package com.yourcompany.yourapp

import android.app.Application
import androidx.lifecycle.ProcessLifecycleOwner
import com.scope.common.SharedConstants
import com.scope.common.models.ConfigType
import com.scope.ibeacons.BLEManager
import com.scope.ibeacons.util.iBFPrefUtil
import timber.log.Timber

class MyApp : Application() {

    companion object {
        lateinit var instance: MyApp
            private set
    }

    override fun onCreate() {
        super.onCreate()
        instance = this

        // Optional: Setup logging
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
        }

        // Optional: Track app lifecycle
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)

        // CRITICAL: Only initialize SDK after permissions are granted
        if (allPermissionsGranted()) {
            configureBLEFramework()
        }
    }

    /**
     * Configures the BLE Framework SDK for SBeacon communication.
     * MUST be called only after all required permissions are granted.
     */
    fun configureBLEFramework() {
        Timber.i("Configuring BLE framework...")

        // Configure SDK preferences
        with(iBFPrefUtil.getInstance(applicationContext)) {
            // Set to SBeacon-only mode
            requiredBluetoothManagers = listOf("SBeaconManager")

            // Set configuration type
            saveString(SharedConstants.PREFERENCES_CONFIG_TYPE, ConfigType.BLE.name)
        }

        // Start foreground service (required for Android 8.0+ background scanning)
        BLEManager.retrieveInstance(applicationContext).startForegroundBluetoothService()
    }

    private fun allPermissionsGranted(): Boolean {
        // Implement permission check logic
        // See Permission Handling section below
        return false
    }
}

Important: Call configureBLEFramework() only after all permissions are granted. The SDK will crash if permissions are missing.


Key Configuration

Setting Value Description
requiredBluetoothManagers listOf("SBeaconManager") Configures SDK for SBeacon-only operation
ConfigType ConfigType.BLE Sets the configuration mode
startForegroundBluetoothService() - Required for continuous BLE scanning on Android 8.0+

Permission Handling

PermissionsUtil

Create a utility to determine required permissions based on Android version:

package com.yourcompany.yourapp.utils

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat

object PermissionsUtil {

    /**
     * Returns batches of permissions that haven't been granted yet.
     * Permissions are batched for sequential requesting.
     */
    fun getNotGrantedPermissions(context: Context): List<Array<String>> {
        val batches = mutableListOf<Array<String>>()

        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {  // Android 13+
                // Batch 1: Fine location
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
                }

                // Batch 2: Bluetooth permissions
                val bluetoothPerms = mutableListOf<String>()
                if (!isPermissionGranted(context, Manifest.permission.BLUETOOTH_SCAN)) {
                    bluetoothPerms.add(Manifest.permission.BLUETOOTH_SCAN)
                }
                if (!isPermissionGranted(context, Manifest.permission.BLUETOOTH_CONNECT)) {
                    bluetoothPerms.add(Manifest.permission.BLUETOOTH_CONNECT)
                }
                if (bluetoothPerms.isNotEmpty()) {
                    batches.add(bluetoothPerms.toTypedArray())
                }

                // Batch 3: Background location
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
                }

                // Batch 4: Notifications
                if (!isPermissionGranted(context, Manifest.permission.POST_NOTIFICATIONS)) {
                    batches.add(arrayOf(Manifest.permission.POST_NOTIFICATIONS))
                }
            }

            Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {  // Android 12
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
                }
                val bluetoothPerms = mutableListOf<String>()
                if (!isPermissionGranted(context, Manifest.permission.BLUETOOTH_SCAN)) {
                    bluetoothPerms.add(Manifest.permission.BLUETOOTH_SCAN)
                }
                if (!isPermissionGranted(context, Manifest.permission.BLUETOOTH_CONNECT)) {
                    bluetoothPerms.add(Manifest.permission.BLUETOOTH_CONNECT)
                }
                if (bluetoothPerms.isNotEmpty()) {
                    batches.add(bluetoothPerms.toTypedArray())
                }
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
                }
            }

            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {  // Android 11
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
                }
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
                }
            }

            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {  // Android 10
                val perms = mutableListOf<String>()
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
                    perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
                }
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
                    perms.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
                }
                if (perms.isNotEmpty()) {
                    batches.add(perms.toTypedArray())
                }
            }

            else -> {  // Android 8.1-9
                if (!isPermissionGranted(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
                    batches.add(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
                }
            }
        }

        return batches
    }

    private fun isPermissionGranted(context: Context, permission: String): Boolean {
        return ContextCompat.checkSelfPermission(context, permission) ==
            PackageManager.PERMISSION_GRANTED
    }
}

PermissionHandler

Orchestrates sequential permission requests:

package com.yourcompany.yourapp.utils

import android.app.Activity
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ActivityCompat

class PermissionHandler(
    private val activity: Activity,
    private val launcher: ActivityResultLauncher<Array<String>>,
    private val onAllGranted: () -> Unit,
    private val onDenied: (Array<String>) -> Unit,
    private val onPermanentlyDenied: (String) -> Unit
) {
    private var permissionQueue: MutableList<Array<String>> = mutableListOf()
    var isRequesting = false
        private set

    fun start(permissionBatches: List<Array<String>>) {
        if (isRequesting) return

        permissionQueue.clear()
        permissionQueue.addAll(permissionBatches)
        isRequesting = true

        requestNextBatch()
    }

    private fun requestNextBatch() {
        if (permissionQueue.isEmpty()) {
            isRequesting = false
            onAllGranted()
            return
        }

        val batch = permissionQueue.removeAt(0)
        launcher.launch(batch)
    }

    fun onResult(permissions: Array<String>, results: BooleanArray) {
        val deniedPermissions = permissions.filterIndexed { index, _ ->
            !results[index]
        }.toTypedArray()

        if (deniedPermissions.isEmpty()) {
            requestNextBatch()
        } else {
            val permanentlyDenied = deniedPermissions.firstOrNull { permission ->
                !ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
            }

            isRequesting = false

            if (permanentlyDenied != null) {
                onPermanentlyDenied(permanentlyDenied)
            } else {
                onDenied(deniedPermissions)
            }
        }
    }
}

Fragment Integration

class BeaconsFragment : Fragment() {

    private val permissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { results ->
        permissionHandler.onResult(
            results.keys.toTypedArray(),
            results.values.toBooleanArray()
        )
    }

    private val permissionHandler by lazy {
        PermissionHandler(
            activity = requireActivity(),
            launcher = permissionLauncher,
            onAllGranted = {
                // Initialize SDK
                (requireActivity().application as MyApp).configureBLEFramework()
                startBeaconsScan()
            },
            onDenied = { deniedPermissions ->
                showPermissionRationaleDialog(deniedPermissions)
            },
            onPermanentlyDenied = { permission ->
                showGoToSettingsDialog(permission)
            }
        )
    }

    override fun onResume() {
        super.onResume()

        if (permissionHandler.isRequesting) return

        val notGranted = PermissionsUtil.getNotGrantedPermissions(requireContext())
        if (notGranted.isEmpty()) {
            startBeaconsScan()
        } else {
            permissionHandler.start(notGranted)
        }
    }
}

Why Sequential Requests? Android 11+ requires background location to be requested separately. Sequential requests avoid overwhelming users and allow providing context for each permission group.


Next Steps

Clone this wiki locally