BluetoothLeScanner — co to je, metody a BLE skenování v Androidu

Autor: IT Sectr Publikováno: 2026-07-16 Doba čtení: 10 min

BluetoothLeScanner — třída Androidu pro skenování Bluetooth Low Energy zařízení, dostupná od API 21 (Android 5.0). BluetoothLeScanner nahradil zastaralou metodu startLeScan na BluetoothAdapter, poskytující flexibilní API s konfigurací skenování (ScanSettings), filtrováním (ScanFilter) a podporou režimu na pozadí (PendingIntent). Instance se získává přes BluetoothAdapter.getBluetoothLeScanner(). Podle Android Developers, 2026, BluetoothLeScanner podporuje tři režimy spotřeby energie a umožňuje skenovat BLE reklamní pakety s filtrováním podle UUID služby, názvu zařízení nebo MAC adresy.

Hlavní body

  • BluetoothLeScanner — moderní Android API (API 21+) pro BLE skenování, náhrada zastaralého startLeScan
  • ScanSettings — konfigurace režimu skenování: LOW_POWER, BALANCED, LOW_LATENCY a typ callbacku
  • ScanFilter — filtrování výsledků podle UUID služby, názvu zařízení, MAC adresy, dat výrobce
  • ScanCallback — callback výsledků onScanResult, onBatchScanResults a onScanFailed s kódy chyb
  • PendingIntent — skenování na pozadí přes BroadcastReceiver, i když je aplikace na pozadí

Co je BluetoothLeScanner: podstata a získání instance

BluetoothLeScanner — systémová třída pro správu BLE skenování na Androidu. Na rozdíl od BluetoothAdapter.startLeScan(), který přijímá jednoduchý LeScanCallback, BluetoothLeScanner poskytuje objektově orientované API s nastaveními, filtry a rozšířeným zpracováním chyb. Třída se objevila v API 21 (Android 5.0) spolu s podporou BLE 4.2 a zůstává hlavním způsobem BLE skenování na všech moderních verzích Androidu.

Získání instance BluetoothLeScanner se provádí přes BluetoothAdapter.getBluetoothLeScanner(). Metoda vrací null, pokud Bluetooth adaptér není dostupný (Bluetooth vypnutý nebo zařízení nepodporuje BLE). Před získáním zkontrolujte BluetoothAdapter.isEnabled() a přítomnost FEATURE_BLUETOOTH_LE přes PackageManager. Po získání skeneru můžete spustit skenování v jakémkoli vlákně — Android sám plánuje BLE operace na interním vlákně Bluetooth stacku.

kotlin
// Získání BluetoothLeScanner
class BLEScannerManager(context: Context) {

    private val bluetoothManager: BluetoothManager =
        context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val adapter: BluetoothAdapter? = bluetoothManager.adapter
    private var scanner: BluetoothLeScanner? = null

    fun initScanner(): Boolean {
        // Zkontrolujte dostupnost BLE
        if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false
        }

        // Zkontrolujte zapnutý Bluetooth
        if (adapter?.isEnabled != true) {
            return false
        }

        // Získejte skener
        scanner = adapter?.bluetoothLeScanner
        return scanner != null
    }

    // Zkontrolujte dostupnost skeneru
    val isAvailable: Boolean
        get() = scanner != null

    // Spusťte základní skenování bez filtrů
    fun startBasicScan() {
        scanner?.startScan(object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                handleResult(result)
            }
        })
    }

    private fun handleResult(result: ScanResult) {
        val device = result.device
        print("Device: ${device.name ?: "Unnamed"}, RSSI: ${result.rssi}, address: ${device.address}")
    }
}

Třída BLEScannerManager demonstruje bezpečné získání a inicializaci BluetoothLeScanner. initScanner kontroluje přítomnost BLE přes hasSystemFeature, zapnutý Bluetooth a úspěšné získání skeneru. startBasicScan spouští skenování bez nastavení a filtrů — detekuje všechna BLE zařízení v dosahu. handleResult analyzuje ScanResult: BluetoothDevice (název, adresa), RSSI (úroveň signálu), scanRecord (reklamní data).

ScanSettings: režimy skenování a typ callbacku

