12 KiB
12 KiB
Flutter Barcode Scanner App Guidelines
App Overview
Simple barcode scanner app with two screens:
- Home Screen: Barcode scanner + scan result display + history list
- Detail Screen: 4 text fields + Save (API call) & Print buttons
Project Structure
lib/
core/
constants/
theme/
widgets/
network/
api_client.dart
features/
scanner/
data/
datasources/
scanner_remote_datasource.dart
models/
scan_item.dart
save_request_model.dart
repositories/
scanner_repository.dart
domain/
entities/
scan_entity.dart
repositories/
scanner_repository.dart
usecases/
save_scan_usecase.dart
presentation/
providers/
scanner_provider.dart
pages/
home_page.dart
detail_page.dart
widgets/
barcode_scanner_widget.dart
scan_result_display.dart
scan_history_list.dart
main.dart
App Flow
- Scan Barcode: Camera scans → Show result below scanner
- Tap Result: Navigate to detail screen
- Fill Form: Enter data in 4 text fields
- Save: Call API to save data + store locally
- Print: Print form data
Data Models
class ScanItem {
final String barcode;
final DateTime timestamp;
final String field1;
final String field2;
final String field3;
final String field4;
ScanItem({
required this.barcode,
required this.timestamp,
this.field1 = '',
this.field2 = '',
this.field3 = '',
this.field4 = '',
});
}
class SaveRequest {
final String barcode;
final String field1;
final String field2;
final String field3;
final String field4;
SaveRequest({
required this.barcode,
required this.field1,
required this.field2,
required this.field3,
required this.field4,
});
Map<String, dynamic> toJson() => {
'barcode': barcode,
'field1': field1,
'field2': field2,
'field3': field3,
'field4': field4,
};
}
Home Screen Layout
┌─────────────────────────┐
│ │
│ Barcode Scanner │
│ (Camera View) │
│ │
├─────────────────────────┤
│ Last Scanned: 123456 │
│ [Tap to edit] │
├─────────────────────────┤
│ Scan History │
│ • 123456 - 10:30 AM │
│ • 789012 - 10:25 AM │
│ • 345678 - 10:20 AM │
│ │
└─────────────────────────┘
Detail Screen Layout
┌─────────────────────────┐
│ Barcode: 123456789 │
├─────────────────────────┤
│ │
│ Field 1: [____________] │
│ │
│ Field 2: [____________] │
│ │
│ Field 3: [____________] │
│ │
│ Field 4: [____________] │
│ │
├─────────────────────────┤
│ [Save] [Print] │
└─────────────────────────┘
Key Features
Barcode Scanner
- Use mobile_scanner package
- Support Code128 and common formats
- Show scanned value immediately below scanner
- Add to history list automatically
API Integration
- Call API when Save button is pressed
- Send form data to server
- Handle success/error responses
- Show loading state during API call
Local Storage
- Use Hive for simple local storage
- Store scan history with timestamps
- Save form data after successful API call
Navigation
- Tap on scan result → Navigate to detail screen
- Pass barcode value to detail screen
- Simple back navigation
State Management (Riverpod)
Scanner State
class ScannerState {
final String? currentBarcode;
final List<ScanItem> history;
ScannerState({
this.currentBarcode,
this.history = const [],
});
}
Form State
class FormState {
final String barcode;
final String field1;
final String field2;
final String field3;
final String field4;
final bool isLoading;
final String? error;
FormState({
required this.barcode,
this.field1 = '',
this.field2 = '',
this.field3 = '',
this.field4 = '',
this.isLoading = false,
this.error,
});
}
Use Cases
Save Scan Use Case
class SaveScanUseCase {
final ScannerRepository repository;
SaveScanUseCase(this.repository);
Future<Either<Failure, void>> call(SaveRequest request) async {
return await repository.saveScan(request);
}
}
Repository Pattern
abstract class ScannerRepository {
Future<Either<Failure, void>> saveScan(SaveRequest request);
}
class ScannerRepositoryImpl implements ScannerRepository {
final ScannerRemoteDataSource remoteDataSource;
ScannerRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, void>> saveScan(SaveRequest request) async {
try {
await remoteDataSource.saveScan(request);
return const Right(null);
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
}
Data Source
abstract class ScannerRemoteDataSource {
Future<void> saveScan(SaveRequest request);
}
class ScannerRemoteDataSourceImpl implements ScannerRemoteDataSource {
final ApiClient apiClient;
ScannerRemoteDataSourceImpl(this.apiClient);
@override
Future<void> saveScan(SaveRequest request) async {
final response = await apiClient.post(
'/api/scans',
data: request.toJson(),
);
if (response.statusCode != 200) {
throw ServerException('Failed to save scan');
}
}
}
Widget Structure
Home Page
class HomePage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Column(
children: [
// Barcode Scanner (top half)
Expanded(
flex: 1,
child: BarcodeScannerWidget(),
),
// Scan Result Display
ScanResultDisplay(),
// History List (bottom half)
Expanded(
flex: 1,
child: ScanHistoryList(),
),
],
),
);
}
}
Detail Page with Save API Call
class DetailPage extends ConsumerWidget {
final String barcode;
Widget build(BuildContext context, WidgetRef ref) {
final formState = ref.watch(formProvider);
return Scaffold(
appBar: AppBar(title: Text('Edit Details')),
body: Column(
children: [
// Barcode Header
Container(
padding: EdgeInsets.all(16),
child: Text('Barcode: $barcode'),
),
// 4 Text Fields
Expanded(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: InputDecoration(labelText: 'Field 1'),
onChanged: (value) => ref.read(formProvider.notifier).updateField1(value),
),
TextField(
decoration: InputDecoration(labelText: 'Field 2'),
onChanged: (value) => ref.read(formProvider.notifier).updateField2(value),
),
TextField(
decoration: InputDecoration(labelText: 'Field 3'),
onChanged: (value) => ref.read(formProvider.notifier).updateField3(value),
),
TextField(
decoration: InputDecoration(labelText: 'Field 4'),
onChanged: (value) => ref.read(formProvider.notifier).updateField4(value),
),
],
),
),
),
// Error Message
if (formState.error != null)
Container(
padding: EdgeInsets.all(16),
child: Text(
formState.error!,
style: TextStyle(color: Colors.red),
),
),
// Save & Print Buttons
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: formState.isLoading ? null : () => _saveData(ref),
child: formState.isLoading
? CircularProgressIndicator()
: Text('Save'),
),
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _printData,
child: Text('Print'),
),
),
],
),
),
],
),
);
}
void _saveData(WidgetRef ref) async {
final formState = ref.read(formProvider);
final saveRequest = SaveRequest(
barcode: barcode,
field1: formState.field1,
field2: formState.field2,
field3: formState.field3,
field4: formState.field4,
);
await ref.read(formProvider.notifier).saveData(saveRequest);
}
}
Core Functions
Save Data with API Call
class FormNotifier extends StateNotifier<FormState> {
final SaveScanUseCase saveScanUseCase;
FormNotifier(this.saveScanUseCase, String barcode)
: super(FormState(barcode: barcode));
Future<void> saveData(SaveRequest request) async {
state = state.copyWith(isLoading: true, error: null);
final result = await saveScanUseCase(request);
result.fold(
(failure) => state = state.copyWith(
isLoading: false,
error: failure.message,
),
(_) {
state = state.copyWith(isLoading: false);
// Save to local storage after successful API call
_saveToLocal(request);
// Navigate back or show success message
},
);
}
void _saveToLocal(SaveRequest request) {
// Save to Hive local storage
final scanItem = ScanItem(
barcode: request.barcode,
timestamp: DateTime.now(),
field1: request.field1,
field2: request.field2,
field3: request.field3,
field4: request.field4,
);
// Add to Hive box
}
}
Print Data
void printData(ScanItem item) {
// Format data for printing
// Use platform printing service
}
Dependencies
dependencies:
flutter_riverpod: ^2.4.9
mobile_scanner: ^4.0.1
hive: ^2.2.3
hive_flutter: ^1.1.0
go_router: ^12.1.3
dio: ^5.3.2
dartz: ^0.10.1
get_it: ^7.6.4
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.7
Error Handling
abstract class Failure {
final String message;
const Failure(this.message);
}
class ServerFailure extends Failure {
const ServerFailure(String message) : super(message);
}
class NetworkFailure extends Failure {
const NetworkFailure(String message) : super(message);
}
Key Points
- Save button calls API to save form data
- Show loading state during API call
- Handle API errors with user-friendly messages
- Save to local storage only after successful API call
- Use clean architecture with use cases and repository pattern
- Keep UI simple with proper error handling