Flutter SDK
The Calljmp Flutter SDK provides a comprehensive solution for mobile backend development, featuring secure authentication, direct SQLite database access, file storage, and custom service integration. Built with security-first principles, it uses App Attestation (iOS) and Play Integrity (Android) instead of traditional API keys.
Key Features
- Secure Authentication - Email/password authentication with user tagging
- Direct Database Access - Full SQLite database access with parameterized queries
- File Storage - Secure cloud storage with bucket organization
- Device Attestation - App Attestation (iOS) and Play Integrity (Android)
- Custom Services - Integration with custom backend services
- Project Management - Multi-project support with connection management
Installation
Add the Calljmp SDK to your Flutter project:
pubspec.yaml
dependencies:
calljmp: ^latest
Install the dependencies:
flutter pub get
Quick Start
Initialize the SDK
Import and initialize Calljmp in your Flutter app:
lib/main.dart
import 'package:calljmp/calljmp.dart';
final calljmp = Calljmp();
Authentication
Calljmp provides secure email/password authentication with user tagging and flexible policies.
Email Authentication
Authenticate users with email and password:
final result = await calljmp.users.auth.email.authenticate(
email: "user@example.com",
password: "securePassword123",
policy: UserAuthenticationPolicy.signInOrCreate,
tags: ["role:user", "department:sales"],
);
if (result.error == null) {
print('User authenticated: ${result.value?.email}');
print('User tags: ${result.value?.tags}');
} else {
print('Authentication failed: ${result.error}');
}
Authentication Policies
Control authentication behavior with different policies:
// Only create new accounts, fail if account exists
await calljmp.users.auth.email.authenticate(
email: "newuser@example.com",
password: "password",
policy: UserAuthenticationPolicy.createNewOnly,
);
// Only sign into existing accounts, fail if account doesn't exist
await calljmp.users.auth.email.authenticate(
email: "existinguser@example.com",
password: "password",
policy: UserAuthenticationPolicy.signInExistingOnly,
);
// Try to sign in, create account if it doesn't exist (default)
await calljmp.users.auth.email.authenticate(
email: "user@example.com",
password: "password",
policy: UserAuthenticationPolicy.signInOrCreate,
);
Database Operations
Calljmp provides direct SQLite database access with full SQL support and row-level security.
Basic Queries
Execute SQL queries directly:
// Simple SELECT query
final result = await calljmp.database.query(
sql: 'SELECT id, email, tags, created_at FROM users',
);
if (result.error == null) {
final data = result.value!;
print('Found ${data.results.length} users');
for (final row in data.results) {
print('User: ${row['email']} (${row['tags']})');
}
} else {
print('Query failed: ${result.error}');
}
Parameterized Queries
Use parameterized queries for security:
// Query with parameters
final result = await calljmp.database.query(
sql: 'SELECT * FROM posts WHERE author_id = ? AND status = ?',
parameters: [userId, 'published'],
);
File Storage
Calljmp provides secure cloud storage with bucket organization and access controls.
Upload Files
Upload various types of content:
import 'dart:typed_data';
import 'package:http_parser/http_parser.dart';
// Upload text content
final textFile = await calljmp.storage.upload(
content: 'Hello, world! This is my first text file.',
contentType: MediaType('text', 'plain'),
bucketId: 'documents',
key: 'messages/hello.txt',
description: 'A simple greeting',
tags: ['text', 'greeting'],
);
if (textFile.error == null) {
print('File uploaded: ${textFile.value?.key}');
print('File size: ${textFile.value?.size} bytes');
} else {
print('Upload failed: ${textFile.error}');
}
Upload Binary Files
// Upload image from bytes
final imageBytes = Uint8List.fromList([...]); // Your image data
final imageFile = await calljmp.storage.upload(
content: imageBytes,
contentType: MediaType('image', 'jpeg'),
bucketId: 'user-uploads',
key: 'profiles/user_${userId}/avatar.jpg',
description: 'User profile picture',
tags: ['profile', 'avatar', 'user:$userId'],
);
// Upload document from file path
final documentFile = await calljmp.storage.upload(
filePath: '/path/to/document.pdf',
bucketId: 'documents',
key: 'contracts/agreement_${DateTime.now().millisecondsSinceEpoch}.pdf',
description: 'Service agreement',
tags: ['legal', 'contract', 'user:$userId'],
);
List Files
Browse files in a bucket:
final result = await calljmp.storage.list(
bucketId: 'user-uploads',
limit: 50,
offset: 0,
);
if (result.error == null) {
final data = result.value!;
print('Found ${data.files.length} files');
for (final file in data.files) {
print('${file.key}: ${file.size} bytes');
print(' Description: ${file.description}');
print(' Tags: ${file.tags}');
}
} else {
print('Failed to list files: ${result.error}');
}
Download Files
Retrieve file content:
// Get file metadata without downloading content
final fileInfo = await calljmp.storage.peek(
bucketId: 'user-uploads',
key: 'profiles/user_123/avatar.jpg',
);
if (fileInfo.error == null) {
final file = fileInfo.value!;
print('File size: ${file.size} bytes');
print('Content type: ${file.type}');
print('Description: ${file.description}');
}
// Download file content as stream
final streamResult = await calljmp.storage.retrieve(
bucketId: 'user-uploads',
key: 'profiles/user_123/avatar.jpg',
);
if (streamResult.error == null) {
final stream = streamResult.value!;
// Process the stream data
}
Update File Metadata
await calljmp.storage.update(
bucketId: 'user-uploads',
key: 'profiles/user_123/avatar.jpg',
description: 'Updated profile picture',
tags: ['profile', 'avatar', 'updated'],
);
Delete Files
await calljmp.storage.delete(
bucketId: 'user-uploads',
key: 'profiles/user_123/old_avatar.jpg',
);
Custom Services
Call your deployed backend services with automatic authentication.
Service Requests
Make authenticated requests to your custom endpoints:
// Simple GET request
final response = await calljmp.service
.request(route: "/api/user/profile")
.get()
.json((json) => UserProfile.fromJson(json));
if (response.error == null) {
final profile = response.value!;
print('User profile: ${profile.displayName}');
} else {
print('Request failed: ${response.error}');
}
POST Requests with Data
// POST request with JSON body
final newPost = {
'title': 'My New Post',
'content': 'This is the content of my post...',
'tags': ['flutter', 'mobile', 'development'],
};
final response = await calljmp.service
.request(route: "/api/posts")
.post(body: newPost)
.json((json) => Post.fromJson(json));
if (response.error == null) {
final post = response.value!;
print('Created post with ID: ${post.id}');
} else {
print('Failed to create post: ${response.error}');
}
Custom Headers and Query Parameters
// Request with custom headers and query parameters
final response = await calljmp.service
.request(route: "/api/search")
.headers({
'X-Custom-Header': 'value',
'Accept-Language': 'en-US',
})
.params({
'q': 'flutter development',
'limit': '20',
'offset': '0',
})
.get()
.json((json) => SearchResults.fromJson(json));
Error Handling
Calljmp uses a consistent error handling pattern across all operations:
// All operations return Result<T, CalljmpError>
final result = await calljmp.database.query(sql: 'SELECT * FROM users');
if (result.error != null) {
final error = result.error!;
switch (error.code) {
case CalljmpErrorCode.unauthorized:
print('User not authenticated');
// Redirect to login screen
break;
case CalljmpErrorCode.forbidden:
print('User lacks permission for this operation');
break;
case CalljmpErrorCode.usageExceeded:
print('Usage limit exceeded');
// Show upgrade prompt
break;
case CalljmpErrorCode.networkError:
print('Network connection failed');
// Show retry option
break;
default:
print('Unexpected error: ${error.message}');
}
} else {
// Success - use result.value
final data = result.value!;
// Process successful result
}
Common Error Codes
unauthorized
: User is not authenticatedforbidden
: User lacks required permissionsbadRequest
: Invalid request parametersnotFound
: Requested resource doesn't existusageExceeded
: Account usage limits exceedednetworkError
: Network connectivity issuesinternal
: Server-side error occurred
Best Practices
Security
- Use User Tags: Implement role-based access control with user tags
- Parameterized Queries: Always use parameters to prevent SQL injection
- Validate Input: Validate all user input before database operations
- Least Privilege: Give users minimum required permissions
Performance
- Limit Query Results: Use LIMIT and OFFSET for pagination
- Index Database Columns: Create indexes for frequently queried columns
- Cache Results: Cache frequently accessed data locally
- Optimize File Uploads: Compress images and limit file sizes
Development
- Handle Errors: Always check for errors in result objects
- Test Offline: Ensure graceful handling of network failures
- Monitor Usage: Track API usage to avoid limits
Security
Calljmp provides robust security features to ensure that your app and its data are protected.
App Attestation (iOS)
Calljmp utilizes Apple's App Attestation for verifying the integrity of your iOS app. This ensures that only legitimate apps are communicating with your backend.
For more information, refer to Apple's App Attestation documentation.
Play Integrity (Android)
For Android apps, Google Play Integrity helps ensure that your app is genuine and hasn't been tampered with.
For more details, check out Google Play Integrity documentation.
These features enhance the security of your app, ensuring that your backend communications are from trusted sources.
Next Steps
For more advanced topics and examples, check out:
- Cloud Service Documentation
- CLI Documentation
- React Native SDK (for cross-platform reference)