Developing a Mobile App for Equipment Rental Management
Equipment rental—tools, construction machinery, audio/video, medical equipment—is about accounting: who took what, for how long, in what condition, when returned and didn't break. Mobile app solves two tasks simultaneously: client app for booking and operator app for fleet management.
Inventory via QR/Barcode
Each equipment unit has unique identifier—QR code or barcode sticker on body. Operator scans for delivery and return:
// iOS: fast scanning via AVCaptureSession
class BarcodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
func setupCapture() {
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { return }
session.addInput(input)
let output = AVCaptureMetadataOutput()
session.addOutput(output)
output.setMetadataObjectsDelegate(self, queue: .main)
output.metadataObjectTypes = [.qr, .code128, .ean13]
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
session.startRunning()
}
func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
guard let readableCode = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let assetId = readableCode.stringValue else { return }
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
loadEquipmentDetails(assetId: assetId)
}
}
After scan app shows equipment card: name, serial number, current status (AVAILABLE / RENTED / MAINTENANCE), rental history.
Handover Act with Photo Fixation
Before delivering equipment, operator photographs condition. Photos attached to rental agreement as completeness proof. Critical for dispute resolution on return.
// Android: CameraX for condition photo-fixation
private fun takeHandoverPhoto() {
val imageCapture = imageCapture ?: return
val outputOptions = ImageCapture.OutputFileOptions.Builder(
createTempFile("handover_${System.currentTimeMillis()}", ".jpg")
).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val photoFile = output.savedUri?.toFile() ?: return
viewModel.addHandoverPhoto(photoFile, currentRentalId)
}
override fun onError(exception: ImageCaptureException) {
showError("Photo error: ${exception.message}")
}
}
)
}
Photos compressed to 1200px on long side before server upload—original 12MP shot at 4 MB excessive for this task.
Booking with Availability Check
Client app shows equipment availability calendar. No booking overlaps allowed. Check logic on server, but UI must visually block occupied dates:
// iOS: DatePicker with unavailable dates
func configureDatePicker(unavailableDates: [DateInterval]) {
datePicker.minimumDate = Date()
// For more flexible control use custom calendar component
// with specific range blocking
calendarView.disabledDateRanges = unavailableDates.map {
CalendarDateRange(start: $0.start, end: $0.end)
}
}
Concurrent booking (two users simultaneous)—server optimistic locking: first gets confirmation, second—409 Conflict with suggest different dates.
Cost Calculation
Tarification can be hourly, daily, or combined. Server calculation:
def calculate_rental_cost(
equipment: Equipment,
start_date: datetime,
end_date: datetime
) -> Decimal:
duration = end_date - start_date
hours = duration.total_seconds() / 3600
if hours <= 4:
return equipment.hourly_rate * Decimal(str(hours))
elif hours <= 24:
return equipment.daily_rate # daily rate better
else:
days = math.ceil(hours / 24)
return equipment.daily_rate * Decimal(str(days))
App shows real-time calculation on date change—via debounced API or local calc with same rules.
Deposit and Security Deposit
For expensive equipment—security deposit required. Scheme: when booking block amount on card (authorization hold), on return in good condition—unlock (void), on damage—deduct (capture).
// Stripe: authorization-only payment intent
let params = STPPaymentIntentParams(clientSecret: clientSecret)
params.captureMethod = .manual // capture later if needed
STPPaymentHandler.shared().confirmPayment(params, with: self) { status, intent, error in
// intent.id save—need for capture or void on return
}
Timeline Estimates
Basic version (equipment catalog, booking, QR inventory, payment): 5–7 weeks. Operator module with photo-fixation and handover acts—another 2–3 weeks. Pricing is calculated individually.