ScanSettings — třída pro konfiguraci BLE skenování. Hlavní parametr — režim skenování (scanMode), který určuje kompromis mezi spotřebou energie a zpožděním detekce. ScanSettings.Builder umožňuje konfigurovat: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (zpoždění dávkového odesílání) a phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).

Tři režimy skenování: SCAN_MODE_LOW_POWER (0) — skenování na pozadí s nízkou spotřebou energie, zpoždění detekce několik sekund. SCAN_MODE_BALANCED (1) — vyvážený režim pro většinu scénářů. SCAN_MODE_LOW_LATENCY (2) — minimální zpoždění detekce (přibližně 100 ms), maximální spotřeba energie. Pro aktivní vyhledávání zařízení použijte LOW_LATENCY, pro monitorování na pozadí — LOW_POWER.

reportDelay — zpoždění v milisekundách před hromadným odesláním výsledků. Pokud reportDelay = 0, výsledky se odesílají ihned po detekci. Pokud > 0, Android hromadí výsledky a odesílá dávku přes onBatchScanResults. Dávkové odesílání snižuje počet callback volání a snižuje spotřebu energie, vhodné pro skenování na pozadí s nízkou prioritou.

kotlin
// Konfigurace ScanSettings pro různé scénáře
class ScanSettingsProvider {

    // 1. Rychlé skenování (aktivní vyhledávání)
    fun lowLatencyScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
            .setReportDelay(0)
            .setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
            .build()
    }

    // 2. Energeticky účinné skenování (monitorování na pozadí)
    fun lowPowerScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .setReportDelay(2000)  // dávka každé 2 sekundy
            .build()
    }

    // 3. BLE Long Range skenování (Coded PHY)
    fun longRangeScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_CODED)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .build()
    }

    // 4. Skenování pouze na 2M PHY (BLE 5.0+)
    fun highSpeedScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_2M)
            .build()
    }
}

Třída ScanSettingsProvider obsahuje typické konfigurace. lowLatencyScan — pro UI skenování (vyhledávání "tady a teď"). lowPowerScan — pro monitorování na pozadí s dávkou každé 2 sekundy a callbackType FIRST_MATCH (aktivuje se pouze při první detekci). longRangeScan používá PHY_LE_CODED (BLE Long Range, až 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, pouze BLE 5.0+ zařízení).

ScanFilter: filtrování BLE zařízení podle UUID a názvu

ScanFilter — třída pro filtrování výsledků BLE skenování. Bez filtru BluetoothLeScanner vrací všechna BLE zařízení v dosahu — v hustém BLE prostředí to jsou stovky paketů za minutu. ScanFilter zužuje výsledky na potřebná zařízení, snižuje spotřebu energie a zatížení aplikace. Filtry se aplikují na úrovni Bluetooth stacku — nevhodné pakety jsou odmítnuty před doručením aplikaci.

Typy filtrů: setServiceUuid — UUID služby (povinně plný 128-bitový formát). setDeviceName — podřetězec názvu zařízení (rozlišuje malá a velká písmena, přesná shoda podřetězce). setDeviceAddress — přesná MAC adresa. setManufacturerData — data výrobce (ID společnosti + maska). Pro jedno skenování lze nastavit několik filtrů — zařízení musí odpovídat všem (AND logika). Pro OR logiku spusťte několik skenování.

kotlin
// Vytvoření ScanFilter pro různé scénáře
class ScanFilterFactory {

    // 1. Filtrování podle UUID služby (Heart Rate Monitor)
    fun byHeartRateService(): ScanFilter {
        return ScanFilter.Builder()
            .setServiceUuid(
                ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
            )
            .build()
    }

    // 2. Filtrování podle názvu zařízení ("iBeacon*")
    fun byDeviceName(): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceName("Sensor")
            .build()
    }

    // 3.   MAC- (zařízení)
    fun byMacAddress(mac: String): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceAddress(mac)
            .build()
    }

    // 4. Kombinovaný filtr (UUID + )
    fun combinedFilter(): List<ScanFilter> {
        return listOf(
            ScanFilter.Builder()
                .setServiceUuid(
                    ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
                )
                .setDeviceName("MyDevice")
                .build()
        )
    }

    // 5.   data výrobce
    fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
        return ScanFilter.Builder()
            .setManufacturerData(companyId, data, mask)
            .build()
    }
}

