RFID scanner inventory management in mobile app

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
RFID scanner inventory management in mobile app
Medium
~5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing RFID Inventory in Mobile Applications

RFID inventory differs from barcodes — it's mass simultaneous reading. Warehouse operator scans shelf with reader and reads 200 tags in 3 seconds. Mobile app task: accept this tag stream losslessly, deduplicate (one tag may be read multiple times), compare with expected list, and show discrepancies. Seems simple until you face duplicates, tags outside session, and offline requirements.

RFID Inventory Architecture

Inventory session — finite state machine with clear transitions:

IDLE → SCANNING → PROCESSING → COMPLETED
              ↓
           PAUSED → SCANNING

Each read tag — update in MutableStateFlow with EPC deduplication:

class InventorySession(private val expectedItems: List<InventoryItem>) {
    private val _scannedEpcs = MutableStateFlow<Set<String>>(emptySet())
    val scannedEpcs: StateFlow<Set<String>> = _scannedEpcs.asStateFlow()

    // Derived states
    val matchedItems = scannedEpcs.map { epcs ->
        expectedItems.filter { it.epc in epcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    val missingItems = scannedEpcs.map { epcs ->
        expectedItems.filter { it.epc !in epcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    val unexpectedEpcs = scannedEpcs.map { epcs ->
        val knownEpcs = expectedItems.map { it.epc }.toSet()
        epcs.filter { it !in knownEpcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    fun onTagRead(epc: String) {
        _scannedEpcs.update { current -> current + epc }
    }

    fun reset() {
        _scannedEpcs.value = emptySet()
    }
}

Set<String> — automatic deduplication. One EPC may arrive 50+ times during inventory (reader scans at high speed), but appears once in Set.

Real-time Result Display

LazyColumn with key(item.epc) — animated addition of found items:

@Composable
fun InventoryResultsScreen(session: InventorySession) {
    val matched by session.matchedItems.collectAsState()
    val missing by session.missingItems.collectAsState()
    val scanned by session.scannedEpcs.collectAsState()

    Column {
        // Progress: X of Y found
        LinearProgressIndicator(
            progress = { if (session.expectedItems.isEmpty()) 0f
                        else matched.size.toFloat() / session.expectedItems.size }
        )
        Text("Found: ${matched.size}/${session.expectedItems.size}")

        LazyColumn {
            items(matched, key = { it.epc }) { item ->
                InventoryItemRow(item = item, status = ItemStatus.FOUND)
            }
            items(missing, key = { it.epc }) { item ->
                InventoryItemRow(item = item, status = ItemStatus.MISSING)
            }
        }
    }
}

Offline Mode and Synchronization

Warehouses often lack Wi-Fi. Architecture: local Room database as source of truth, synchronization via WorkManager on connection recovery. Conflicts on merge handled by "last write wins" strategy with server timestamp or idempotent operation queue.

Typical issue — transactions during bulk receipt. If user scans 200 items and app crashes at item 150, must either rollback all or continue from break point. Room supports transactions via @Transaction, but "complete operation" boundary must be explicitly defined at business logic level.

@Entity(tableName = "inventory_sessions")
data class InventorySessionEntity(
    @PrimaryKey val sessionId: String,
    val locationId: String,
    val startedAt: Long,
    val completedAt: Long?,
    val status: String // "in_progress", "completed", "synced"
)

@Entity(tableName = "scanned_tags")
data class ScannedTagEntity(
    @PrimaryKey val epc: String,
    val sessionId: String,
    val firstSeenAt: Long,
    val readCount: Int
)

readCount — quantity of reads for one tag in session. Anomalously low (1–2) while adjacent tags read 20+ times — sign of poor physical tag placement or damage. Useful metric for QA.

After session completion — synchronization via WorkManager on network:

val syncRequest = OneTimeWorkRequestBuilder<InventorySyncWorker>()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
    .setInputData(workDataOf("session_id" to sessionId))
    .build()
workManager.enqueueUniqueWork("sync_$sessionId", ExistingWorkPolicy.KEEP, syncRequest)

GS1 EPC Decoding

EPC — not just hex string. Structured code: urn:epc:id:sgtin:0614141.107346.2017 (SGTIN — Serialized GTIN, global trade number with serial). Decode via GS1 EPC Information Services:

// SGTIN-96 decoding (most common format)
fun decodeSgtin96(epc: String): Sgtin96? {
    val bytes = epc.chunked(2).map { it.toInt(16) }.toByteArray()
    val bits = BigInteger(1, bytes)

    val header = bits.shiftRight(88).and(BigInteger.valueOf(0xFF)).toInt()
    if (header != 0x30) return null // Not SGTIN-96

    val filter = bits.shiftRight(85).and(BigInteger.valueOf(0x07)).toInt()
    val partition = bits.shiftRight(82).and(BigInteger.valueOf(0x07)).toInt()
    // ... continue per partition table: company prefix + item reference + serial
}

Ready library: com.gs4tr.epcis:epcis-rest-client or org.fosstrak.epcis:epcis-repository-client.

Timeline

Inventory mobile app with Zebra/custom BLE reader, offline Room, GS1 decoding and sync: 5 days (simple warehouse, one reader, one tag type) to 2–3 weeks (multi-location, multiple tag types, custom EPC scheme, REST WMS integration).