Skip to content

Best Practices

This guide covers best practices for building robust, performant, and maintainable applications with LDK.

Architecture & Design Patterns

1. Service Layer Pattern

Organize your LDK usage into service classes for better maintainability:

// services/user_service.dart
class UserService {
  static final UserService _instance = UserService._internal();
  factory UserService() => _instance;
  UserService._internal();

  late final LDKCollection _usersCollection;
  late final LDKEnhancedAuth _auth;

  Future<void> initialize() async {
    _usersCollection = LDK.collection('users') as LDKEnhancedCollection;
    _auth = LDK.instance.enhancedAuth;
  }

  Future<List<User>> getUsers({
    String? search,
    int page = 1,
    int limit = 20,
  }) async {
    final query = LDKQueryBuilder()
      .limit(limit)
      .offset((page - 1) * limit)
      .sort(['-createdAt']);

    if (search != null && search.isNotEmpty) {
      query.where('username', containsi: search);
    }

    final response = await _usersCollection.get(
      query: query,
      useCache: true,
    );

    return response.data.map((json) => User.fromJson(json)).toList();
  }

  Future<User> createUser(CreateUserRequest request) async {
    final response = await _usersCollection.create(
      request.toJson(),
      optimistic: true,
    );

    return User.fromJson(response);
  }

  Stream<User> watchUser(String userId) {
    return _usersCollection.subscribeToEntry(userId).map((event) {
      return User.fromJson(event.data);
    });
  }
}

2. Repository Pattern

Implement repositories for complex data operations:

// repositories/post_repository.dart
abstract class PostRepository {
  Future<List<Post>> getPosts({PostFilter? filter});
  Future<Post> getPost(String id);
  Future<Post> createPost(CreatePostRequest request);
  Future<Post> updatePost(String id, UpdatePostRequest request);
  Future<void> deletePost(String id);
  Stream<List<Post>> watchPosts({PostFilter? filter});
}

class LDKPostRepository implements PostRepository {
  final LDKEnhancedCollection _collection;
  final LDKIntelligentCache _cache;

  LDKPostRepository()
      : _collection = LDK.collection('posts') as LDKEnhancedCollection,
        _cache = LDK.instance.cache;

  @override
  Future<List<Post>> getPosts({PostFilter? filter}) async {
    final query = _buildQuery(filter);

    final response = await _collection.get(
      query: query,
      useCache: true,
    );

    return response.data.map((json) => Post.fromJson(json)).toList();
  }

  @override
  Stream<List<Post>> watchPosts({PostFilter? filter}) {
    return _collection.watchQuery(
      query: _buildQuery(filter),
      refreshInterval: Duration(seconds: 30),
    ).map((response) {
      return response.data.map((json) => Post.fromJson(json)).toList();
    });
  }

  LDKQueryBuilder _buildQuery(PostFilter? filter) {
    final query = LDKQueryBuilder()
      .populate(['author', 'categories'])
      .sort(['-publishedAt']);

    if (filter != null) {
      if (filter.authorId != null) {
        query.where('author', eq: filter.authorId);
      }
      if (filter.categoryId != null) {
        query.where('categories', eq: filter.categoryId);
      }
      if (filter.status != null) {
        query.where('status', eq: filter.status);
      }
      if (filter.search != null) {
        query.or([
          LDKQueryBuilder().where('title', containsi: filter.search),
          LDKQueryBuilder().where('content', containsi: filter.search),
        ]);
      }
    }

    return query;
  }
}

3. State Management Integration

Integrate LDK with your preferred state management solution:

With Provider

// providers/posts_provider.dart
class PostsProvider extends ChangeNotifier {
  final PostRepository _repository;

  List<Post> _posts = [];
  bool _isLoading = false;
  String? _error;
  StreamSubscription? _subscription;

  PostsProvider(this._repository);

  List<Post> get posts => _posts;
  bool get isLoading => _isLoading;
  String? get error => _error;

  Future<void> loadPosts({PostFilter? filter}) async {
    _setLoading(true);
    _setError(null);

    try {
      _posts = await _repository.getPosts(filter: filter);
      notifyListeners();
    } catch (e) {
      _setError(e.toString());
    } finally {
      _setLoading(false);
    }
  }

