Skip to content

Troubleshooting Guide

This guide helps you diagnose and resolve common issues when using LDK (Lazy Development Kit).

Common Issues

1. Initialization Problems

Issue: LDKConfigurationException: LDK has not been initialized

Cause: Attempting to use LDK services before calling LDK.initialize().

Solution:

// ❌ Wrong - using LDK before initialization
void main() {
  runApp(MyApp());
  // LDK.initialize() called later or not at all
}

// ✅ Correct - initialize before using
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await LDK.initialize(
    baseUrl: 'https://your-strapi-server.com',
  );

  runApp(MyApp());
}

Issue: LDKConfigurationException: Base URL cannot be empty

Cause: Empty or null baseUrl parameter.

Solution:

// ❌ Wrong
await LDK.initialize(baseUrl: '');

// ✅ Correct
await LDK.initialize(baseUrl: 'https://your-strapi-server.com');

Issue: LDKConfigurationException: LDK is already initialized

Cause: Calling LDK.initialize() multiple times.

Solution:

// Check if already initialized
if (!LDK.instance.isInitialized) {
  await LDK.initialize(baseUrl: 'https://your-server.com');
}

// Or dispose and reinitialize
await LDK.dispose();
await LDK.initialize(baseUrl: 'https://your-server.com');

2. Network & Connection Issues

Issue: LDKNetworkException: Connection timeout

Cause: Network connectivity issues or server not responding.

Diagnosis:

// Test network connectivity
import 'package:connectivity_plus/connectivity_plus.dart';

Future<bool> checkConnectivity() async {
  final connectivity = Connectivity();
  final result = await connectivity.checkConnectivity();
  return result != ConnectivityResult.none;
}

// Test server reachability
Future<bool> testServerConnection(String baseUrl) async {
  try {
    final response = await http.get(Uri.parse('$baseUrl/api'));
    return response.statusCode < 500;
  } catch (e) {
    return false;
  }
}

Solutions:

// 1. Increase timeout values
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  connectTimeout: Duration(seconds: 60),
  receiveTimeout: Duration(seconds: 60),
);

// 2. Implement retry logic
Future<T> withRetry<T>(Future<T> Function() operation) async {
  int attempts = 0;
  const maxAttempts = 3;

  while (attempts < maxAttempts) {
    try {
      return await operation();
    } catch (e) {
      attempts++;
      if (attempts >= maxAttempts) rethrow;

      await Future.delayed(Duration(seconds: attempts * 2));
    }
  }

  throw StateError('Should not reach here');
}

// Usage
final posts = await withRetry(() => LDK.collection('posts').get());

Issue: LDKServerException: 404 Not Found

Cause: Incorrect API endpoint or collection name.

Diagnosis:

// Check if collection exists in Strapi
Future<void> debugCollections() async {
  try {
    // This should list available content types
    final response = await http.get(
      Uri.parse('${LDK.instance.baseUrl}/api/content-type-builder/content-types'),
      headers: {'Authorization': 'Bearer ${await getAdminToken()}'},
    );

    print('Available collections: ${response.body}');
  } catch (e) {
    print('Failed to fetch collections: $e');
  }
}

Solutions:

// 1. Verify collection name matches Strapi
final posts = LDK.collection('posts'); // Should match your Strapi collection

// 2. Check Strapi permissions
// Ensure the collection has proper read/write permissions for your user role

// 3. Verify API endpoint
// Check that your Strapi server is running and accessible

3. Authentication Issues

Issue: LDKAuthException: Invalid credentials

Cause: Incorrect email/password or user doesn't exist.

Diagnosis:

Future<void> debugAuth(String email, String password) async {
  try {
    // Test with direct HTTP call
    final response = await http.post(
      Uri.parse('${LDK.instance.baseUrl}/api/auth/local'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'identifier': email,
        'password': password,
      }),
    );

    print('Auth response: ${response.statusCode} - ${response.body}');
  } catch (e) {
    print('Auth debug error: $e');
  }
}