Třída ScanFilterFactory ukazuje všechny typy filtrů. byHeartRateService filtruje zařízení se službou pulsu 0x180D. byDeviceName najde zařízení obsahující "Sensor" v názvu (Apple doporučuje unikátní názvy pro filtrování). byMacAddress — přesné vyhledávání konkrétního zařízení. combinedFilter — AND filtr podle UUID a názvu. byManufacturer — filtr podle dat výrobce (např. pro iBeacon se používá company ID Apple 0x004C).

ScanCallback: zpracování výsledků a chyb skenování

ScanCallback — abstraktní třída pro příjem výsledků BLE skenování. Obsahuje tři metody: onScanResult — jednotlivý výsledek (typ callbacku, ScanResult), onBatchScanResults — dávka výsledků pro reportDelay > 0, onScanFailed — kód chyby. Všechny metody jsou volány na hlavním vlákně Androidu (main thread). Pro dlouhé zpracování v onScanResult použijte korutiny nebo HandlerThread.

ScanResult obsahuje: BluetoothDevice device (zařízení), int rssi (úroveň signálu v dBm), ScanRecord scanRecord (reklamní data), long timestampNanos (čas detekce od spuštění systému). ScanRecord poskytuje: getServiceData() — UUID + vlastní data, getManufacturerSpecificData() — data výrobce, getAdvertiseFlags() — BLE příznaky. Typ callbacku (callbackType) udává: CALLBACK_TYPE_ALL_MATCHES — shoda s filtrem, CALLBACK_TYPE_FIRST_MATCH — první detekce, CALLBACK_TYPE_MATCH_LOST — ztráta zařízení.

Kódy chyb onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — skenování již bylo spuštěno, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — registrace aplikace v Bluetooth stacku se nezdařila, SCAN_FAILED_INTERNAL_ERROR (3) — interní chyba stacku, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE skenování není na zařízení podporováno.

kotlin
// Plné výsledky skenování a zpracování chyb
class ScanResultHandler {

    private val results = mutableListOf<ScanResult>()

    val scanCallback = object : ScanCallback() {

        // 1. Jednotlivý výsledek
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            // callbackType: 1 = ALL_MATCHES, 2 = FIRST_MATCH, 4 = MATCH_LOST
            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
                onDeviceLost(result)
                return
            }

            // Přidat do seznamu (deduplikace podle adresy)
            val existingIndex = results.indexOfFirst {
                it.device.address == result.device.address
            }
            if (existingIndex >= 0) {
                results[existingIndex] = result  // aktualizovat RSSI
            } else {
                results.add(result)
            }

            // Extrahovat data z reklamního paketu
            val record = result.scanRecord
            val serviceData = record?.serviceData
            val manufacturerData = record?.manufacturerSpecificData

            print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
        }

        // 2. Dávkové výsledky (reportDelay > 0)
        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            results?.let { batch ->
                print("Batch: ${batch.size} devices")
            }
        }

        // 3. Chyba skenování
        override fun onScanFailed(errorCode: Int) {
            val error = when (errorCode) {
                ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already scanning"
                ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Registration failed"
                ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
                ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "BLE not supported"
                else -> "Unknown error: $errorCode"
            }
            print("Error: $error")
        }
    }

    private fun onDeviceLost(result: ScanResult) {
        results.removeAll { it.device.address == result.device.address }
        print("Device lost: ${result.device.address}")
    }
}

Třída ScanResultHandler zpracovává všechny typy callbacků BluetoothLeScanner. onScanResult aktualizuje seznam zařízení s deduplikací podle MAC adresy — RSSI se aktualizuje pro již nalezená zařízení. CALLBACK_TYPE_MATCH_LOST signalizuje ztrátu zařízení (odstranění ze seznamu). onBatchScanResults zpracovává dávkové výsledky pro reportDelay > 0. onScanFailed mapuje kódy chyb na čitelná hlášení — klíčové pro ladění BLE skenování.

PendingIntent: BLE skenování na pozadí přes BroadcastReceiver