  void startWatching({PostFilter? filter}) {
    _subscription?.cancel();
    _subscription = _repository.watchPosts(filter: filter).listen(
      (posts) {
        _posts = posts;
        notifyListeners();
      },
      onError: (error) {
        _setError(error.toString());
      },
    );
  }

  void _setLoading(bool loading) {
    _isLoading = loading;
    notifyListeners();
  }

  void _setError(String? error) {
    _error = error;
    notifyListeners();
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
}

With Riverpod

// providers/posts_providers.dart
final postRepositoryProvider = Provider<PostRepository>((ref) {
  return LDKPostRepository();
});

final postsProvider = StateNotifierProvider<PostsNotifier, AsyncValue<List<Post>>>((ref) {
  final repository = ref.watch(postRepositoryProvider);
  return PostsNotifier(repository);
});

class PostsNotifier extends StateNotifier<AsyncValue<List<Post>>> {
  final PostRepository _repository;
  StreamSubscription? _subscription;

  PostsNotifier(this._repository) : super(const AsyncValue.loading()) {
    loadPosts();
  }

  Future<void> loadPosts({PostFilter? filter}) async {
    state = const AsyncValue.loading();

    try {
      final posts = await _repository.getPosts(filter: filter);
      state = AsyncValue.data(posts);
    } catch (error, stackTrace) {
      state = AsyncValue.error(error, stackTrace);
    }
  }

  void startWatching({PostFilter? filter}) {
    _subscription?.cancel();
    _subscription = _repository.watchPosts(filter: filter).listen(
      (posts) {
        state = AsyncValue.data(posts);
      },
      onError: (error, stackTrace) {
        state = AsyncValue.error(error, stackTrace);
      },
    );
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
}

Performance Optimization

1. Efficient Data Loading

Use pagination and lazy loading for large datasets:

class InfiniteScrollController {
  final PostRepository _repository;
  final int _pageSize;

  List<Post> _posts = [];
  int _currentPage = 1;
  bool _isLoading = false;
  bool _hasMore = true;

  InfiniteScrollController(this._repository, {int pageSize = 20})
      : _pageSize = pageSize;

  List<Post> get posts => _posts;
  bool get isLoading => _isLoading;
  bool get hasMore => _hasMore;

  Future<void> loadNextPage() async {
    if (_isLoading || !_hasMore) return;

    _isLoading = true;

    try {
      final newPosts = await _repository.getPosts(
        filter: PostFilter(
          page: _currentPage,
          limit: _pageSize,
        ),
      );

      if (newPosts.length < _pageSize) {
        _hasMore = false;
      }

      _posts.addAll(newPosts);
      _currentPage++;
    } catch (e) {
      // Handle error
      rethrow;
    } finally {
      _isLoading = false;
    }
  }

  void reset() {
    _posts.clear();
    _currentPage = 1;
    _hasMore = true;
  }
}

// Usage in widget
class PostListWidget extends StatefulWidget {
  @override
  _PostListWidgetState createState() => _PostListWidgetState();
}

class _PostListWidgetState extends State<PostListWidget> {
  late final InfiniteScrollController _controller;
  late final ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _controller = InfiniteScrollController(PostRepository());
    _scrollController = ScrollController()..addListener(_onScroll);
    _controller.loadNextPage();
  }

  void _onScroll() {
    if (_scrollController.position.pixels >= 
        _scrollController.position.maxScrollExtent - 200) {
      _controller.loadNextPage();
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: _controller.posts.length + (_controller.hasMore ? 1 : 0),
      itemBuilder: (context, index) {
        if (index == _controller.posts.length) {
          return Center(child: CircularProgressIndicator());
        }

        return PostTile(post: _controller.posts[index]);
      },
    );
  }
}

2. Smart Caching Strategies

Configure caching based on data characteristics:

class CacheConfigurationService {
  static Future<void> configureCaching() async {
    final cache = LDK.instance.cache;

    // Static content - long TTL, LFU eviction
    await cache.setCacheStrategy('categories', CacheStrategy(
      ttl: Duration(hours: 24),
      maxSize: 100,
      evictionPolicy: EvictionPolicy.lfu,
      backgroundRefresh: true,
    ));

    // User-generated content - medium TTL, LRU eviction
    await cache.setCacheStrategy('posts', CacheStrategy(
      ttl: Duration(minutes: 30),
      maxSize: 500,
      evictionPolicy: EvictionPolicy.lru,
      refreshOnAccess: true,
      backgroundRefresh: true,
    ));

    // Real-time data - short TTL, FIFO eviction
    await cache.setCacheStrategy('notifications', CacheStrategy(
      ttl: Duration(minutes: 5),
      maxSize: 50,
      evictionPolicy: EvictionPolicy.fifo,
      autoInvalidate: true,
    ));

    // User profiles - medium TTL, size-based eviction
    await cache.setCacheStrategy('users', CacheStrategy(
      ttl: Duration(hours: 2),
      maxSize: 200,
      evictionPolicy: EvictionPolicy.lru,
    ));
  }
}

3. Optimistic Updates

Implement optimistic updates for better UX:

class OptimisticUpdateService {
  final LDKEnhancedCollection _collection;
  final StreamController<List<Post>> _postsController;

