Ontwikkel een Flutter-app met geavanceerde integratie van externe API's en netwerkbeheer
description
Met deze prompt kan de gebruiker leren hoe hij meerdere externe API's effectief kan integreren in Flutter-apps, met een focus op robuust netwerkbeheer en foutafhandeling. Dit verbetert de betrouwbaarheid en prestaties van de app aanzienlijk, biedt een betere gebruikerservaring en onderscheidt zich van basisintegraties door geavanceerde caching en foutafhandelingsstrategieën.
prompt
Help me een Flutter-app te ontwikkelen die efficiënt meerdere externe API's integreert en robuust netwerkbeheer gebruikt. Ik wil ondersteuning voor functies zoals <beschrijf de gewenste API-functionaliteiten> en een betrouw ...
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een uitgebreide gids voor het ontwikkelen van een Flutter-app die meerdere externe API's efficiënt integreert met robuust netwerkbeheer, inclusief voorbeelden en best practices.
---
### 1. Structuur van API-aanroepen
Gebruik een aparte serviceklasse voor API-interacties, bij voorkeur met het `http`-pakket of een geavanceerdere oplossing zoals `dio`. Hiermee scheid je netwerklogica van de UI.
**Voorbeeld: API Service met `dio`**
```dart
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/',
connectTimeout: 5000,
receiveTimeout: 3000,
)) {
// Optioneel: interceptors voor logging en foutafhandeling
_dio.interceptors.add(LogInterceptor(responseBody: true));
}
Future<Map<String, dynamic>> fetchProductCatalog() async {
try {
final response = await _dio.get('/products');
return response.data;
} on DioError catch (e) {
throw _handleDioError(e);
}
}
Future<Map<String, dynamic>> placeOrder(Map<String, dynamic> orderData) async {
try {
final response = await _dio.post('/orders', data: orderData);
return response.data;
} on DioError catch (e) {
throw _handleDioError(e);
}
}
// Foutafhandelingsfunctie
Exception _handleDioError(DioError error) {
if (error.type == DioErrorType.connectTimeout ||
error.type == DioErrorType.receiveTimeout) {
return Exception('Netwerk timeout. Controleer je verbinding.');
} else if (error.response != null) {
return Exception('Server fout: ${error.response?.statusCode}');
} else {
return Exception('Onbekende netwerkfout.');
}
}
}
```
---
### 2. Caching implementeren
Voor betere prestaties en gebruikerservaring kun je caching toepassen. Een eenvoudige aanpak is om data lokaal op te slaan met bijvoorbeeld `shared_preferences` of `hive`. Voor meer geavanceerde caching kun je een dedicated cache-bibliotheek gebruiken.
**Voorbeeld: Basis caching met `shared_preferences`**
```dart
import 'package:shared_preferences/shared_preferences.dart';
class CacheService {
Future<void> saveProductCatalog(List<dynamic> products) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('productCatalog', jsonEncode(products));
}
Future<List<dynamic>?> getProductCatalog() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('productCatalog');
if (jsonString != null) {
return jsonDecode(jsonString);
}
return null;
}
}
```
**Gebruik in API Service:**
```dart
import 'dart:convert';
class ProductRepository {
final ApiService apiService;
final CacheService cacheService;
ProductRepository({required this.apiService, required this.cacheService});
Future<List<dynamic>> getProductCatalog() async {
// Probeer eerst cache
final cachedData = await cacheService.getProductCatalog();
if (cachedData != null) {
return cachedData;
}
// Anders API-oproep
final data = await apiService.fetchProductCatalog();
await cacheService.saveProductCatalog(data['products']);
return data['products'];
}
}
```
---
### 3. Netwerkfouten afhandelen
Gebruik try-catch blokken en geef gebruikers duidelijke feedback. Je kunt ook een globale foutafhandelingsstrategie implementeren.
**Voorbeeld: Netwerkfout tonen in UI**
```dart
class ProductPage extends StatefulWidget {
@override
_ProductPageState createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
late ProductRepository _repository;
List<dynamic>? _products;
String? _errorMessage;
@override
void initState() {
super.initState();
_repository = ProductRepository(
apiService: ApiService(),
cacheService: CacheService(),
);
_loadProducts();
}
void _loadProducts() async {
try {
final products = await _repository.getProductCatalog();
setState(() {
_products = products;
_errorMessage = null;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
if (_errorMessage != null) {
return Center(child: Text('Fout: $_errorMessage'));
}
if (_products == null) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: _products!.length,
itemBuilder: (context, index) {
final product = _products![index];
return ListTile(
title: Text(product['name']),
);
},
);
}
}
```
---
### 4. Best practices
- **Scheiding van concerns:** Houd API-logica gescheiden van UI.
- **Asynchrone verwerking:** Gebruik `async/await` en zorg voor loading indicators.
- **Foutafhandeling:** Geef gebruikers duidelijke en vriendelijke foutmeldingen.
- **Netwerkbeheer:** Gebruik interceptors voor logging en het detecteren van offline status.
- **Caching:** Cache belangrijke data en ververs deze op de achtergrond indien nodig.
- **Authenticatie:** Behandel tokens veilig, bijvoorbeeld met `flutter_secure_storage`.
- **Gebruik Provider, Riverpod of Bloc:** Voor state management en het beheer van API-data.
---
Met deze aanpak zorg je voor een efficiënte, robuuste en gebruikersvriendelijke Flutter-app die meerdere API's integreert en goed omgaat met netwerkproblemen.
provider: deepseek
model: deepseek-chat
Zeker! Hier een uitgebreide uitleg voor het ontwikkelen van een robuuste Flutter-app met meerdere API-integraties:
## 1. Projectstructuur en Dependencies
Voeg eerst de benodigde dependencies toe aan `pubspec.yaml`:
```yaml
dependencies:
flutter:
sdk: flutter
http: ^1.1.0
dio: ^5.0.0
shared_preferences: ^2.2.2
connectivity_plus: ^5.0.1
cached_network_image: ^3.3.0
dev_dependencies:
build_runner: ^2.4.0
retrofit: ^4.0.0
json_annotation: ^4.8.1
```
## 2. Netwerklaag met Dio en Retrofit
### API Client Setup
```dart
// lib/core/network/api_client.dart
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:json_annotation/json_annotation.dart';
part 'api_client.g.dart';
@RestApi(baseUrl: "https://api.jouw-domein.com/v1/")
abstract class ApiClient {
factory ApiClient(Dio dio, {String baseUrl}) = _ApiClient;
@POST("/auth/login")
Future<LoginResponse> login(@Body() LoginRequest request);
@GET("/products")
Future<ProductListResponse> getProducts(
@Query("page") int page,
@Query("limit") int limit,
);
@POST("/orders")
Future<OrderResponse> placeOrder(@Body() OrderRequest request);
@GET("/orders/{id}")
Future<OrderDetails> getOrder(@Path("id") String orderId);
}
```
### Dio Configuration
```dart
// lib/core/network/dio_provider.dart
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DioProvider {
static Dio createDio() {
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
));
// Interceptors voor logging en auth
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
dio.interceptors.add(AuthInterceptor());
dio.interceptors.add(RetryInterceptor());
return dio;
}
}
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
// Token is verlopen, redirect naar login
// Eventueel refresh token logica hier
}
handler.next(err);
}
}
```
## 3. Repository Pattern voor API Management
```dart
// lib/repositories/auth_repository.dart
class AuthRepository {
final ApiClient _apiClient;
final SharedPreferences _prefs;
AuthRepository(this._apiClient, this._prefs);
Future<Result<User>> login(String email, String password) async {
try {
final response = await _apiClient.login(
LoginRequest(email: email, password: password),
);
await _prefs.setString('auth_token', response.token);
await _prefs.setString('user_id', response.user.id);
return Result.success(response.user);
} on DioException catch (e) {
return Result.failure(_handleDioError(e));
} catch (e) {
return Result.failure(AppError.unknown());
}
}
AppError _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return AppError.network();
case DioExceptionType.badResponse:
final statusCode = error.response?.statusCode;
if (statusCode == 401) return AppError.unauthorized();
if (statusCode == 404) return AppError.notFound();
return AppError.server();
default:
return AppError.unknown();
}
}
}
```
## 4. Caching Implementatie
### Memory Cache
```dart
// lib/core/cache/memory_cache.dart
class MemoryCache {
final _cache = <String, CacheItem>{};
void set(String key, dynamic data, {Duration? ttl}) {
_cache[key] = CacheItem(
data: data,
expiry: ttl != null ? DateTime.now().add(ttl) : null,
);
}
dynamic get(String key) {
final item = _cache[key];
if (item == null) return null;
if (item.expiry != null && item.expiry!.isBefore(DateTime.now())) {
_cache.remove(key);
return null;
}
return item.data;
}
void remove(String key) => _cache.remove(key);
void clear() => _cache.clear();
}
class CacheItem {
final dynamic data;
final DateTime? expiry;
CacheItem({required this.data, this.expiry});
}
```
### Cached Repository
```dart
// lib/repositories/product_repository.dart
class ProductRepository {
final ApiClient _apiClient;
final MemoryCache _cache;
ProductRepository(this._apiClient, this._cache);
Future<Result<List<Product>>> getProducts({int page = 1, bool forceRefresh = false}) async {
final cacheKey = 'products_page_$page';
// Return cached data if available and not forcing refresh
if (!forceRefresh) {
final cached = _cache.get(cacheKey);
if (cached != null) {
return Result.success(cached);
}
}
try {
final response = await _apiClient.getProducts(page, 20);
final products = response.products;
// Cache voor 5 minuten
_cache.set(cacheKey, products, ttl: const Duration(minutes: 5));
return Result.success(products);
} on DioException catch (e) {
// Fallback naar cache bij netwerkfout
final cached = _cache.get(cacheKey);
if (cached != null) {
return Result.success(cached);
}
return Result.failure(_handleDioError(e));
}
}
}
```
## 5. Netwerk Status Monitoring
```dart
// lib/core/network/connectivity_service.dart
import 'package:connectivity_plus/connectivity_plus.dart';
class ConnectivityService {
final Connectivity _connectivity = Connectivity();
Stream<ConnectivityResult> get connectivityStream =>
_connectivity.onConnectivityChanged;
Future<ConnectivityResult> checkConnectivity() async {
return await _connectivity.checkConnectivity();
}
Future<bool> hasInternetConnection() async {
final result = await checkConnectivity();
return result != ConnectivityResult.none;
}
}
```
## 6. State Management met Cubit/Bloc
```dart
// lib/features/products/cubit/products_cubit.dart
class ProductsCubit extends Cubit<ProductsState> {
final ProductRepository _productRepository;
final ConnectivityService _connectivityService;
ProductsCubit(this._productRepository, this._connectivityService)
: super(ProductsInitial());
Future<void> loadProducts({bool forceRefresh = false}) async {
emit(ProductsLoading());
// Check connectivity
final hasConnection = await _connectivityService.hasInternetConnection();
if (!hasConnection && forceRefresh) {
emit(ProductsError(AppError.network()));
return;
}
final result = await _productRepository.getProducts(
forceRefresh: forceRefresh,
);
result.when(
success: (products) => emit(ProductsLoaded(products)),
failure: (error) => emit(ProductsError(error)),
);
}
}
```
## 7. Gebruikersinterface met Foutafhandeling
```dart
// lib/features/products/widgets/product_list.dart
class ProductList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocConsumer<ProductsCubit, ProductsState>(
listener: (context, state) {
if (state is ProductsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_getErrorMessage(state.error)),
action: SnackBarAction(
label: 'Opnieuw proberen',
onPressed: () => context.read<ProductsCubit>().loadProducts(),
),
),
);
}
},
builder: (context, state) {
if (state is ProductsLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state is ProductsError) {
return ErrorRetryWidget(
error: state.error,
onRetry: () => context.read<ProductsCubit>().loadProducts(),
);
}
if (state is ProductsLoaded) {
return RefreshIndicator(
onRefresh: () async {
await context.read<ProductsCubit>().loadProducts(forceRefresh: true);
},
child: ListView.builder(
itemCount: state.products.length,
itemBuilder: (context, index) => ProductItem(state.products[index]),
),
);
}
return const SizedBox.shrink();
},
);
}
String _getErrorMessage(AppError error) {
switch (error.type) {
case AppErrorType.network:
return 'Geen internetverbinding. Controleer uw netwerk.';
case AppErrorType.unauthorized:
return 'Sessie verlopen. Log opnieuw in.';
case AppErrorType.server:
return 'Serverfout. Probeer het later opnieuw.';
default:
return 'Er is een fout opgetreden. Probeer het opnieuw.';
}
}
}
```
## 8. Best Practices
### A. Dependency Injection
```dart
// lib/core/di/service_locator.dart
final getIt = GetIt.instance;
void setupDependencies() {
getIt.registerSingleton<Dio>(DioProvider.createDio());
getIt.registerSingleton<ApiClient>(ApiClient(getIt<Dio>()));
getIt.registerSingleton<MemoryCache>(MemoryCache());
getIt.registerSingleton<ConnectivityService>(ConnectivityService());
getIt.registerFactory<AuthRepository>(
() => AuthRepository(getIt<ApiClient>(), SharedPreferences.getInstance()),
);
}
```
### B. Environment Configuration
```dart
// lib/core/config/environment.dart
abstract class Environment {
static const String baseUrl = String.fromEnvironment(
'BASE_URL',
defaultValue: 'https://api.staging.jouwdomein.com/v1/',
);
}
```
### C. Model Classes
```dart
// lib/features/auth/models/user.dart
@JsonSerializable()
class User {
final String id;
final String email;
final String name;
User({required this.id, required this.email, required this.name});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
```
## 9. Build Runner Commando
Voer dit uit om de retrofit code te genereren:
```bash
flutter pub run build_runner build
```
Deze architectuur biedt:
- **Schone scheiding** van concerns
- **Robuuste foutafhandeling** met fallback naar cache
- **Efficiënte caching** strategie
- **Netwerk status monitoring**
- **Schaalbaar** voor meerdere API's
- **Testbaar** door dependency injection
Succes met je Flutter app ontwikkeling!
error: Generation failed [deepseek] prompt_id=4907 locale=en err=DeepSeek connection/timeout | Generation failed [deepseek] prompt_id=4907 locale=de err=DeepSeek connection/timeout