Skip to content

Flutter Food Database API Integration Tutorial

Published June 5, 2026

A Flutter food database API integration turns your macro tracker from a static prototype into a product users can actually log meals with. This tutorial walks through search, food detail, barcode lookup, and the backend proxy pattern so your API key never ships inside the APK.

We use Calorie API as the food database because it combines search, suggest, barcode, and per-100g macros in one REST API with a free developer tier.

Prerequisites

ItemVersion
Flutter3.16+
Dart3.2+
http or dio packageLatest stable
Backend URLNext.js, Firebase, or Supabase proxy

Get a free key at Calorie API registration.

Step 1: Add Dependencies

# pubspec.yaml
dependencies:
  http: ^1.2.0

Run flutter pub get.

Step 2: Create Models

class FoodSearchResult {
  final int id;
  final String name;
  final String? brand;
  final double? calories;

  FoodSearchResult({
    required this.id,
    required this.name,
    this.brand,
    this.calories,
  });

  factory FoodSearchResult.fromJson(Map<String, dynamic> json) {
    return FoodSearchResult(
      id: json['id'] as int,
      name: json['name'] as String,
      brand: json['brand'] as String?,
      calories: (json['calories'] as num?)?.toDouble(),
    );
  }
}

Typed models catch null fields before they crash your ListView.

Step 3: API Service (Through Your Backend)

Never hardcode X-API-Key in Flutter source. Call your proxy:

import 'dart:convert';
import 'package:http/http.dart' as http;

class NutritionApiService {
  NutritionApiService({required this.baseUrl});
  final String baseUrl;

  Future<List<FoodSearchResult>> searchFoods(String query, {int limit = 15}) async {
    final uri = Uri.parse('$baseUrl/api/foods/search')
        .replace(queryParameters: {'q': query, 'limit': '$limit'});
    final res = await http.get(uri);
    if (res.statusCode != 200) {
      throw Exception('Search failed: ${res.statusCode}');
    }
    final body = jsonDecode(res.body) as Map<String, dynamic>;
    final results = body['results'] as List<dynamic>;
    return results.map((e) => FoodSearchResult.fromJson(e as Map<String, dynamic>)).toList();
  }

  Future<Map<String, dynamic>> getFoodById(int id) async {
    final uri = Uri.parse('$baseUrl/api/foods/$id');
    final res = await http.get(uri);
    if (res.statusCode != 200) throw Exception('Detail failed');
    return jsonDecode(res.body) as Map<String, dynamic>;
  }

  Future<Map<String, dynamic>> lookupBarcode(String upc) async {
    final uri = Uri.parse('$baseUrl/api/foods/barcode/${Uri.encodeComponent(upc)}');
    final res = await http.get(uri);
    if (res.statusCode != 200) throw Exception('Barcode not found');
    return jsonDecode(res.body) as Map<String, dynamic>;
  }
}

See secure Next.js API route or Firebase Cloud Function proxy for backend setup.

Step 4: Debounced Search Widget

import 'dart:async';
import 'package:flutter/material.dart';

class FoodSearchPage extends StatefulWidget {
  const FoodSearchPage({super.key, required this.api});
  final NutritionApiService api;

  @override
  State<FoodSearchPage> createState() => _FoodSearchPageState();
}

class _FoodSearchPageState extends State<FoodSearchPage> {
  final _controller = TextEditingController();
  Timer? _debounce;
  List<FoodSearchResult> _results = [];
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _controller.addListener(_onQueryChanged);
  }

  void _onQueryChanged() {
    _debounce?.cancel();
    _debounce = Timer(const Duration(milliseconds: 300), () async {
      final q = _controller.text.trim();
      if (q.length < 2) {
        setState(() => _results = []);
        return;
      }
      setState(() => _loading = true);
      try {
        final data = await widget.api.searchFoods(q);
        setState(() => _results = data);
      } finally {
        setState(() => _loading = false);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Log food')),
      body: Column(
        children: [
          TextField(controller: _controller, decoration: const InputDecoration(hintText: 'Search foods')),
          if (_loading) const LinearProgressIndicator(),
          Expanded(
            child: ListView.builder(
              itemCount: _results.length,
              itemBuilder: (context, i) {
                final food = _results[i];
                return ListTile(
                  title: Text(food.name),
                  subtitle: Text('${food.calories?.toStringAsFixed(0) ?? 'N/A'} kcal / 100g'),
                  onTap: () => Navigator.pushNamed(context, '/food/${food.id}'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Step 5: Scale Portions

double scaleNutrient(double per100g, double grams) => (grams / 100) * per100g;

Load full detail from getFoodById, multiply protein, carbs, and fat by user grams.

Step 6: Barcode With mobile_scanner

dependencies:
  mobile_scanner: ^5.0.0

On scan success, call lookupBarcode(barcode.rawValue!) and navigate to the log screen.

Production Checklist

  1. Proxy API key on server (backend proxy pattern)
  2. Cache recent foods in shared_preferences or Hive
  3. Handle offline with cached results
  4. Monitor Calorie API quota before Play Store launch

Get your free API key | Docs

Frequently Asked Questions

How do I integrate a food database API in Flutter?

Create typed Dart models, call your backend proxy with the http package, debounce search input by 300 ms, and scale per-100g nutrients by the user's portion in grams.

Which food database API works best with Flutter?

Calorie API is a strong choice for Flutter apps because it provides REST search, suggest, barcode lookup, and per-100g macros with a free tier and predictable JSON.

Should I store my API key in Flutter?

No. APK files can be decompiled. Proxy requests through Firebase, Next.js, or Supabase and keep the Calorie API key server-side only.

How do I add barcode scanning in Flutter?

Use mobile_scanner or similar, read the UPC, and call your backend barcode endpoint which forwards to Calorie API search/barcode.

Can Flutter apps use Calorie API for commercial apps?

Yes. Start on the free tier for development and upgrade to a paid plan when you monetize or exceed monthly request limits.

← Back to all articles

Start building with the Calorie API

Get a free API key and access 4M+ foods with search, barcode lookup, and full macro data.