  OptimisticUpdateService(this._collection) 
      : _postsController = StreamController.broadcast();

  Stream<List<Post>> get postsStream => _postsController.stream;

  Future<Post> createPost(CreatePostRequest request) async {
    // Create optimistic post
    final optimisticPost = Post(
      id: 'temp_${DateTime.now().millisecondsSinceEpoch}',
      title: request.title,
      content: request.content,
      status: 'draft',
      isOptimistic: true,
      createdAt: DateTime.now(),
    );

    // Add to stream immediately
    _addOptimisticPost(optimisticPost);

    try {
      // Create actual post
      final response = await _collection.create(
        request.toJson(),
        optimistic: true,
      );

      final realPost = Post.fromJson(response);

      // Replace optimistic post with real post
      _replaceOptimisticPost(optimisticPost.id, realPost);

      return realPost;
    } catch (e) {
      // Remove optimistic post on error
      _removeOptimisticPost(optimisticPost.id);
      rethrow;
    }
  }

  void _addOptimisticPost(Post post) {
    // Implementation depends on your state management
  }

  void _replaceOptimisticPost(String optimisticId, Post realPost) {
    // Implementation depends on your state management
  }

  void _removeOptimisticPost(String optimisticId) {
    // Implementation depends on your state management
  }
}

Error Handling

1. Comprehensive Error Handling

Create a centralized error handling system:

// services/error_service.dart
class ErrorService {
  static final ErrorService _instance = ErrorService._internal();
  factory ErrorService() => _instance;
  ErrorService._internal();

  final StreamController<AppError> _errorController = StreamController.broadcast();
  Stream<AppError> get errorStream => _errorController.stream;

  void handleError(dynamic error, {String? context}) {
    final appError = _mapError(error, context);
    _errorController.add(appError);

    // Log error
    _logError(appError);

    // Report to crash analytics if needed
    _reportError(appError);
  }

  AppError _mapError(dynamic error, String? context) {
    if (error is LDKAuthException) {
      return AppError(
        type: ErrorType.authentication,
        message: _getAuthErrorMessage(error),
        originalError: error,
        context: context,
      );
    } else if (error is LDKServerException) {
      return AppError(
        type: ErrorType.server,
        message: _getServerErrorMessage(error),
        originalError: error,
        context: context,
      );
    } else if (error is LDKNetworkException) {
      return AppError(
        type: ErrorType.network,
        message: 'Network connection failed. Please check your internet connection.',
        originalError: error,
        context: context,
      );
    } else {
      return AppError(
        type: ErrorType.unknown,
        message: 'An unexpected error occurred.',
        originalError: error,
        context: context,
      );
    }
  }

  String _getAuthErrorMessage(LDKAuthException error) {
    switch (error.code) {
      case 'invalid_credentials':
        return 'Invalid email or password.';
      case 'account_locked':
        return 'Your account has been temporarily locked.';
      case 'token_expired':
        return 'Your session has expired. Please sign in again.';
      default:
        return 'Authentication failed.';
    }
  }

  String _getServerErrorMessage(LDKServerException error) {
    if (error.statusCode == 404) {
      return 'The requested resource was not found.';
    } else if (error.statusCode == 500) {
      return 'Server error. Please try again later.';
    } else {
      return 'Server error: ${error.message}';
    }
  }

  void _logError(AppError error) {
    print('Error [${error.type}]: ${error.message}');
    if (error.context != null) {
      print('Context: ${error.context}');
    }
  }