Solutions:

// 1. Verify credentials
try {
  await LDK.instance.auth.signIn(
    email: 'user@example.com',
    password: 'correctPassword',
  );
} on LDKAuthException catch (e) {
  switch (e.code) {
    case 'invalid_credentials':
      showError('Invalid email or password');
      break;
    case 'account_locked':
      showError('Account is locked');
      break;
    default:
      showError('Authentication failed: ${e.message}');
  }
}

// 2. Check user exists in Strapi admin panel
// 3. Verify email confirmation if required

Issue: LDKAuthException: Token expired

Cause: JWT token has expired and needs refresh.

Solution:

// Enable automatic token refresh
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableEnhancedAuth: true, // Enables automatic token refresh
);

// Or handle manually
try {
  await LDK.collection('posts').get();
} on LDKAuthException catch (e) {
  if (e.code == 'token_expired') {
    // Redirect to login
    Navigator.pushReplacementNamed(context, '/login');
  }
}

4. Real-time & WebSocket Issues

Issue: WebSocket connection fails

Cause: Server doesn't support WebSockets or network restrictions.

Diagnosis:

Future<void> debugWebSocket() async {
  try {
    final wsUrl = 'ws://localhost:1337/graphql';
    final channel = WebSocketChannel.connect(Uri.parse(wsUrl));

    await channel.ready;
    print('WebSocket connection successful');

    channel.sink.close();
  } catch (e) {
    print('WebSocket connection failed: $e');
  }
}

Solutions:

// 1. Disable real-time features if not supported
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableRealtime: false, // Disable if WebSocket not supported
);

// 2. Use polling as fallback
Stream<List<Post>> watchPosts() {
  try {
    // Try real-time first
    return LDK.collection('posts').subscribeToUpdates().map((event) {
      return [Post.fromJson(event.data)];
    });
  } catch (e) {
    // Fallback to polling
    return Stream.periodic(Duration(seconds: 30)).asyncMap((_) async {
      final response = await LDK.collection('posts').get();
      return response.data.map((json) => Post.fromJson(json)).toList();
    });
  }
}

Issue: Real-time updates not received

Cause: Subscription not properly configured or GraphQL not enabled.

Solution:

// Ensure GraphQL is enabled
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableGraphQL: true,
  enableRealtime: true,
);

// Check subscription setup
class PostService {
  StreamSubscription? _subscription;

  void startWatching() {
    try {
      final collection = LDK.collection('posts') as LDKEnhancedCollection;

      _subscription = collection.subscribeToUpdates().listen(
        (event) {
          print('Received update: ${event.type} - ${event.data}');
        },
        onError: (error) {
          print('Subscription error: $error');
        },
        onDone: () {
          print('Subscription closed');
        },
      );
    } catch (e) {
      print('Failed to start subscription: $e');
    }
  }

  void dispose() {
    _subscription?.cancel();
  }
}

5. Caching Issues

Issue: Stale data from cache

Cause: Cache not invalidating properly or TTL too long.

Solution:

// 1. Force refresh
final posts = await LDK.collection('posts').get(
  useCache: false, // Skip cache
  forceRefresh: true, // Force network request
);

// 2. Clear specific cache
await LDK.instance.cache.invalidateCollection('posts');

// 3. Clear all cache
await LDK.instance.cache.clearAll();

// 4. Adjust cache TTL
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  cacheConfig: {
    'strategies': {
      'posts': {
        'ttl': 60000, // 1 minute instead of default
        'maxSize': 50,
        'evictionPolicy': 'lru',
      },
    },
  },
);

Issue: LDKConfigurationException: Cache not configured

Cause: Trying to use cache when it's disabled.

Solution:

// Enable cache
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableIntelligentCache: true,
  enableOfflineStorage: true, // Required for cache
);

// Or check if available before using
try {
  final cache = LDK.instance.cache;
  await cache.invalidateCollection('posts');
} catch (e) {
  print('Cache not available: $e');
}