Skenování PendingIntent — mechanismus BluetoothLeScanner pro BLE skenování fungující i když je aplikace na pozadí (s omezeními Android 8+). Místo ScanCallback se používá PendingIntent, který při detekci BLE zařízení odesílá Broadcast do systémového BroadcastReceiver. To umožňuje aplikaci přijímat oznámení o BLE zařízeních, aniž by byla v paměti (systém vytvoří proces při přijetí broadcastu).

Omezení skenování na pozadí: Na Android 8+ (API 26) jsou služby na pozadí omezeny — skenování PendingIntent toto omezení obchází přes BroadcastReceiver, který systém může spustit při přijetí BLE události. Na Android 10+ (API 29) je BLE skenování na pozadí dále omezeno politikami úspory energie výrobců (Xiaomi, Huawei, Samsung blokují BLE operace na pozadí). Pro kritické BLE scénáře je vyžadováno oznámení s foreground service.

kotlin
// BLE skenování na pozadí přes PendingIntent
class BackgroundBLEScanner(private val context: Context) {

    private val scanner: BluetoothLeScanner? by lazy {
        val adapter = BluetoothAdapter.getDefaultAdapter()
        adapter?.bluetoothLeScanner
    }

    fun startBackgroundScan() {
        // Vytvořte PendingIntent pro BroadcastReceiver
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Nastavení skenování na pozadí
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .build()

        // Spusťte skenování na pozadí
        scanner?.startScan(
            null,  // filtry
            settings,
            pendingIntent
        )
    }

    fun stopBackgroundScan() {
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        scanner?.stopScan(pendingIntent)
    }
}

// BroadcastReceiver   BLE-
class BLEBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // Získejte výsledky skenování
        val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
        results?.let { scanResults ->
            for (result in scanResults) {
                // Odešlete oznámení uživateli
                showNotification(context, result.device.name ?: " ")
            }
        }
    }

    private fun showNotification(context: Context, name: String) {
        val notification = Notification.Builder(context, "ble_channel")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle("BLE devices")
            .setContentText("Found: $name")
            .setAutoCancel(true)
            .build()
        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
                as NotificationManager
        manager.notify(System.currentTimeMillis().toInt(), notification)
    }
}

Třída BackgroundBLEScanner spouští BLE skenování na pozadí přes PendingIntent. startBackgroundScan vytváří PendingIntent, který při detekci BLE zařízení odesílá Broadcast do BLEBroadcastReceiver. BroadcastReceiver extrahuje ScanResult přes getPendingIntentScanResults() a může zobrazit oznámení nebo odeslat data na server. Tento přístup funguje i když aplikace byla ukončena systémem — Android spouští BroadcastReceiver při přijetí broadcastu.

Příklad BLE skeneru v Kotlin s BluetoothLeScanner

Plný příklad BLE skeneru v Kotlin, který používá BluetoothLeScanner s ScanSettings, ScanFilter a ScanCallback pro hledání Heart Rate Monitor zařízení. Skener zobrazuje seznam nalezených zařízení s RSSI a UUID služeb, s možností připojení přes BluetoothGatt.

kotlin
// Plný BLE skener s korutinami v Kotlin
class DeviceScanner(private val context: Context) {

    private val adapter: BluetoothAdapter? by lazy {
        val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
                as BluetoothManager
        manager.adapter
    }

    private val scanner: BluetoothLeScanner? by lazy {
        adapter?.bluetoothLeScanner
    }

    fun startScan(duration: Long = 10000): Flow<ScanResult> = callbackFlow {
        // Zkontrolujte stav Bluetooth
        if (adapter?.isEnabled != true) {
            close(IllegalStateException("Bluetooth disabled"))
            return@callbackFlow
        }

        // Konfigurace skenování
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        val filters = listOf(
            ScanFilter.Builder()
                .setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
                .build()
        )

        val callback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                trySend(result)
            }

            override fun onScanFailed(errorCode: Int) {
                close(BLEException("Scan failed: $errorCode"))
            }
        }

        // Spusťte skenování
        scanner?.startScan(filters, settings, callback)

        // Automatické zastavení po době
        delay(duration)
        scanner?.stopScan(callback)
        close()
    }.flowOn(Dispatchers.IO)

    fun stop() {
        scanner?.stopScan(object : ScanCallback() {})
    }
}

