Skip to main content

Dart/Flutter SDK

Build cross-platform mobile apps with Flutter that integrate with Staqr Platform and Commerce APIs.

Security Notice

Never embed API keys directly in mobile applications.

Instead, use one of these patterns:

  1. OAuth2/OIDC user authentication
  2. Backend proxy for sensitive API calls
  3. Token-based authentication with refresh

See Mobile Security for detailed guidance.


Installation

Add to your pubspec.yaml:

dependencies:
staqr_api_client: ^1.0.0

Then run:

dart pub get

Quick Start

import 'package:staqr_api_client/staqr_api_client.dart';

void main() async {
// Configuration - token from secure source (not hardcoded!)
final client = StaqrApiClient(
baseUrl: 'https://api.staqr.com',
accessToken: await SecureStorage.getToken(),
);

try {
// List flows
final flows = await client.getFlowsApi().listFlows(limit: 50);
print('Found ${flows.length} flows');

for (final flow in flows) {
print(' - ${flow.name} (${flow.id})');
}
} catch (e) {
print('Error: $e');
}
}

Available Packages

PackageAPIPlatform Support
staqr_api_clientStaqr PlatformiOS, Android, Web
staqr_commerce_v0Commerce v0 (Legacy)iOS, Android, Web
staqr_commerce_v1Commerce v1iOS, Android, Web
staqr_commerce_v2Commerce v2iOS, Android, Web

Authentication

Secure Token Storage

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureTokenService {
final FlutterSecureStorage _storage = const FlutterSecureStorage();

Future<void> saveToken(String token) async {
await _storage.write(key: 'staqr_access_token', value: token);
}

Future<String?> getToken() async {
return await _storage.read(key: 'staqr_access_token');
}

Future<void> deleteToken() async {
await _storage.delete(key: 'staqr_access_token');
}
}

OAuth2 Integration

import 'package:flutter_appauth/flutter_appauth.dart';

class AuthService {
final FlutterAppAuth _appAuth = const FlutterAppAuth();

Future<String?> authenticate() async {
final result = await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'your-client-id',
'your-redirect-uri',
issuer: 'https://auth.staqr.com',
scopes: ['openid', 'profile', 'email'],
),
);

return result?.accessToken;
}
}

Flutter State Management

With Provider

import 'package:provider/provider.dart';
import 'package:staqr_api_client/staqr_api_client.dart';

class StaqrProvider extends ChangeNotifier {
StaqrApiClient? _client;
List<Flow> _flows = [];
bool _isLoading = false;

List<Flow> get flows => _flows;
bool get isLoading => _isLoading;

Future<void> initialize(String token) async {
_client = StaqrApiClient(
baseUrl: 'https://api.staqr.com',
accessToken: token,
);
notifyListeners();
}

Future<void> loadFlows() async {
if (_client == null) return;

_isLoading = true;
notifyListeners();

try {
_flows = await _client!.getFlowsApi().listFlows(limit: 100);
} catch (e) {
print('Error loading flows: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
}

// Usage in widget
class FlowsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<StaqrProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const CircularProgressIndicator();
}

return ListView.builder(
itemCount: provider.flows.length,
itemBuilder: (context, index) {
final flow = provider.flows[index];
return ListTile(
title: Text(flow.name),
subtitle: Text(flow.id),
);
},
);
},
);
}
}

With Riverpod

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:staqr_api_client/staqr_api_client.dart';

// Token provider (from secure storage)
final tokenProvider = FutureProvider<String?>((ref) async {
final storage = FlutterSecureStorage();
return await storage.read(key: 'staqr_access_token');
});

// Staqr client provider
final staqrClientProvider = Provider<StaqrApiClient?>((ref) {
final tokenAsync = ref.watch(tokenProvider);

return tokenAsync.when(
data: (token) => token != null
? StaqrApiClient(baseUrl: 'https://api.staqr.com', accessToken: token)
: null,
loading: () => null,
error: (_, __) => null,
);
});

// Flows provider
final flowsProvider = FutureProvider<List<Flow>>((ref) async {
final client = ref.watch(staqrClientProvider);
if (client == null) return [];

return await client.getFlowsApi().listFlows(limit: 100);
});

With Bloc

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:staqr_api_client/staqr_api_client.dart';

// Events
abstract class FlowsEvent {}
class LoadFlows extends FlowsEvent {}
class RefreshFlows extends FlowsEvent {}

// States
abstract class FlowsState {}
class FlowsInitial extends FlowsState {}
class FlowsLoading extends FlowsState {}
class FlowsLoaded extends FlowsState {
final List<Flow> flows;
FlowsLoaded(this.flows);
}
class FlowsError extends FlowsState {
final String message;
FlowsError(this.message);
}

// Bloc
class FlowsBloc extends Bloc<FlowsEvent, FlowsState> {
final StaqrApiClient _client;

FlowsBloc(this._client) : super(FlowsInitial()) {
on<LoadFlows>(_onLoadFlows);
on<RefreshFlows>(_onRefreshFlows);
}

Future<void> _onLoadFlows(LoadFlows event, Emitter<FlowsState> emit) async {
emit(FlowsLoading());
try {
final flows = await _client.getFlowsApi().listFlows(limit: 100);
emit(FlowsLoaded(flows));
} catch (e) {
emit(FlowsError(e.toString()));
}
}

Future<void> _onRefreshFlows(RefreshFlows event, Emitter<FlowsState> emit) async {
try {
final flows = await _client.getFlowsApi().listFlows(limit: 100);
emit(FlowsLoaded(flows));
} catch (e) {
emit(FlowsError(e.toString()));
}
}
}

Error Handling

import 'package:dio/dio.dart';
import 'package:staqr_api_client/staqr_api_client.dart';

Future<void> safeApiCall() async {
try {
final flows = await client.getFlowsApi().listFlows();
// Handle success
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
// Handle timeout
showError('Connection timed out. Please try again.');
break;
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
if (statusCode == 401) {
// Token expired - refresh or re-authenticate
await refreshToken();
} else if (statusCode == 403) {
showError('You do not have permission to perform this action.');
} else if (statusCode == 404) {
showError('Resource not found.');
} else {
showError('Server error. Please try again later.');
}
break;
case DioExceptionType.cancel:
// Request was cancelled
break;
default:
showError('Network error. Please check your connection.');
}
} catch (e) {
showError('An unexpected error occurred.');
}
}

Offline Support

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:hive_flutter/hive_flutter.dart';

class OfflineAwareStaqrService {
final StaqrApiClient _client;
final Box<List<Flow>> _cache;

OfflineAwareStaqrService(this._client, this._cache);

Future<List<Flow>> getFlows({bool forceRefresh = false}) async {
final connectivity = await Connectivity().checkConnectivity();
final isOnline = connectivity != ConnectivityResult.none;

if (isOnline && (forceRefresh || !_cache.containsKey('flows'))) {
// Fetch from API and cache
final flows = await _client.getFlowsApi().listFlows();
await _cache.put('flows', flows);
return flows;
}

// Return cached data
return _cache.get('flows') ?? [];
}
}

Requirements

  • Dart SDK >= 2.17.0
  • Flutter >= 3.0.0 (for Flutter apps)

Dependencies

The SDK uses:

  • dio for HTTP requests
  • json_serializable for JSON parsing
  • built_value for immutable models

Next Steps