6. Offline Storage Issues

Issue: Data not persisting offline

Cause: Offline storage not enabled or permissions issue.

Diagnosis:

Future<void> debugOfflineStorage() async {
  try {
    final offline = LDK.instance.offlineStorage;

    // Check if online
    final isOnline = await offline.isOnline;
    print('Online status: $isOnline');

    // Check pending operations
    final pending = await offline.getPendingOperations();
    print('Pending operations: ${pending.length}');

  } catch (e) {
    print('Offline storage not available: $e');
  }
}

Solution:

// 1. Enable offline storage
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableOfflineStorage: true,
);

// 2. Check storage permissions (Android)
// Add to android/app/src/main/AndroidManifest.xml:
// <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

// 3. Handle offline scenarios
try {
  await LDK.collection('posts').create(postData);
} catch (e) {
  if (e is LDKNetworkException) {
    // Data will be queued for sync when online
    print('Saved offline, will sync when connected');
  }
}

7. Performance Issues

Issue: Slow query performance

Cause: Large datasets, missing indexes, or inefficient queries.

Diagnosis:

// Measure query performance
Future<void> measureQueryPerformance() async {
  final stopwatch = Stopwatch()..start();

  try {
    final response = await LDK.collection('posts').get();
    stopwatch.stop();

    print('Query took: ${stopwatch.elapsedMilliseconds}ms');
    print('Returned ${response.data.length} items');
  } catch (e) {
    print('Query failed: $e');
  }
}

Solutions:

// 1. Use pagination
final query = LDKQueryBuilder()
  .limit(20)
  .offset(0)
  .sort(['-createdAt']);

final response = await LDK.collection('posts').get(query: query);

// 2. Optimize queries with specific fields
final query = LDKQueryBuilder()
  .select(['id', 'title', 'createdAt']) // Only fetch needed fields
  .populate(['author']) // Only populate necessary relations
  .limit(20);

// 3. Use caching for frequently accessed data
final response = await LDK.collection('posts').get(
  query: query,
  useCache: true,
);

// 4. Implement virtual scrolling for large lists
class VirtualScrollList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 1000000, // Large dataset
      itemBuilder: (context, index) {
        // Only build visible items
        return FutureBuilder(
          future: loadItem(index),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListTile(title: Text(snapshot.data.toString()));
            }
            return ListTile(title: Text('Loading...'));
          },
        );
      },
    );
  }
}

Issue: Memory leaks

Cause: Not disposing streams or subscriptions.

Diagnosis:

// Use Flutter Inspector to check widget tree
// Look for growing number of listeners

// Monitor memory usage
import 'dart:developer' as developer;

void logMemoryUsage() {
  developer.log('Memory usage: ${ProcessInfo.currentRss ~/ 1024 ~/ 1024} MB');
}

Solution:

class PostListWidget extends StatefulWidget {
  @override
  _PostListWidgetState createState() => _PostListWidgetState();
}

class _PostListWidgetState extends State<PostListWidget> {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();

    // Subscribe to updates
    _subscription = LDK.collection('posts').subscribeToUpdates().listen((event) {
      // Handle updates
    });
  }

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

Debugging Tools

1. Enable Debug Logging

await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableLogging: true, // Enable HTTP request/response logging
);

2. Network Inspector

Use Flutter's network inspector or add custom logging:

class DebugHttpClient extends LDKHttpClient {
  @override
  Future<LDKResponse<T>> get<T>(String path, {Map<String, String>? queryParameters}) async {
    print('GET $path with params: $queryParameters');

    final stopwatch = Stopwatch()..start();
    try {
      final response = await super.get<T>(path, queryParameters: queryParameters);
      stopwatch.stop();

      print('GET $path completed in ${stopwatch.elapsedMilliseconds}ms');
      return response;
    } catch (e) {
      stopwatch.stop();
      print('GET $path failed after ${stopwatch.elapsedMilliseconds}ms: $e');
      rethrow;
    }
  }
}

