mofilo_nutrient_verifier 1.0.1
mofilo_nutrient_verifier: ^1.0.1 copied to clipboard
Nutrition data validator for user-submitted food data. Validates using physics-based limits, energy conservation, and toxicity thresholds. Binary pass/fail with trust scoring.
example/lib/main.dart
/// Example demonstrating the mofilo_nutrient_verifier package.
///
/// This example shows how to:
/// 1. Validate food items
/// 2. Handle validation results
/// 3. Calculate expected calories
/// 4. Display helpful error messages to users
///
/// Run this example with: flutter run
import 'package:flutter/material.dart';
import 'package:mofilo_nutrient_verifier/mofilo_nutrient_verifier.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nutrient Verifier Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true,
),
home: const ExamplePage(),
);
}
}
class ExamplePage extends StatefulWidget {
const ExamplePage({super.key});
@override
State<ExamplePage> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
ValidationResult? _lastResult;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Nutrient Verifier Examples'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text(
'Tap any food to validate it:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
// Example 1: Valid food
_buildExampleCard(
'Valid: Grilled Chicken Breast',
FoodItem(
id: 'ex1',
name: 'Grilled Chicken Breast',
servingSize: 100,
servingUnit: 'g',
protein: 31,
carbs: 0,
fat: 3.6,
calories: 165,
),
Colors.green,
),
// Example 2: Invalid - Matter density violation
_buildExampleCard(
'Invalid: Too many macros',
FoodItem(
id: 'ex2',
name: 'Impossible Protein Bar',
servingSize: 100,
servingUnit: 'g',
protein: 60,
carbs: 50,
fat: 30, // Total: 140g in 100g serving!
calories: 650,
),
Colors.red,
),
// Example 3: Invalid - Calorie mismatch
_buildExampleCard(
'Invalid: Wrong calories',
FoodItem(
id: 'ex3',
name: 'Dream Food',
servingSize: 100,
servingUnit: 'g',
protein: 50, // 200 kcal
carbs: 2, // 8 kcal
fat: 0.5, // 4.5 kcal
calories: 100, // Should be ~213 kcal!
),
Colors.orange,
),
// Example 4: Invalid - Hierarchy violation
_buildExampleCard(
'Invalid: Fiber exceeds carbs',
FoodItem(
id: 'ex4',
name: 'Fiber Powder',
servingSize: 100,
servingUnit: 'g',
carbs: 10,
dietaryFiber: 15, // Fiber can't exceed total carbs!
calories: 40,
),
Colors.purple,
),
// Example 5: Invalid - Toxic sodium
_buildExampleCard(
'Invalid: Dangerous sodium level',
FoodItem(
id: 'ex5',
name: 'Super Salty Snack',
servingSize: 100,
servingUnit: 'g',
servingCount: 10, // 10 servings
sodium: 1500, // 1500mg × 10 = 15,000mg (TOXIC!)
carbs: 50,
fat: 20,
calories: 400,
),
Colors.red,
),
const SizedBox(height: 24),
const Divider(),
const SizedBox(height: 16),
// Utility function example
const Text(
'Utility: Calculate Expected Calories',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
_buildCalorieCalculator(),
const SizedBox(height: 24),
// Last validation result
if (_lastResult != null) ...[
const Divider(),
const SizedBox(height: 16),
const Text(
'Last Validation Result:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
_buildResultDisplay(_lastResult!),
],
],
),
);
}
Widget _buildExampleCard(String title, FoodItem food, Color color) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () => _validateFood(food),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 8,
height: 48,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'${food.calories} kcal | P: ${food.protein ?? 0}g | '
'C: ${food.carbs ?? 0}g | F: ${food.fat ?? 0}g',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
const Icon(Icons.chevron_right),
],
),
),
),
);
}
Widget _buildCalorieCalculator() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Protein: 30g, Carbs: 50g, Fat: 10g'),
const SizedBox(height: 8),
Text(
'Expected Calories: ${NutrientVerifier.calculateExpectedCalories(
protein: 30,
carbs: 50,
fat: 10,
)} kcal',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 4),
Text(
'Formula: (30×4) + (50×4) + (10×9) = 410 kcal',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
);
}
Widget _buildResultDisplay(ValidationResult result) {
return Card(
color: result.pass ? Colors.green[50] : Colors.red[50],
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
result.pass ? Icons.check_circle : Icons.error,
color: result.pass ? Colors.green : Colors.red,
size: 32,
),
const SizedBox(width: 12),
Expanded(
child: Text(
result.pass ? 'PASS' : 'NO PASS',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: result.pass ? Colors.green : Colors.red,
),
),
),
Text(
'Score: ${result.trustScore}/100',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
if (!result.pass && result.reasons.isNotEmpty) ...[
const SizedBox(height: 16),
const Text(
'Reasons:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
...result.reasons.map((reason) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('• ', style: TextStyle(fontSize: 16)),
Expanded(
child: Text(
reason,
style: const TextStyle(fontSize: 14),
),
),
],
),
)),
],
],
),
),
);
}
void _validateFood(FoodItem food) {
final result = NutrientVerifier.verify(food);
setState(() {
_lastResult = result;
});
// Show snackbar with quick result
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
result.pass
? '✓ ${food.name}: PASS'
: '✗ ${food.name}: NO PASS',
),
backgroundColor: result.pass ? Colors.green : Colors.red,
duration: const Duration(seconds: 2),
),
);
}
}