  void _reportError(AppError error) {
    // Report to crash analytics service
    // FirebaseCrashlytics.instance.recordError(error.originalError, null);
  }
}

class AppError {
  final ErrorType type;
  final String message;
  final dynamic originalError;
  final String? context;

  AppError({
    required this.type,
    required this.message,
    required this.originalError,
    this.context,
  });
}

enum ErrorType {
  authentication,
  network,
  server,
  validation,
  unknown,
}

2. Retry Logic

Implement intelligent retry mechanisms:

class RetryService {
  static Future<T> withRetry<T>(
    Future<T> Function() operation, {
    int maxRetries = 3,
    Duration delay = const Duration(seconds: 1),
    bool Function(dynamic error)? shouldRetry,
  }) async {
    int attempts = 0;

    while (attempts < maxRetries) {
      try {
        return await operation();
      } catch (error) {
        attempts++;

        if (attempts >= maxRetries || 
            (shouldRetry != null && !shouldRetry(error))) {
          rethrow;
        }

        // Exponential backoff
        final retryDelay = delay * math.pow(2, attempts - 1);
        await Future.delayed(retryDelay);
      }
    }

    throw StateError('Retry logic error'); // Should never reach here
  }

  static bool shouldRetryNetworkError(dynamic error) {
    if (error is LDKNetworkException) {
      return true;
    }
    if (error is LDKServerException) {
      // Retry on 5xx errors, not on 4xx errors
      return error.statusCode >= 500;
    }
    return false;
  }
}

// Usage
Future<List<Post>> getPosts() async {
  return RetryService.withRetry(
    () => _repository.getPosts(),
    maxRetries: 3,
    shouldRetry: RetryService.shouldRetryNetworkError,
  );
}

Security Best Practices

1. Token Management

Implement secure token handling:

class TokenManager {
  static const String _tokenKey = 'auth_token';
  static const String _refreshTokenKey = 'refresh_token';

  final FlutterSecureStorage _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: IOSAccessibility.first_unlock_this_device,
    ),
  );

  Future<void> storeTokens({
    required String accessToken,
    required String refreshToken,
  }) async {
    await Future.wait([
      _storage.write(key: _tokenKey, value: accessToken),
      _storage.write(key: _refreshTokenKey, value: refreshToken),
    ]);
  }

  Future<String?> getAccessToken() async {
    return await _storage.read(key: _tokenKey);
  }

  Future<String?> getRefreshToken() async {
    return await _storage.read(key: _refreshTokenKey);
  }

  Future<void> clearTokens() async {
    await Future.wait([
      _storage.delete(key: _tokenKey),
      _storage.delete(key: _refreshTokenKey),
    ]);
  }

  Future<bool> isTokenExpired(String token) async {
    try {
      final payload = _decodeJWT(token);
      final exp = payload['exp'] as int;
      final expiryDate = DateTime.fromMillisecondsSinceEpoch(exp * 1000);

      // Consider token expired 5 minutes before actual expiry
      return DateTime.now().isAfter(expiryDate.subtract(Duration(minutes: 5)));
    } catch (e) {
      return true; // Assume expired if can't decode
    }
  }

  Map<String, dynamic> _decodeJWT(String token) {
    final parts = token.split('.');
    if (parts.length != 3) {
      throw FormatException('Invalid JWT token');
    }

    final payload = parts[1];
    final normalized = base64Url.normalize(payload);
    final decoded = utf8.decode(base64Url.decode(normalized));

    return jsonDecode(decoded) as Map<String, dynamic>;
  }
}

2. Input Validation

Validate data before sending to server:

class ValidationService {
  static String? validateEmail(String? email) {
    if (email == null || email.isEmpty) {
      return 'Email is required';
    }

    final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$');
    if (!emailRegex.hasMatch(email)) {
      return 'Please enter a valid email address';
    }

    return null;
  }

  static String? validatePassword(String? password) {
    if (password == null || password.isEmpty) {
      return 'Password is required';
    }

    if (password.length < 8) {
      return 'Password must be at least 8 characters long';
    }

    if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)').hasMatch(password)) {
      return 'Password must contain at least one uppercase letter, one lowercase letter, and one number';
    }

    return null;
  }

  static String? validateRequired(String? value, String fieldName) {
    if (value == null || value.trim().isEmpty) {
      return '$fieldName is required';
    }
    return null;
  }

  static String? validateLength(String? value, int minLength, int maxLength, String fieldName) {
    if (value == null) return null;

    if (value.length < minLength) {
      return '$fieldName must be at least $minLength characters long';
    }

    if (value.length > maxLength) {
      return '$fieldName must be no more than $maxLength characters long';
    }

    return null;
  }
}