3. Cache Inspector

class CacheDebugger {
  static Future<void> printCacheStats() async {
    try {
      final cache = LDK.instance.cache;

      // Get cache statistics
      cache.cacheStatsStream.listen((stats) {
        print('Cache Stats:');
        print('  Hit Rate: ${stats.hitRate.toStringAsFixed(2)}%');
        print('  Total Requests: ${stats.totalRequests}');
        print('  Cache Hits: ${stats.hits}');
        print('  Cache Misses: ${stats.misses}');
        print('  Cached Items: ${stats.itemCount}');
      });

    } catch (e) {
      print('Cache not available: $e');
    }
  }

  static Future<void> printCacheContents() async {
    try {
      final cache = LDK.instance.cache;

      // This would require additional methods in the cache implementation
      // final contents = await cache.getAllKeys();
      // print('Cached keys: $contents');

    } catch (e) {
      print('Failed to get cache contents: $e');
    }
  }
}

4. Connection Monitor

class ConnectionMonitor {
  static void startMonitoring() {
    // Monitor network connectivity
    Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
      print('Connectivity changed: $result');

      if (result == ConnectivityResult.none) {
        print('Device is offline');
      } else {
        print('Device is online');
      }
    });

    // Monitor LDK offline status
    try {
      LDK.instance.offlineStorage.isOnlineStream.listen((isOnline) {
        print('LDK online status: $isOnline');
      });
    } catch (e) {
      print('Offline storage not available');
    }
  }
}

Getting Help

1. Check Documentation

2. Enable Verbose Logging

// Enable all logging
await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableLogging: true,
);

// Add custom logging
import 'dart:developer' as developer;

void logDebug(String message) {
  developer.log(message, name: 'LDK_DEBUG');
}

3. Create Minimal Reproduction

When reporting issues, create a minimal example:

import 'package:flutter/material.dart';
import 'package:ldk/ldk.dart';

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

  try {
    await LDK.initialize(
      baseUrl: 'https://your-server.com',
      enableLogging: true,
    );

    // Reproduce the issue here
    final posts = await LDK.collection('posts').get();
    print('Success: ${posts.data.length} posts loaded');

  } catch (e) {
    print('Error: $e');
  }
}

4. Community Support

5. Provide Debug Information

When asking for help, include:

void printDebugInfo() {
  print('LDK Debug Information:');
  print('  Initialized: ${LDK.instance.isInitialized}');
  print('  Flutter Version: ${Platform.version}');
  print('  Platform: ${Platform.operatingSystem}');

  try {
    print('  GraphQL Available: ${LDK.instance.graphql != null}');
  } catch (e) {
    print('  GraphQL Available: false');
  }

  try {
    print('  Cache Available: ${LDK.instance.cache != null}');
  } catch (e) {
    print('  Cache Available: false');
  }

  try {
    print('  Realtime Available: ${LDK.instance.realtime != null}');
  } catch (e) {
    print('  Realtime Available: false');
  }
}

Prevention Tips

1. Always Handle Errors

// ❌ Don't ignore errors
final posts = await LDK.collection('posts').get();

// ✅ Always handle errors
try {
  final posts = await LDK.collection('posts').get();
  // Use posts
} catch (e) {
  // Handle error appropriately
  print('Failed to load posts: $e');
}

2. Dispose Resources

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? _subscription;

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

3. Test Edge Cases

// Test with empty data
// Test with network failures
// Test with invalid tokens
// Test offline scenarios

4. Use Type Safety

// ✅ Use proper types
class Post {
  final String id;
  final String title;

  Post({required this.id, required this.title});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id']?.toString() ?? '',
      title: json['attributes']?['title'] ?? '',
    );
  }
}

By following this troubleshooting guide, you should be able to diagnose and resolve most issues with LDK. Remember to always check the logs, handle errors gracefully, and dispose of resources properly.


Next: Performance Optimization →