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 authenticated
  • forbidden: User lacks required permissions
  • badRequest: Invalid request parameters
  • notFound: Requested resource doesn't exist
  • usageExceeded: Account usage limits exceeded
  • networkError: Network connectivity issues
  • internal: Server-side error occurred

Best Practices

Security

  1. Use User Tags: Implement role-based access control with user tags
  2. Parameterized Queries: Always use parameters to prevent SQL injection
  3. Validate Input: Validate all user input before database operations
  4. Least Privilege: Give users minimum required permissions

Performance

  1. Limit Query Results: Use LIMIT and OFFSET for pagination
  2. Index Database Columns: Create indexes for frequently queried columns
  3. Cache Results: Cache frequently accessed data locally
  4. Optimize File Uploads: Compress images and limit file sizes

Development

  1. Handle Errors: Always check for errors in result objects
  2. Test Offline: Ensure graceful handling of network failures
  3. 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: