Protocol Buffers is perfect for Flutter apps. It provides smaller message sizes and faster serialization compared to JSON, which means better battery life, reduced data usage, and faster app performance. This is especially valuable for mobile apps where bandwidth and battery matter.
This guide shows you how to use Protocol Buffers in Dart and Flutter applications. We'll use simple examples and focus on real-world mobile app scenarios. Compare with JSON serialization to see the benefits.
What You'll Need
- •Flutter/Dart: SDK 3.0+ (check with
flutter --version) - •Protocol Buffer Compiler: We'll install this next
- •protoc_plugin: Dart plugin for protoc
Step 1: Install Protocol Buffers
First, activate the Dart protoc plugin:
dart pub global activate protoc_plugin
Install the Protocol Buffer compiler. On macOS:
brew install protobuf
On Ubuntu/Debian:
sudo apt update sudo apt install protobuf-compiler # Verify installation protoc --version
On Windows, download from GitHub releases and add to your PATH.
Step 2: Set Up Your Flutter Project
Create a new Flutter project:
flutter create flutter_protobuf_demo cd flutter_protobuf_demo mkdir protos mkdir lib/generated
Add protobuf to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
protobuf: ^3.1.0
dev_dependencies:
flutter_test:
sdk: flutterInstall dependencies:
flutter pub get
Step 3: Create Your .proto File
Create protos/subscriber.proto:
syntax = "proto3";
package telecom;
// Mobile subscriber information
message Subscriber {
string msisdn = 1; // Mobile number
string name = 2; // Subscriber name
string email = 3; // Email address
SubscriptionType type = 4; // Plan type
bool active = 5; // Account status
repeated string services = 6; // Active services
}
enum SubscriptionType {
PREPAID = 0;
POSTPAID = 1;
CORPORATE = 2;
}Step 4: Generate Dart Code
Make sure the Dart protoc plugin is in your PATH:
export PATH="$PATH:$HOME/.pub-cache/bin" # On Windows PowerShell: $env:PATH += ";$HOME\.pub-cache\bin"
Generate Dart code:
protoc --dart_out=lib/generated --proto_path=protos protos/subscriber.proto
This creates Dart files in lib/generated/.
Step 5: Use Protocol Buffers in Dart
Create a simple Dart script to test (lib/main.dart):
import 'generated/subscriber.pb.dart';
void main() {
// Create a new subscriber
final subscriber = Subscriber()
..msisdn = '+91-9876543210'
..name = 'Telecom Customer'
..email = '[email protected]'
..type = SubscriptionType.POSTPAID
..active = true
..services.addAll(['Voice', 'Data', '4G LTE']);
print('Created subscriber: ${subscriber.name}');
print('MSISDN: ${subscriber.msisdn}');
print('Type: ${subscriber.type}');
// Serialize to binary
final bytes = subscriber.writeToBuffer();
print('Serialized to ${bytes.length} bytes\n');
// Deserialize from binary
final decoded = Subscriber.fromBuffer(bytes);
print('Deserialized subscriber: ${decoded.name}');
print('Email: ${decoded.email}');
print('Active: ${decoded.active}');
// Access repeated fields
print('\nActive services:');
for (var service in decoded.services) {
print(' - $service');
}
}Step 6: Run Your Application
Run the Dart script:
dart run lib/main.dart
You should see output like:
Created subscriber: Telecom Customer MSISDN: +91-9876543210 Type: SubscriptionType.POSTPAID Serialized to 68 bytes Deserialized subscriber: Telecom Customer Email: [email protected] Active: true Active services: - Voice - Data - 4G LTE
Using Protobuf in a Flutter Widget
Here's how to use Protobuf in a Flutter UI:
import 'package:flutter/material.dart';
import 'generated/subscriber.pb.dart';
class SubscriberScreen extends StatefulWidget {
@override
_SubscriberScreenState createState() => _SubscriberScreenState();
}
class _SubscriberScreenState extends State<SubscriberScreen> {
Subscriber? subscriber;
@override
void initState() {
super.initState();
_loadSubscriber();
}
void _loadSubscriber() {
// Create subscriber (in real app, load from API)
final sub = Subscriber()
..msisdn = '+91-9876543210'
..name = 'Telecom Customer'
..email = '[email protected]'
..type = SubscriptionType.POSTPAID
..active = true
..services.addAll(['Voice', 'Data', '4G LTE']);
setState(() {
subscriber = sub;
});
}
@override
Widget build(BuildContext context) {
if (subscriber == null) {
return Scaffold(
appBar: AppBar(title: Text('Subscriber')),
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(title: Text('Subscriber Details')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Name: ${subscriber!.name}',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('MSISDN: ${subscriber!.msisdn}'),
Text('Email: ${subscriber!.email}'),
Text('Type: ${subscriber!.type}'),
Text('Status: ${subscriber!.active ? "Active" : "Inactive"}'),
SizedBox(height: 16),
Text(
'Services:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
...subscriber!.services.map((service) => Text(' • $service')),
],
),
),
);
}
}Sending Protobuf Over HTTP
Use Protobuf with HTTP requests in Flutter:
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'generated/subscriber.pb.dart';
Future<void> sendSubscriber() async {
// Create subscriber
final subscriber = Subscriber()
..msisdn = '+91-9876543210'
..name = 'Mobile User'
..type = SubscriptionType.PREPAID;
// Serialize to bytes
final bytes = subscriber.writeToBuffer();
// Send to server
final response = await http.post(
Uri.parse('https://api.example.com/subscribers'),
headers: {
'Content-Type': 'application/x-protobuf',
},
body: bytes,
);
if (response.statusCode == 200) {
print('Subscriber sent successfully');
}
}
Future<Subscriber?> fetchSubscriber(String msisdn) async {
// Fetch from server
final response = await http.get(
Uri.parse('https://api.example.com/subscribers/$msisdn'),
headers: {
'Accept': 'application/x-protobuf',
},
);
if (response.statusCode == 200) {
// Parse protobuf response
return Subscriber.fromBuffer(response.bodyBytes);
}
return null;
}Convert Between JSON and Protobuf
Dart protobuf supports JSON conversion:
import 'dart:convert';
import 'generated/subscriber.pb.dart';
void jsonExample() {
final subscriber = Subscriber()
..msisdn = '+91-9876543210'
..name = 'Mobile User'
..type = SubscriptionType.PREPAID
..active = true;
// Convert to JSON
final jsonString = jsonEncode(subscriber.toProto3Json());
print('JSON output:');
print(jsonString);
// Parse from JSON
final jsonData = {
'msisdn': '+91-9111111111',
'name': 'New Subscriber',
'type': 'POSTPAID',
'active': true,
};
final parsed = Subscriber()..mergeFromProto3Json(jsonData);
print('\nParsed from JSON: ${parsed.name}');
print('Type: ${parsed.type}');
}Save to Local Storage
Save and load protobuf data from device storage:
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'generated/subscriber.pb.dart';
Future<void> saveSubscriber(Subscriber subscriber) async {
// Get app documents directory
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/subscriber.bin');
// Write protobuf bytes
await file.writeAsBytes(subscriber.writeToBuffer());
print('Saved to ${file.path}');
}
Future<Subscriber?> loadSubscriber() async {
try {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/subscriber.bin');
// Read and parse
final bytes = await file.readAsBytes();
return Subscriber.fromBuffer(bytes);
} catch (e) {
print('Error loading subscriber: $e');
return null;
}
}
// Usage in Flutter
void example() async {
final subscriber = Subscriber()
..msisdn = '+91-9876543210'
..name = 'Saved User'
..type = SubscriptionType.POSTPAID;
await saveSubscriber(subscriber);
final loaded = await loadSubscriber();
if (loaded != null) {
print('Loaded: ${loaded.name}');
}
}Note: Don't forget to add path_provider to your pubspec.yaml dependencies.
Best Practices for Flutter
Use Protobuf for API Communication
Protobuf shines in mobile apps. Smaller message sizes mean less data usage, faster loading, and better battery life.
Cache Protobuf Locally
Save protobuf messages to device storage for offline access. They're more compact than JSON, saving storage space.
Use Cascade Notation
Dart's cascade operator (..) makes building protobuf messages clean and readable.
Handle Network Errors
Always wrap protobuf parsing in try-catch blocks, especially when reading from network or storage.
Common Issues
Issue: protoc_plugin not found
Solution: Make sure ~/.pub-cache/bin (or Windows equivalent) is in your PATH. Reactivate withdart pub global activate protoc_plugin.
Issue: Import errors in generated files
Solution: Make sure the protobuf package is added to your pubspec.yaml and run flutter pub get.
Issue: Hot reload doesn't pick up proto changes
Solution: After regenerating proto files, do a full restart (flutter run) instead of hot reload.
Related Tools
Additional Resources
Official Documentation & References
- Official Dart Protobuf Tutorial - Google's official Dart guide
- Protobuf on pub.dev - Dart package repository
- Dart Protobuf on GitHub - Source code and examples
Conclusion
Protocol Buffers is an excellent choice for Flutter apps. The performance benefits are real: smaller app downloads, reduced bandwidth usage, faster data processing, and better battery life. Dart's clean syntax makes working with protobuf messages natural and enjoyable.
Start by using protobuf for your API communication and local caching. Your users will notice the difference in app responsiveness and data usage. The initial setup is worth the long-term benefits.