Developing a Mobile App for Warehouse Management (WMS)
Mobile WMS is not just barcode scanning. It's the interface between warehouse workers and the inventory management system: receiving goods, placing items at locations, picking for orders, moving between zones, inventory counts, shipping. Every operation requires confirmation through scanning, and the app works with poor Wi-Fi, gloved hands, and 10-hour shifts.
Hardware: Mobile Computers and Terminals
Consumer smartphones on a warehouse floor are a temporary solution. Professional mobile computers (Zebra MC9300, Honeywell CT40, Point Mobile PM90) offer IP65/IP67 protection, hot-swappable batteries, built-in laser scanners with 10-meter range supporting all 1D and 2D codes.
Development for Zebra: DataWedge is configured through Intent API — the app declares a profile in datawedge.db or programmatically via broadcast. Scanning arrives as an Intent with extra com.symbol.datawedge.data_string:
class WarehouseActivity : AppCompatActivity() {
private val scanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val barcode = intent.getStringExtra("com.symbol.datawedge.data_string") ?: return
val source = intent.getStringExtra("com.symbol.datawedge.label_type") // EAN13, CODE128, QR_CODE
viewModel.processBarcode(barcode, source)
}
}
override fun onResume() {
super.onResume()
registerReceiver(scanReceiver, IntentFilter("com.symbol.datawedge.api.RESULT_ACTION"))
// Enable DataWedge profile for our app
sendDataWedgeIntent("com.symbol.datawedge.api.SWITCH_TO_PROFILE", "WarehousePro")
}
}
For Honeywell — Honeywell Scanning SDK (similar Intent mechanism). For cross-platform support: abstract ScannerProvider with implementations ZebraScanner, HoneywellScanner, CameraScanner. Device type determined via Build.MANUFACTURER.
Tasks and Queues: Server-Side Model
Warehouse workers see a task list sorted by priority and route (to minimize warehouse movements). A task contains: operation type, list of items with SKUs and quantities, cell addresses (shelf-level-position).
@Entity(tableName = "tasks")
data class WarehouseTask(
@PrimaryKey val taskId: String,
val type: TaskType, // RECEIVING, PUTAWAY, PICKING, REPLENISHMENT, INVENTORY
val status: TaskStatus, // ASSIGNED, IN_PROGRESS, COMPLETED, BLOCKED
val priority: Int,
val assignedUserId: String,
val lines: List<TaskLine>,
val syncedAt: Long,
)
@Entity(tableName = "task_lines")
data class TaskLine(
@PrimaryKey val lineId: String,
val taskId: String,
val sku: String,
val description: String,
val expectedQty: Double,
val actualQty: Double?,
val fromLocation: String?,
val toLocation: String,
val lotNumber: String?,
val expiryDate: String?,
)
Offline mode: when Wi-Fi is lost, tasks are available from Room database. All operations are recorded locally with timestamps and synced as a batch when connection is restored. Version conflicts (another user processed the same item) are resolved server-side by timestamp; the app notifies of changes.
Picking by Route (Picking)
Picking is the most time-critical operation. Task sorting by route: serpentine algorithm or nearest-neighbor based on cell addresses. Implemented on the backend; the mobile app receives a pre-sorted list.
Visual indicator of current cell: highlighting on warehouse map or large cell address (A-12-03) on fullscreen. Confirmation by scanning cell address protects against errors. After scanning the cell, scan the item barcode and enter quantity (or automatically 1 for unit picking).
sealed class PickingStep {
object ScanLocation : PickingStep()
data class ScanItem(val expectedSku: String) : PickingStep()
data class EnterQuantity(val sku: String, val required: Double) : PickingStep()
data class Completed(val lineId: String) : PickingStep()
}
class PickingViewModel : ViewModel() {
private val _step = MutableStateFlow<PickingStep>(PickingStep.ScanLocation)
fun processBarcode(barcode: String) {
when (val step = _step.value) {
is PickingStep.ScanLocation -> {
if (barcode == currentLine.toLocation) {
_step.value = PickingStep.ScanItem(currentLine.sku)
} else {
_events.tryEmit(Event.WrongLocation(barcode, currentLine.toLocation))
}
}
is PickingStep.ScanItem -> {
if (barcode == step.expectedSku || isAlias(barcode, step.expectedSku)) {
_step.value = PickingStep.EnterQuantity(step.expectedSku, currentLine.expectedQty)
} else {
_events.tryEmit(Event.WrongItem(barcode))
}
}
else -> { /* handle other steps */ }
}
}
}
WMS/ERP Integration
Typical integrations: SAP WM/EWM via RFC/BAPI or REST (SAP API Hub), 1C:UPP/ERP via HTTP service, Microsoft Dynamics 365 via Dataverse API, custom WMS via REST/gRPC.
Integration strategy: don't call the ERP directly from mobile — use an intermediate service (middleware) that buffers operations, handles ERP errors, and doesn't crash the mobile client on 30-second SAP responses.
Developing a mobile WMS app with Zebra/Honeywell terminal support, offline mode, route-based picking, and REST integration: 3–5 months. Full cycle (receiving, putaway, picking, inventory, shipping) with SAP/1C integration: 5–8 months. Pricing is calculated individually.