// Usage in forms
class CreatePostForm extends StatefulWidget {
  @override
  _CreatePostFormState createState() => _CreatePostFormState();
}

class _CreatePostFormState extends State<CreatePostForm> {
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _titleController,
            decoration: InputDecoration(labelText: 'Title'),
            validator: (value) {
              return ValidationService.validateRequired(value, 'Title') ??
                     ValidationService.validateLength(value, 1, 100, 'Title');
            },
          ),
          TextFormField(
            controller: _contentController,
            decoration: InputDecoration(labelText: 'Content'),
            maxLines: 5,
            validator: (value) {
              return ValidationService.validateRequired(value, 'Content') ??
                     ValidationService.validateLength(value, 10, 5000, 'Content');
            },
          ),
          ElevatedButton(
            onPressed: _submitForm,
            child: Text('Create Post'),
          ),
        ],
      ),
    );
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      // Form is valid, proceed with submission
      _createPost();
    }
  }

  Future<void> _createPost() async {
    try {
      await PostService().createPost(CreatePostRequest(
        title: _titleController.text.trim(),
        content: _contentController.text.trim(),
      ));

      Navigator.pop(context);
    } catch (e) {
      ErrorService().handleError(e, context: 'Create Post');
    }
  }
}

Testing Strategies

1. Unit Testing Services

// test/services/post_service_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([LDKEnhancedCollection])
void main() {
  group('PostService', () {
    late PostService postService;
    late MockLDKEnhancedCollection mockCollection;

    setUp(() {
      mockCollection = MockLDKEnhancedCollection();
      postService = PostService(mockCollection);
    });

    test('should return posts when getPosts is called', () async {
      // Arrange
      final mockResponse = StrapiResponse<List<Map<String, dynamic>>>(
        data: [
          {
            'id': '1',
            'attributes': {
              'title': 'Test Post',
              'content': 'Test Content',
              'createdAt': '2023-01-01T00:00:00.000Z',
            }
          }
        ],
        meta: {},
      );

      when(mockCollection.get(query: anyNamed('query')))
          .thenAnswer((_) async => mockResponse);

      // Act
      final result = await postService.getPosts();

      // Assert
      expect(result, hasLength(1));
      expect(result.first.title, equals('Test Post'));
      verify(mockCollection.get(query: anyNamed('query'))).called(1);
    });

    test('should throw exception when getPosts fails', () async {
      // Arrange
      when(mockCollection.get(query: anyNamed('query')))
          .thenThrow(LDKServerException('Server error'));

      // Act & Assert
      expect(
        () => postService.getPosts(),
        throwsA(isA<LDKServerException>()),
      );
    });
  });
}

2. Integration Testing

// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('App Integration Tests', () {
    testWidgets('should login and load posts', (WidgetTester tester) async {
      // Start the app
      app.main();
      await tester.pumpAndSettle();

      // Find login form
      expect(find.byType(TextField), findsNWidgets(2));

      // Enter credentials
      await tester.enterText(find.byType(TextField).first, 'test@example.com');
      await tester.enterText(find.byType(TextField).last, 'password123');

      // Tap login button
      await tester.tap(find.text('Sign In'));
      await tester.pumpAndSettle();

      // Verify navigation to home screen
      expect(find.text('Posts'), findsOneWidget);

      // Wait for posts to load
      await tester.pumpAndSettle(Duration(seconds: 3));

      // Verify posts are displayed
      expect(find.byType(ListTile), findsAtLeastNWidgets(1));
    });
  });
}

Monitoring & Analytics

1. Performance Monitoring

class PerformanceMonitor {
  static final PerformanceMonitor _instance = PerformanceMonitor._internal();
  factory PerformanceMonitor() => _instance;
  PerformanceMonitor._internal();

  final Map<String, Stopwatch> _timers = {};

  void startTimer(String operation) {
    _timers[operation] = Stopwatch()..start();
  }

