DEV Community

Shuffling Psyducks
Shuffling Psyducks

Posted on

How to build an Instagram-style "Shot on Canon" UI in Flutter in 5 minutes

If you are building a social media clone, a real estate app, or a photography portfolio in Flutter, displaying metadata adds a massive layer of polish to your UI.

Users love seeing the hardware specs behind a great photo (e.g., Shot on Canon | ISO 400 | 1/200s).

The problem? Extracting EXIF data natively in Dart from heavy image files (especially RAW formats like .CR2) is incredibly memory-intensive and often crashes the app. Furthermore, uploading those massive files directly to your database will bankrupt your cloud storage costs.

Instead of fighting with native Dart image parsers, the cleanest architecture is to use a microservice that handles both the metadata extraction and the image compression in one go.

Here is how to build this feature in under 5 minutes using the PicTalk API.

1. The Setup

First, grab your free API key from the PicTalk RapidAPI page. This API takes your heavy image file, extracts the EXIF data, compresses it into a lightweight WebP, and returns a CDN link.

Add the http package to your pubspec.yaml:

http: ^1.1.0
Enter fullscreen mode Exit fullscreen mode

2. The API Call

We will use a MultipartRequest to stream the image file to the API without freezing the Flutter UI.

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

Future<Map<String, dynamic>?> processImage(File imageFile) async {
  var uri = Uri.parse('https://pictalk-image-processing.p.rapidapi.com/extract');
  var request = http.MultipartRequest('POST', uri);

  // Add your RapidAPI key
  request.headers.addAll({
    'x-rapidapi-key': 'YOUR_RAPIDAPI_KEY_HERE',
    'x-rapidapi-host': 'pictalk-image-processing.p.rapidapi.com',
  });

  var multipartFile = await http.MultipartFile.fromPath('image_file', imageFile.path);
  request.files.add(multipartFile);

  var streamedResponse = await request.send();
  var response = await http.Response.fromStream(streamedResponse);

  if (response.statusCode == 200) {
    return jsonDecode(response.body); // Returns our URL and EXIF data!
  } 
  return null;
}
Enter fullscreen mode Exit fullscreen mode

3. The UI

Now that we have the data, rendering the Instagram-style tag is incredibly simple. The API returns the optimized image URL, so we don't even need to host the image ourselves.

// Assuming 'responseData' is the Map returned from our function
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    // 1. Display the heavily compressed, fast-loading WebP image
    ClipRRect(
      borderRadius: BorderRadius.circular(8.0),
      child: Image.network(responseData['image_url']),
    ),

    SizedBox(height: 8),

    // 2. Display the sleek hardware tag
    Container(
      padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      decoration: BoxDecoration(
        color: Colors.grey.shade200,
        borderRadius: BorderRadius.circular(20),
      ),
      child: Text(
        'Shot on ${responseData['camera_make']} | ISO ${responseData['iso']} | ${responseData['shutter_speed']}s',
        style: TextStyle(fontWeight: FontWeight.w500, color: Colors.black87),
      ),
    ),
  ],
)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)