class BLEException(message: String) : Exception(message)

Třída DeviceScanner používá Kotlin Flow (callbackFlow) pro reaktivní BLE skenování. Skenování se spouští s nastavením LOW_LATENCY a filtrem podle UUID Heart Rate Service. Výsledky jsou emitovány přes onScanResult do Flow. Automatické zastavení po zadané době (10 sekund výchozí). FlowOn(Dispatchers.IO) přesouvá BLE operace do vlákna na pozadí. Tento přístup umožňuje použití BLE skenování v MVVM architektuře přes viewModelScope.launch a collect.

Často kladené otázky

Co je BluetoothLeScanner v Androidu?

BluetoothLeScanner — třída Androidu (API 21+) pro BLE skenování. Získává se přes BluetoothAdapter.getBluetoothLeScanner(). Podporuje tři režimy skenování (LOW_POWER, BALANCED, LOW_LATENCY), filtrování podle UUID, názvu a MAC adresy, dávkové výsledky a PendingIntent pro skenování na pozadí. Nahrazuje zastaralou metodu BluetoothAdapter.startLeScan().

Jaký je rozdíl mezi LOW_POWER a LOW_LATENCY?

SCAN_MODE_LOW_POWER — režim na pozadí se zpožděním detekce 5–10 sekund, minimální spotřeba energie. SCAN_MODE_LOW_LATENCY — aktivní režim se zpožděním přibližně 100 ms, maximální spotřeba energie. SCAN_MODE_BALANCED — kompromis (~2 sekundy zpoždění). Pro UI skenování použijte LOW_LATENCY, pro monitorování na pozadí — LOW_POWER s PendingIntent.

Proč BluetoothLeScanner nenachází zařízení?

Příčiny: Bluetooth vypnutý (zkontrolujte adapter.isEnabled), neudělená oprávnění (BLUETOOTH_SCAN na API 31+, ACCESS_FINE_LOCATION na API 23–30), scanner = null (adaptér není dostupný), zařízení mimo dosah nebo použit nesprávný filtr. Zkontrolujte také onScanFailed — kód chyby ukáže příčinu: SCAN_FAILED_ALREADY_STARTED (1) nebo SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).

Jak skenovat BLE na pozadí na Androidu?

Použijte PendingIntent verzi startScan() — předejte PendingIntent místo ScanCallback. Při detekci BLE zařízení Android odešle Broadcast do BroadcastReceiver, který může být spuštěn systémem i když je aplikace na pozadí. Pro Android 8+ přidejte BroadcastReceiver do manifestu. Na Android 10+ zohledněte omezení úspory energie výrobců.

Kolik BLE zařízení lze detekovat při jednom skenování?

BluetoothLeScanner nemá limit na počet detekovatelných zařízení — omezení závisí na BLE saturaci prostředí. V kanceláři může být 20–50 aktivních BLE zařízení, v obchodním centru stovky. Pro filtrování použijte ScanFilter (podle UUID, názvu). Bez filtrování zpracovávejte výsledky asynchronně — onScanResult může být volán desítkykrát za sekundu.

Shrnutí

  • BluetoothLeScanner — moderní třída Androidu (API 21+) pro BLE skenování s nastavením a filtrováním
  • ScanSettings — tři režimy: LOW_POWER (pozadí), BALANCED (vyvážený), LOW_LATENCY (aktivní)
  • ScanFilter — filtrování podle UUID služby, názvu zařízení, MAC adresy, dat výrobce s AND logikou
  • ScanCallback — onScanResult (jednotlivé), onBatchScanResults (dávkové), onScanFailed (kódy chyb)
  • PendingIntent — skenování na pozadí přes BroadcastReceiver, funguje s ukončenou aplikací
  • ScanRecord — BLE reklamní data: serviceData, manufacturerSpecificData, advertiseFlags, TX power level
  • Kotlin Flow — callbackFlow umožňuje použití BluetoothLeScanner v reaktivní architektuře s automatickým zastavením

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také