481 lines
12 KiB
Markdown
481 lines
12 KiB
Markdown
# Flutter Barcode Scanner App Guidelines
|
|
|
|
## App Overview
|
|
Simple barcode scanner app with two screens:
|
|
1. **Home Screen**: Barcode scanner + scan result display + history list
|
|
2. **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
|
|
1. **Scan Barcode**: Camera scans → Show result below scanner
|
|
2. **Tap Result**: Navigate to detail screen
|
|
3. **Fill Form**: Enter data in 4 text fields
|
|
4. **Save**: Call API to save data + store locally
|
|
5. **Print**: Print form data
|
|
|
|
## Data Models
|
|
```dart
|
|
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
|
|
```dart
|
|
class ScannerState {
|
|
final String? currentBarcode;
|
|
final List<ScanItem> history;
|
|
|
|
ScannerState({
|
|
this.currentBarcode,
|
|
this.history = const [],
|
|
});
|
|
}
|
|
```
|
|
|
|
### Form State
|
|
```dart
|
|
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
|
|
```dart
|
|
class SaveScanUseCase {
|
|
final ScannerRepository repository;
|
|
|
|
SaveScanUseCase(this.repository);
|
|
|
|
Future<Either<Failure, void>> call(SaveRequest request) async {
|
|
return await repository.saveScan(request);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Repository Pattern
|
|
```dart
|
|
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
|
|
```dart
|
|
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
|
|
```dart
|
|
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
|
|
```dart
|
|
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
|
|
```dart
|
|
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
|
|
```dart
|
|
void printData(ScanItem item) {
|
|
// Format data for printing
|
|
// Use platform printing service
|
|
}
|
|
```
|
|
|
|
## Dependencies
|
|
```yaml
|
|
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
|
|
```dart
|
|
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 |