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¶
- API Reference - Complete API documentation
- Feature Guides - Detailed feature explanations
- Examples - Working code examples
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¶
- GitHub Issues: Report bugs
- Discussions: Ask questions
- Stack Overflow: Tag questions with
ldk-flutter
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.