  void stopTimer(String operation) {
    final timer = _timers[operation];
    if (timer != null) {
      timer.stop();
      final duration = timer.elapsedMilliseconds;

      _logPerformance(operation, duration);
      _timers.remove(operation);
    }
  }

  void _logPerformance(String operation, int durationMs) {
    print('Performance: $operation took ${durationMs}ms');

    // Report to analytics service
    // FirebaseAnalytics.instance.logEvent(
    //   name: 'performance_metric',
    //   parameters: {
    //     'operation': operation,
    //     'duration_ms': durationMs,
    //   },
    // );
  }

  Future<T> measureAsync<T>(String operation, Future<T> Function() function) async {
    startTimer(operation);
    try {
      return await function();
    } finally {
      stopTimer(operation);
    }
  }
}

// Usage
class PostService {
  Future<List<Post>> getPosts() async {
    return PerformanceMonitor().measureAsync('get_posts', () async {
      final response = await _collection.get();
      return response.data.map((json) => Post.fromJson(json)).toList();
    });
  }
}

2. Usage Analytics

class AnalyticsService {
  static final AnalyticsService _instance = AnalyticsService._internal();
  factory AnalyticsService() => _instance;
  AnalyticsService._internal();

  void trackEvent(String eventName, {Map<String, dynamic>? parameters}) {
    print('Analytics: $eventName ${parameters ?? ''}');

    // Send to analytics service
    // FirebaseAnalytics.instance.logEvent(
    //   name: eventName,
    //   parameters: parameters,
    // );
  }

  void trackScreenView(String screenName) {
    trackEvent('screen_view', parameters: {'screen_name': screenName});
  }

  void trackUserAction(String action, {Map<String, dynamic>? context}) {
    trackEvent('user_action', parameters: {
      'action': action,
      ...?context,
    });
  }

  void trackError(String error, {String? context}) {
    trackEvent('error_occurred', parameters: {
      'error': error,
      if (context != null) 'context': context,
    });
  }
}

// Usage in widgets
class PostListScreen extends StatefulWidget {
  @override
  _PostListScreenState createState() => _PostListScreenState();
}

class _PostListScreenState extends State<PostListScreen> {
  @override
  void initState() {
    super.initState();
    AnalyticsService().trackScreenView('post_list');
  }

  void _onPostTapped(Post post) {
    AnalyticsService().trackUserAction('post_tapped', context: {
      'post_id': post.id,
      'post_title': post.title,
    });

    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => PostDetailScreen(post: post)),
    );
  }
}

Deployment Considerations

1. Environment Configuration

// config/environment.dart
enum Environment { development, staging, production }

class EnvironmentConfig {
  static const Environment _environment = Environment.production; // Set via build flags

  static Environment get environment => _environment;

  static String get strapiUrl {
    switch (_environment) {
      case Environment.development:
        return 'http://localhost:1337';
      case Environment.staging:
        return 'https://staging-api.yourapp.com';
      case Environment.production:
        return 'https://api.yourapp.com';
    }
  }

  static bool get enableLogging => _environment != Environment.production;

  static Duration get cacheTimeout {
    switch (_environment) {
      case Environment.development:
        return Duration(minutes: 1);
      case Environment.staging:
        return Duration(minutes: 5);
      case Environment.production:
        return Duration(minutes: 30);
    }
  }
}

2. Build Configuration

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize LDK with environment-specific configuration
  await LDK.initialize(
    baseUrl: EnvironmentConfig.strapiUrl,
    enableLogging: EnvironmentConfig.enableLogging,
    cacheConfig: {
      'strategies': {
        'default': {
          'ttl': EnvironmentConfig.cacheTimeout.inMilliseconds,
          'maxSize': 100,
          'evictionPolicy': 'lru',
        },
      },
    },
  );

  runApp(MyApp());
}

Summary

Following these best practices will help you build robust, scalable, and maintainable applications with LDK:

  1. Architecture: Use service layers and repositories for clean separation of concerns
  2. Performance: Implement efficient data loading, caching, and optimistic updates
  3. Error Handling: Create comprehensive error handling with retry logic
  4. Security: Implement secure token management and input validation
  5. Testing: Write unit and integration tests for critical functionality
  6. Monitoring: Track performance and usage analytics
  7. Deployment: Use environment-specific configurations

These patterns will help you leverage LDK's full potential while maintaining code quality and user experience.


Next: Performance Optimization →