معماری تمیز در فلاتر: ساختار پوشهها و لایهبندی برای پروژههای بزرگ
وقتی اپ بزرگ میشود، بدون ساختار مشخص کد به هم میریزد. این مقاله ساختار feature-first، لایههای UI، state، repository و data source و اصول تستپذیری را به زبان ساده توضیح میدهد.
هر اپ واقعی با یک API حرف میزند. این راهنما نشان میدهد چطور با Dio درخواست بزنید، پاسخها را به مدل Dart تبدیل کنید، خطاها را درست مدیریت کنید و کد شبکه را در یک لایهی تمیز نگه دارید.

تقریباً هر اپ واقعی با یک سرور حرف میزند. فلاتر با پکیج http هم میتواند درخواست بزند، ولی Dio ابزارهای بیشتری مثل interceptor، لغو درخواست و آپلود با نمایش پیشرفت دارد و در پروژههای جدی بیشتر استفاده میشود.
یک نمونهی مشترک از Dio بسازید و تنظیمات پایه را یکبار بدهید:
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/api/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 20),
headers: {'Accept': 'application/json'},
));پاسخ JSON را به یک کلاس Dart تبدیل کنید تا بقیهی کد با نوعهای مشخص کار کند، نه با Map خام:
class Course {
final String id;
final String name;
final int price;
const Course({required this.id, required this.name, required this.price});
factory Course.fromJson(Map<String, dynamic> json) => Course(
id: json['id'] as String,
name: json['name'] as String,
price: json['price'] as int,
);
}ویجتها نباید مستقیم Dio صدا بزنند. یک کلاس واسط بسازید:
class CourseRepository {
CourseRepository(this._dio);
final Dio _dio;
Future<List<Course>> fetchCourses() async {
final response = await _dio.get('/courses');
return (response.data as List)
.map((e) => Course.fromJson(e as Map<String, dynamic>))
.toList();
}
}اگر روزی آدرس یا فرمت API عوض شد، فقط همین کلاس تغییر میکند.
خطای شبکه چند نوع دارد: قطع اینترنت، timeout، و پاسخ خطا از سرور. همه را به یک پیام قابلفهم تبدیل کنید:
String friendlyMessage(Object error) {
if (error is DioException) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
return 'پاسخی از سرور نیامد. دوباره تلاش کنید.';
case DioExceptionType.connectionError:
return 'اتصال اینترنت را بررسی کنید.';
default:
final data = error.response?.data;
if (data is Map && data['message'] is String) return data['message'];
}
}
return 'خطای نامشخص رخ داد.';
}هر کاری که باید روی همهی درخواستها انجام شود (مثل افزودن هدر احراز هویت یا ثبت لاگ) را در یک interceptor بگذارید:
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = tokenStore.read();
if (token != null) options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
},
));مقادیر حساس مثل توکن را در flutter_secure_storage نگه دارید، نه در SharedPreferences.
CancelToken لغو کنید.با این ساختار، تغییر سرور یا افزودن یک endpoint جدید فقط یک فایل را درگیر میکند.
دورهی VIP فست فلاتر از صفر تا انتشار اپلیکیشن، با پشتیبانی مستقیم مدرس.