Add scanner-to-api implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
743
docs/plans/2026-05-07-scanner-to-api-plan.md
Normal file
743
docs/plans/2026-05-07-scanner-to-api-plan.md
Normal file
@@ -0,0 +1,743 @@
|
||||
# Scanner to API Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Build a Flutter app that captures barcode scans from the D500 PAD scanner and sends the data to a user-configured HTTP endpoint.
|
||||
|
||||
**Architecture:** Android BroadcastReceiver captures scan broadcasts from the device's built-in scanning service, forwards data to Flutter via EventChannel. The Dart side receives scan events, sends HTTP POST to the configured URL, and displays results.
|
||||
|
||||
**Tech Stack:** Flutter 3.x, Dart, Android native (Kotlin), http package, shared_preferences
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Dependencies
|
||||
|
||||
**Files:**
|
||||
- Modify: `pubspec.yaml`
|
||||
|
||||
**Step 1: Add http and shared_preferences to pubspec.yaml**
|
||||
|
||||
Add under `dependencies:` (after `cupertino_icons`):
|
||||
|
||||
```yaml
|
||||
http: ^1.2.0
|
||||
shared_preferences: ^2.2.0
|
||||
```
|
||||
|
||||
**Step 2: Run flutter pub get**
|
||||
|
||||
Run: `flutter pub get`
|
||||
Expected: dependencies resolved successfully
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add pubspec.yaml pubspec.lock
|
||||
git commit -m "feat: add http and shared_preferences dependencies"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create Scan Record Model
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/models/scan_record.dart`
|
||||
- Create: `test/models/scan_record_test.dart`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Create `test/models/scan_record_test.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pad_scanner/models/scan_record.dart';
|
||||
|
||||
void main() {
|
||||
group('ScanRecord', () {
|
||||
test('creates from map', () {
|
||||
final record = ScanRecord(
|
||||
barcode: '1234567890',
|
||||
codeType: 'CODE128',
|
||||
timestamp: DateTime.parse('2026-05-07T10:30:00Z'),
|
||||
);
|
||||
expect(record.barcode, '1234567890');
|
||||
expect(record.codeType, 'CODE128');
|
||||
expect(record.status, SendStatus.pending);
|
||||
});
|
||||
|
||||
test('toJson produces correct map', () {
|
||||
final record = ScanRecord(
|
||||
barcode: 'ABC123',
|
||||
codeType: 'QR',
|
||||
timestamp: DateTime.parse('2026-05-07T10:30:00Z'),
|
||||
);
|
||||
final json = record.toJson();
|
||||
expect(json['barcode'], 'ABC123');
|
||||
expect(json['code_type'], 'QR');
|
||||
expect(json['timestamp'], '2026-05-07T10:30:00.000Z');
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `flutter test test/models/scan_record_test.dart`
|
||||
Expected: FAIL - file not found
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create `lib/models/scan_record.dart`:
|
||||
|
||||
```dart
|
||||
enum SendStatus { pending, success, failed }
|
||||
|
||||
class ScanRecord {
|
||||
final String barcode;
|
||||
final String codeType;
|
||||
final DateTime timestamp;
|
||||
SendStatus status;
|
||||
|
||||
ScanRecord({
|
||||
required this.barcode,
|
||||
required this.codeType,
|
||||
required this.timestamp,
|
||||
this.status = SendStatus.pending,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'barcode': barcode,
|
||||
'code_type': codeType,
|
||||
'timestamp': timestamp.toUtc().toIso8601String(),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `flutter test test/models/scan_record_test.dart`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/models/scan_record.dart test/models/scan_record_test.dart
|
||||
git commit -m "feat: add ScanRecord model with toJson"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create API Service
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/services/api_service.dart`
|
||||
- Create: `test/services/api_service_test.dart`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Create `test/services/api_service_test.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:pad_scanner/services/api_service.dart';
|
||||
|
||||
void main() {
|
||||
group('ApiService', () {
|
||||
test('sendScanData returns true on 200', () async {
|
||||
final client = MockClient((request) async {
|
||||
return http.Response('{"status":"ok"}', 200);
|
||||
});
|
||||
final service = ApiService(client: client);
|
||||
final result = await service.sendScanData(
|
||||
'http://localhost:8000/scan',
|
||||
barcode: '123456',
|
||||
codeType: 'CODE128',
|
||||
);
|
||||
expect(result, isTrue);
|
||||
});
|
||||
|
||||
test('sendScanData returns false on error', () async {
|
||||
final client = MockClient((request) async {
|
||||
return http.Response('error', 500);
|
||||
});
|
||||
final service = ApiService(client: client);
|
||||
final result = await service.sendScanData(
|
||||
'http://localhost:8000/scan',
|
||||
barcode: '123456',
|
||||
codeType: 'CODE128',
|
||||
);
|
||||
expect(result, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `flutter test test/services/api_service_test.dart`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create `lib/services/api_service.dart`:
|
||||
|
||||
```dart
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ApiService {
|
||||
final http.Client _client;
|
||||
|
||||
ApiService({http.Client? client})
|
||||
: _client = client ?? http.Client();
|
||||
|
||||
Future<bool> sendScanData(
|
||||
String url, {
|
||||
required String barcode,
|
||||
required String codeType,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _client
|
||||
.post(
|
||||
Uri.parse(url),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'barcode': barcode,
|
||||
'code_type': codeType,
|
||||
'timestamp': DateTime.now().toUtc().toIso8601String(),
|
||||
}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 5));
|
||||
return response.statusCode >= 200 && response.statusCode < 300;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `flutter test test/services/api_service_test.dart`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/services/api_service.dart test/services/api_service_test.dart
|
||||
git commit -m "feat: add ApiService for HTTP POST"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Create Scanner Service (Flutter EventChannel)
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/services/scanner_service.dart`
|
||||
|
||||
**Step 1: Write the scanner service**
|
||||
|
||||
Create `lib/services/scanner_service.dart`:
|
||||
|
||||
```dart
|
||||
import 'dart:async';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ScanResult {
|
||||
final String barcode;
|
||||
final String codeType;
|
||||
|
||||
ScanResult({required this.barcode, required this.codeType});
|
||||
}
|
||||
|
||||
class ScannerService {
|
||||
static const _eventChannel =
|
||||
EventChannel('com.example.pad_scanner/scan');
|
||||
|
||||
Stream<ScanResult>? _scanStream;
|
||||
|
||||
Stream<ScanResult> get scanResults {
|
||||
_scanStream ??= _eventChannel
|
||||
.receiveBroadcastStream()
|
||||
.map((event) => event as Map)
|
||||
.map((event) => ScanResult(
|
||||
barcode: event['barcode'] as String? ?? '',
|
||||
codeType: event['codeType'] as String? ?? 'UNKNOWN',
|
||||
));
|
||||
return _scanStream!;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/services/scanner_service.dart
|
||||
git commit -m "feat: add ScannerService with EventChannel"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Android Native - BroadcastReceiver + EventChannel
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/kotlin/com/example/pad_scanner/MainActivity.kt`
|
||||
|
||||
**Step 1: Implement BroadcastReceiver and EventChannel in MainActivity**
|
||||
|
||||
Replace entire content of `MainActivity.kt`:
|
||||
|
||||
```kotlin
|
||||
package com.example.pad_scanner
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private var scanReceiver: BroadcastReceiver? = null
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
registerScanReceiver()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
unregisterScanReceiver()
|
||||
}
|
||||
|
||||
private fun registerScanReceiver() {
|
||||
scanReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// noop - handled by EventChannel sink
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterScanReceiver() {
|
||||
scanReceiver?.let {
|
||||
unregisterReceiver(it)
|
||||
scanReceiver = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: io.flutter.embedding.engine.FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger,
|
||||
"com.example.pad_scanner/scan")
|
||||
.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
|
||||
override fun onListen(arguments: Any?, events: io.flutter.plugin.common.EventChannel.EventSink?) {
|
||||
if (events == null) return
|
||||
|
||||
receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val barcode = intent.getStringExtra("scannerdata") ?: return
|
||||
events.success(mapOf(
|
||||
"barcode" to barcode,
|
||||
"codeType" to "UNKNOWN"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
val filter = IntentFilter("com.android.server.scannerservice.broadcast")
|
||||
registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
receiver?.let {
|
||||
unregisterReceiver(it)
|
||||
receiver = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add android/app/src/main/kotlin/com/example/pad_scanner/MainActivity.kt
|
||||
git commit -m "feat: add BroadcastReceiver and EventChannel for scanner"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Settings Page
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/pages/settings_page.dart`
|
||||
|
||||
**Step 1: Write the settings page**
|
||||
|
||||
Create `lib/pages/settings_page.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:pad_scanner/services/api_service.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
final _controller = TextEditingController();
|
||||
final _apiService = ApiService();
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUrl();
|
||||
}
|
||||
|
||||
Future<void> _loadUrl() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_controller.text = prefs.getString('api_url') ?? '';
|
||||
}
|
||||
|
||||
Future<void> _saveUrl() async {
|
||||
setState(() => _saving = true);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('api_url', _controller.text.trim());
|
||||
setState(() => _saving = false);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('URL saved')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _testConnection() async {
|
||||
final url = _controller.text.trim();
|
||||
if (url.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Please enter a URL first')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final ok = await _apiService.sendScanData(
|
||||
url,
|
||||
barcode: 'TEST_BARCODE',
|
||||
codeType: 'TEST',
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(ok ? 'Connection OK' : 'Connection failed')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'API URL',
|
||||
hintText: 'http://192.168.1.100:8000/scan',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.url,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _saving ? null : _saveUrl,
|
||||
child: _saving
|
||||
? const SizedBox(
|
||||
width: 20, height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Text('Save'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton(
|
||||
onPressed: _testConnection,
|
||||
child: const Text('Test Connection'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/pages/settings_page.dart
|
||||
git commit -m "feat: add settings page for URL configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Main Scan Page
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/pages/scan_page.dart`
|
||||
|
||||
**Step 1: Write the scan page**
|
||||
|
||||
Create `lib/pages/scan_page.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:pad_scanner/models/scan_record.dart';
|
||||
import 'package:pad_scanner/services/scanner_service.dart';
|
||||
import 'package:pad_scanner/services/api_service.dart';
|
||||
import 'package:pad_scanner/pages/settings_page.dart';
|
||||
|
||||
class ScanPage extends StatefulWidget {
|
||||
const ScanPage({super.key});
|
||||
|
||||
@override
|
||||
State<ScanPage> createState() => _ScanPageState();
|
||||
}
|
||||
|
||||
class _ScanPageState extends State<ScanPage> {
|
||||
final _scannerService = ScannerService();
|
||||
final _apiService = ApiService();
|
||||
final _records = <ScanRecord>[];
|
||||
String _status = 'Waiting for scan...';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startListening();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_scannerService.scanResults.listen((result) async {
|
||||
final record = ScanRecord(
|
||||
barcode: result.barcode,
|
||||
codeType: result.codeType,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_records.insert(0, record);
|
||||
_status = 'Sending...';
|
||||
});
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final url = prefs.getString('api_url') ?? '';
|
||||
|
||||
if (url.isEmpty) {
|
||||
setState(() {
|
||||
record.status = SendStatus.failed;
|
||||
_status = 'No URL configured. Go to Settings.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final ok = await _apiService.sendScanData(
|
||||
url,
|
||||
barcode: record.barcode,
|
||||
codeType: record.codeType,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
record.status = ok ? SendStatus.success : SendStatus.failed;
|
||||
_status = ok ? 'Sent successfully' : 'Send failed';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('PAD Scanner'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SettingsPage()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: Text(
|
||||
_status,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _records.isEmpty
|
||||
? const Center(
|
||||
child: Text('No scans yet.\nPress the scan button on the device.',
|
||||
textAlign: TextAlign.center))
|
||||
: ListView.builder(
|
||||
itemCount: _records.length,
|
||||
itemBuilder: (context, index) {
|
||||
final r = _records[index];
|
||||
return ListTile(
|
||||
title: Text(r.barcode),
|
||||
subtitle: Text(
|
||||
'${r.codeType} ${r.timestamp.toLocal().toIso8601String().substring(0, 19)}'),
|
||||
trailing: Icon(
|
||||
r.status == SendStatus.success
|
||||
? Icons.check_circle
|
||||
: r.status == SendStatus.failed
|
||||
? Icons.error
|
||||
: Icons.hourglass_empty,
|
||||
color: r.status == SendStatus.success
|
||||
? Colors.green
|
||||
: r.status == SendStatus.failed
|
||||
? Colors.red
|
||||
: Colors.orange,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/pages/scan_page.dart
|
||||
git commit -m "feat: add scan page with record list and send logic"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Wire Up main.dart
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/main.dart`
|
||||
|
||||
**Step 1: Replace main.dart**
|
||||
|
||||
Replace entire content of `lib/main.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pad_scanner/pages/scan_page.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const PadScannerApp());
|
||||
}
|
||||
|
||||
class PadScannerApp extends StatelessWidget {
|
||||
const PadScannerApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'PAD Scanner',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const ScanPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/main.dart
|
||||
git commit -m "feat: wire up main.dart with ScanPage"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Update AndroidManifest
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/AndroidManifest.xml`
|
||||
|
||||
**Step 1: Add INTERNET permission and uses-library**
|
||||
|
||||
Add inside `<manifest>` tag (before `<application>`):
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
```
|
||||
|
||||
Add inside `<application>` tag (before the `<activity>`):
|
||||
|
||||
```xml
|
||||
<uses-library android:name="android.scanner.library"/>
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add android/app/src/main/AndroidManifest.xml
|
||||
git commit -m "feat: add INTERNET permission and scanner library"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Build and Deploy to Device
|
||||
|
||||
**Step 1: Run all unit tests**
|
||||
|
||||
Run: `flutter test`
|
||||
Expected: all tests PASS
|
||||
|
||||
**Step 2: Build and deploy to D500 device**
|
||||
|
||||
Make sure device is connected via USB, then:
|
||||
|
||||
Run: `flutter run -d <device_id>`
|
||||
|
||||
**Step 3: Verify on device**
|
||||
|
||||
1. App launches with "Waiting for scan..." status
|
||||
2. Go to Settings, enter a URL (or use a mock server), save
|
||||
3. Press the scan button on the device, scan a barcode
|
||||
4. Verify the barcode appears in the list
|
||||
5. Verify the send status indicator updates
|
||||
|
||||
**Step 4: Final commit (if any fixes needed)**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: adjustments from device testing"
|
||||
```
|
||||
Reference in New Issue
Block a user