TDD (Test-Driven Development) — bu Testga asoslangan dasturlash metodologiyasidir, unda dasturchilar kod yozishni boshlashdan oldin testlarni yaratadilar. Bu metodologiya kodning sifatini oshirish, xatoliklarni erta aniqlash va kodni soddalashtirishga yordam beradi.
TDD dastlab xunit deb atalgan test framework-lari yordamida 1990-yillarda Kent Beck tomonidan ishlab chiqilgan. Kent Beck, shuningdek, Extreme Programming (XP) metodologiyasining asoschilaridan biri hisoblanadi. TDD metodologiyasi XP ning asosiy prinsiplaridan biri sifatida paydo bo‘ldi.
- Test yozish: Kodni yozishdan avval, bajarilishi kerak bo'lgan funksiyalarni sinovdan o‘tkazadigan testlar yoziladi.
- Kod yozish: Testdan muvaffaqiyatli o‘tishi uchun kerakli kod yoziladi.
- Testni bajarish: Yozilgan kod testni muvaffaqiyatli o'tkazishi kerak.
- Refaktoring: Kodingizni tozalash va optimallashtirish uchun refaktoring qilinadi, lekin testlar har doim o'tkaziladi.
TDD dasturchilarga yuqori sifatli, o‘qilishi oson va xatoliklardan xoli kod yozish imkoniyatini beradi. TDD yana bir ustunligi ko'dlarni pattrinlarga bo'linishi bu ko'dni o'shni osonlashtiradi va sizdan kegingi dasturchi yoki jamoa bilan ishlagada hechqanday qiyinchliklarsiz loyhani davom etiradi
Struktura shu ko'rinishda bo'ladi hohishga ko'ra boshaqa papkalar ochish ham mumkin muhumi ko'd strukturasi buzulmasligi lozim;
Yuqaridagi rasmda ko'rishingiz mumkin arxitekturada 3 ta qatlam mavjud:
**Data**,**Domain** va **Presentation**. Har birining o'z maqsadi bor va faqat yuqoridagi
oqimga
ko'ra o'zaro
aloqada bo'lishlari mumkin;
**Data** va **Presentation** faqat Domain yordamida bir-biri bilan aloqa qilishi mumkin
Bu loiyhada-api bilan ishlanmagani uchun bizga **Remote Data Sources** mavjud emas
bizga **Local Data Sources**ni o'zi yetarli bo'ladi.
bizda Local Data Sources bilan ishlash uchun ushbu abstract class AlbumsLocalDataSource
mavjud 👇🏻
- Albomlar va albom ichidagi fayllarni boshqaradi.
- Foydalanuvchi ruxsatlarini tekshiradi va media fayllarga kirishni ta'minlaydi.
import 'package:photo_manager/photo_manager.dart';
/// Rasmlar va albomlarni olish uchun abstrakt class.
/// Bu interfeysni implementatsiya qiluvchi classlar quyidagi funksiyalarni bajarishi kerak:
/// 1. `loadAlbums`: Qurilmadagi mavjud albomlarni yuklash.
/// 2. `loadAlbumsItem`: Berilgan albomning ichidagi media fayllarni yuklash.
abstract interface class AlbumsLocalDataSource {
/// Qurilmadagi barcha albomlarni yuklaydi.
/// [Future] qaytaradi, u [List<AssetPathEntity>] ichida albomlar ro'yxatini saqlaydi.
Future<List<AssetPathEntity>> loadAlbums();
/// Berilgan albomga tegishli media fayllarni yuklaydi.
/// [Future] qaytaradi, u [List<AssetEntity>] ichida fayllar ro'yxatini saqlaydi.
Future<List<AssetEntity>> loadAlbumsItem(AssetPathEntity selectedAlbum);
}Abstract class yozishdan maqsad:
-
Modullarni ajratish va yanada aniq struktura yaratish:
AlbumsLocalDataSource— bu interfeys bo'lib, faqatgina funksiyalarni e'lon qiladi. Bu esa implementatsiyani (asosiy kodni) boshqacha yozishga imkon beradi.- Sizning implementatsiyangiz (
AlbumsLocalDataSourceImpl) esa interfeysdagi funksiyalarni haqiqiy hayotda qanday ishlashini ko'rsatadi.
-
Moslashuvchanlik (Flexibility):
- Agar boshqa turdagi media manager kutubxonasiga (masalan, boshqa kutubxona yoki backend) o'tmoqchi bo'lsangiz, faqat yangi implementatsiya yozasiz.
- Interfeys orqali boshqa qismdagi kodga ta'sir qilmasdan, yangi implementatsiya ishlatiladi.
-
Kodni o'qilishi va testlanishini osonlashtirish:
- Abstract class bilan ishlaganda, ilova logikasi toza va tushunarli bo'ladi. Har bir sinf o'z vazifasini bajaradi va bir joyga haddan tashqari ko'p kod yozilmaydi.
-
Jamoaviy ishlashda yordam:
- Jamoangizdagi boshqa dasturchilar sizning abstract classingizdan foydalanib, uning ustiga boshqa implementatsiyalar yozishi yoki turli modullarga ulanishi mumkin.
Endi bu abstract class AlbumsLocalDataSource dan meros olamiz
// Asosiy implementatsiya class, bu yerda lokal media ma'lumotlarini olish logikasi yozilgan.
class AlbumsLocalDataSourceImpl implements AlbumsLocalDataSource {
/// Qurilmadagi mavjud albomlarni yuklaydi.
/// Agar foydalanuvchi media fayllarga ruxsat bergan bo'lsa, albomlar ro'yxatini qaytaradi.
/// Aks holda, foydalanuvchini sozlamalar sahifasiga yo'naltiradi.
@override
Future<List<AssetPathEntity>> loadAlbums() async {
// Media fayilariga kirish uchin ruxsat so'raymiz.
var permission = await PhotoManager.requestPermissionExtend();
// Albomlar ro'yxatini saqlash uchun bo'sh ro'yxat yaratamiz.
List<AssetPathEntity> albumList = [];
if (permission.isAuth) {
// Agar ruxsat berilgan bo'lsa, albomlarni yuklaymiz.
albumList = await PhotoManager.getAssetPathList(
type: RequestType.common, // Rasmlar va videolarni yuklaydi.
);
} else {
// Agar ruxsat berilmagan bo'lsa, foydalanuvchini sozlamalar oynasiga "Permission setting" o'tkazamiz.
PhotoManager.openSetting();
}
// Albomlar ro'yxatini qaytaramiz.
return albumList;
}
/// Berilgan albomning ichidagi media fayllarni yuklaydi.
/// [selectedAlbum] - bu albom linki yani albom joylashgan joyi.
/// Albom ichidagi barcha fayllarni qaytaradi.
@override
Future<List<AssetEntity>> loadAlbumsItem(AssetPathEntity selectedAlbum) async {
// Albom ichidagi media fayllar sonini aniqlaymiz.
int assetCount = await selectedAlbum.assetCountAsync;
// Media fayllarni yuklaymiz (0-dan boshlab to'liq ro'yxatni).
List<AssetEntity> assetList = await selectedAlbum.getAssetListRange(
start: 0, // Boshlanish indeksi.
end: assetCount, // Albomdagi fayllar soni.
);
// Media fayllar ro'yxatini qaytaramiz.
return assetList;
}
}-
loadAlbums():- Media fayllarga ruxsat so'raydi (
PhotoManager.requestPermissionExtend). - Ruxsat berilgan taqdirda qurilmadagi barcha albomlarni
qaytaradi (
PhotoManager.getAssetPathListyordamida). - Agar foydalanuvchi ruxsat bermasa,
PhotoManager.openSettingorqali ruxsat olish oynasini ochadi.
- Media fayllarga ruxsat so'raydi (
-
loadAlbumsItem(AssetPathEntity selectedAlbum):- Berilgan albomning ichidagi barcha fayllarni yuklaydi (
selectedAlbum.getAssetListRangeyordamida). - Bu funksiya faqatgina tanlangan albom ichidagi fayllarni olib keladi.
- Berilgan albomning ichidagi barcha fayllarni yuklaydi (
Repository - bu dastur arxitekturasida ma'lumotlar qatlamini boshqaruvchi va ma'lumot manbalari (masalan, lokal ma'lumotlar bazasi yoki API) bilan UI o'rtasida ko'prik bo'lib ishlovchi qatlamdir. Repository, ma'lumotlar manbalarini abstraksiyalash orqali kodni modulli va oson boshqariladigan qiladi.
-
Kodning o'qiluvchanligini oshirish:
- Ma'lumotlar olish logikasi bir joyda markazlashtiriladi.
- UI qatlami faqat repository orqali ishlaydi, bu esa kodni osonroq boshqarishga imkon beradi.
-
Testlashni osonlashtirish:
- Repository orqali ma'lumotlarni osonlikcha sinab ko'rish va ularni soxta manbalar bilan almashtirish mumkin.
- Bu Unit Test va Integration Testlarni bajarishni yengillashtiradi.
- Abstraktsiya yaratish:
-
AlbumsRepositoryinterfeysini yaratamiz. U qanday funksiyalar kerakligini belgilaydi, masalan:abstract interface class AlbumsRepository { Future<Either<Failure, List<AssetPathEntity>>> loadAlbums(); Future<Either<Failure, List<AssetEntity>>> loadAlbumsItem(AssetPathEntity entity); }
- Bu UI qatlamiga faqat ma'lumot olish uchun qanday interfeyslar mavjudligini bildiradi.
-
Implementatsiya:
AlbumsRepositoryImplorqali bu interfeysni amalga oshirasiz. Bu joyda lokal va tarmoq manbalaridan ma'lumot olish logikasi yoziladi:
class AlbumsRepositoryImpl implements AlbumsRepository { // AlbumsLocalDataSource konstruktor yordamida chaqiramiz final AlbumsLocalDataSource mediaLocalDataSource; // konstruktor AlbumsRepositoryImpl(this.mediaLocalDataSource); @override Future<Either<Failure, List<AssetPathEntity>>> loadAlbums() async { try { final result = await mediaLocalDataSource.loadAlbums();//mediaLocalDataSource chaqiramiz va kelgan ma'lumotni resultga tenglaymiz // agar result null bo'lmasa yani bo'sh bo'lsa , biron qiymatga ega bo'lsa shart bajariladi if (result.isNotEmpty) { // dastur bu qisimga kirsa demak ui kegini etabga malumot success holatda kelganini bildirish uchun // Rigth(result) qilib qaytaramiz return Right(result); } else { // Aksholda ma'lumot error bo'lsa yoki hechqanday ma'lumot bo'lmasa // Left() deb error holatni bildiramiz return Left(Failure(message: "Image Null")); } } catch (e) { return Left(Failure(message: e.toString())); // ko'zda tutilmagan hatoliklar uchun } } // bu finkskiyadaham yuqoridagi funksiya kabi bo'lmoqda @override Future<Either<Failure, List<AssetEntity>>> loadAlbumsItem(AssetPathEntity entity) async{ try { final result = await mediaLocalDataSource.loadAlbumsItem(entity); if (result.isNotEmpty) { return Right(result); } else { return Left(Failure(message: "Album Null")); } } catch (e) { return Left(Failure(message: e.toString())); } } }
Use Case (Ish holati) bu Clean Architecture'da asosiy qatlamlardan biri bo'lib, logik mantiqni (business logic) ajratib olish uchun ishlatiladi. Use Case domain layerda joylashadi.
abstract class UseCase<Type, Params> {
Future<Either<Failure, Type>> call(Params params);
}UseCaseabstrakt sinfi umumiyUseCasekonseptini ifodalaydi, u ma'lum bir Type ( masalan,List<AssetEntity>) va Params (masalan,NoParams) bilan ishlaydi.callmetodi,Future<Either<Failure, Type>>turini qaytaradi, bu metodni asosan asinxron ravishda chaqirish kerak.Either<Failure, Type>:Eithertipi muvaffaqiyat (Right) yoki xatolik (Left) natijasini qaytaradi.Failure- bu xatoliklarni ifodalovchi ob'ekt,Typeesa muvaffaqiyatli natija turini bildiradi.
Params- bu parametrlar, odatda,UseCaseishga tushirilganda kerakli parametrlar bo'ladi. Agar parametrlar kerak bo'lmasa,NoParamsishlatiladi.
class MediaAssetsUseCase implements UseCase<List<AssetEntity>, NoParams> {
final MediaRepositoryImpl repositoryImpl;
MediaAssetsUseCase(this.repositoryImpl);
@override
Future<Either<Failure, List<AssetEntity>>> call(NoParams params) {
return repositoryImpl.loadAssets();
}
}MediaAssetsUseCase-UseCasesinfini amalga oshirgan klass. Bu klassList<AssetEntity>vaNoParamsparametrlarini ishlatadi. Bu shuni anglatadiki, buUseCaserasmlar yoki media aktivlarini olish uchun ishlatiladi.repositoryImpl- buMediaRepositoryImplob'ekti bo'lib, ma'lumotlarni olish logikasini amalga oshiradi.repositoryImplorqali media resurslarini (masalan, rasmlar) olish amalga oshiriladi.callmetodiNoParamsparametrlarini qabul qiladi varepositoryImpl.loadAssets()metodini chaqiradi. Bu metod, assets yoki media fayllarini olish uchunMediaRepositoryImpltomonidan ta'minlangan implementatsiyani ishlatadi.repositoryImpl.loadAssets()ma'lumotlarni olish jarayonini boshqaradi va *Either<Failure, List<AssetEntity>>* qaytaradi, ya'ni muvaffaqiyatli yoki xato holatlarni bildiruvchi natijani qaytaradi.
Kodda NoParams sinfi ishlatilgan, lekin u alohida ko'rsatilmagan. Agar u mavjud bo'lsa, bu
sinf parametrlar bo'lmagan UseCase uchun ishlatiladi.
Misol:
class NoParams {}NoParams sinfi UseCase uchun parametrlar bo'lmagan holatni ifodalaydi. Bu, ko'pincha,
faqat ma'lum bir resursni olish (masalan, barcha albomlar yoki media fayllar) uchun ishlatiladi.
Bu konsept dartz kutubxonasidan olingan. Either turidan foydalanish, muvaffaqiyat va
xatolikni ajratib ko'rsatish uchun juda qulay.
Left- xato holatni ifodalaydi va odatdaFailurebilan bog'liq bo'ladi.Right- muvaffaqiyat holatni ifodalaydi va kerakli ma'lumotni (bu yerdaList<AssetEntity>) qaytaradi.
Shu tariqa, Either<Failure, Type> yordamida quyidagi holatlarni aniq ko'rsatish mumkin:
-
Right- muvaffaqiyatli holat:- Misol: agar media fayllari muvaffaqiyatli olingan bo'lsa,
Right(result)qaytariladi.
- Misol: agar media fayllari muvaffaqiyatli olingan bo'lsa,
-
Left- xatolik holati:- Misol: agar biron bir xato yuz bersa, masalan, ruxsat yo'q bo'
lsa,
Left(Failure(message: "Error message"))qaytariladi.
- Misol: agar biron bir xato yuz bersa, masalan, ruxsat yo'q bo'
lsa,
- Ma'lumot olish yoki o'zgartirish jarayonidagi logik qoidalarni bajaradi.
- Repositorydan ma'lumotni oladi.
- UI yoki boshqa qatlamlar bilan ishlashda to'g'ridan-to'g'ri Repository'ga murojaat qilishning oldini oladi, shunda kod modullashtirilgan va o'qilishi oson bo'ladi.
- UI yoki ViewModel Use Case'ni chaqiradi.
- Use Case esa Repository bilan bog'lanib, ma'lumotni oladi.
- Agar ma'lumot muvaffaqiyatli yuklansa, Right (success) qiymat qaytaradi.
- Agar xato bo'lsa, Left (failure) qiymat qaytaradi.
class AlbumsBloc extends Bloc<AlbumsEvent, AlbumsState> {
final AlbumsUseCase albumsUseCase;
final AlbumsItemUseCase albumsItemUseCase;
AlbumsBloc({
required this.albumsItemUseCase,
required this.albumsUseCase,
}) : super(AlbumsInitial()) {
on<GetAlbumsEvent>(_getAlbums);
on<GetAlbumsItemEvent>(_getAlbumsItem);
}
}AlbumsBloc- Bu Bloc sinfi AlbumsEvent voqealarini va AlbumsState holatlarini boshqaradi.albumsUseCasevaalbumsItemUseCase- Bu ikkita usecaseAlbumsBlocklassida yaratilgan va ma'lumotlarni olish uchun ishlatiladi.albumsUseCasealbumlar ro'yxatini olishni,albumsItemUseCaseesa album ichidagi rasmlar yoki fayllarni olishni boshqaradi.super(AlbumsInitial())- Bloc boshlang'ich holatini belgilaydi. Dastlabki holat *AlbumsInitial* bo'ladi.
on<GetAlbumsEvent>(_getAlbums);
on<GetAlbumsItemEvent>(_getAlbumsItem);on<GetAlbumsEvent>(_getAlbums):GetAlbumsEventvoqeasi kelganda,_getAlbumsmetodini chaqiradi. Bu voqea albumlar ro'yxatini olish uchun ishlatiladi.on<GetAlbumsItemEvent>(_getAlbumsItem):GetAlbumsItemEventvoqeasi kelganda,_getAlbumsItemmetodini chaqiradi. Bu voqea album ichidagi fayllarni olish uchun ishlatiladi.
Future<void>? _getAlbums(GetAlbumsEvent event, Emitter<AlbumsState> emit) async {
try {
emit(AlbumsLoading()); // Loading holatini yuboradi
final result = await albumsUseCase.call(NoParams()); // albumsUseCase dan ma'lumot olish
result.fold((failure) {
emit(AlbumsError(failure.message)); // Agar xato bo'lsa, error holatini yuboradi
}, (response) {
emit(AlbumsSuccess(response)); // Muvaffaqiyatli ma'lumot bo'lsa, success holatini yuboradi
});
} catch (e) {
emit(AlbumsError(
e.toString())); // Ko'zda tutilmagan xatoliklarni ushlaydi va error holatini yuboradi
}
}emit(AlbumsLoading()): Ma'lumot olish jarayoni boshlanganda Loading holatini yuboradi.albumsUseCase.call(NoParams()):albumsUseCaseyordamida albumlar ro'yxatini olish jarayoni.NoParams- parametrlar bo'lmagan holat.result.fold:Either<Failure, Type>tipi bilan ishlash. Agar xato bo'lsa,AlbumsErrorholatini yuboradi, aks holda muvaffaqiyatli ma'lumot kelganda,AlbumsSuccessholatini yuboradi.catch (e): Agar kodda kutilmagan xato yuz bersa, u holda error holati yuboriladi.
Future<void> _getAlbumsItem(GetAlbumsItemEvent event, Emitter<AlbumsState> emit) async {
try {
emit(AlbumsLoading()); // Loading holatini yuboradi
final result = await albumsItemUseCase.call(
event.selectedAlbum); // AlbumsItemUseCase dan ma'lumot olish
result.fold((failure) {
emit(AlbumsError(failure.message)); // Xato holati yuboriladi
}, (response) {
emit(AlbumsItemSuccess(response)); // Rasm fayllari muvaffaqiyatli yuklandi
});
} catch (e) {
emit(AlbumsError(
e.toString())); // Ko'zda tutilmagan xatoliklarni ushlaydi va error holatini yuboradi
}
}emit(AlbumsLoading()): Yana Loading holati yuboriladi, bu yerda albumga tegishli rasm fayllari olish jarayoni boshlanadi.albumsItemUseCase.call(event.selectedAlbum):albumsItemUseCaseyordamida tanlangan albumga tegishli rasm fayllari olinadi.result.fold: Agar xato bo'lsa,AlbumsErrorholati yuboriladi, aks holda *AlbumsItemSuccess* holati yuboriladi.
AlbumsInitial: Dastlabki holat, Bloc hali ishga tushmagan.AlbumsLoading: Ma'lumot olish jarayoni davom etmoqda.AlbumsError: Xatolik yuz berdi.AlbumsSuccess: Muvaffaqiyatli ma'lumot olinmoqda.AlbumsItemSuccess: Album rasmlari muvaffaqiyatli yuklandi.
abstract class AlbumsEvent extends Equatable {
const AlbumsEvent();
@override
List<Object?> get props => [];
}-
AlbumsEventsinfiEquatablesinfidan meros oladi, bu esa Dartda ob'ektlarni solishtirishni osonlashtiradi. -
Equatableyordamida, agar ikkita ob'ektning barcha xususiyatlari bir xil bo'lsa, ular teng deb hisoblanadi. Bu holat Bloc arxitekturasida voqealarni (event) va holatlarni (state) oson taqqoslash uchun foydalidir. -
props:Equatablesinfi kerakli bo'lgan props (xususiyatlar) ro'yxatini belgilaydi. -
AlbumsEventsinfi uchun, bu bo'sh ro'yxat (list) qaytariladi, chunki bu sinf o'zi xususiyatlarga ega emas. Lekin -
GetAlbumsItemEventkabi sinflar o'z xususiyatlarini qo'shishi mumkin.
class GetAlbumsEvent extends AlbumsEvent {}GetAlbumsEvent— BuAlbumsEventsinfidan meros olgan oddiy sinf. Bu voqea album ro'yxatini olish uchun ishlatiladi. Uning ichida hech qanday parametr yo'q, ya'ni faqatGetAlbumsEventchaqirilganda album ro'yxatini olish jarayoni boshlanadi.
class GetAlbumsItemEvent extends AlbumsEvent {
final AssetPathEntity selectedAlbum;
const GetAlbumsItemEvent(this.selectedAlbum);
}GetAlbumsItemEvent— Bu sinfAlbumsEventdan meros olgan vaselectedAlbumparametriga ega bo'lgan voqea sinfi. Bu voqea albumdan tanlangan rasm yoki faylni olish uchun ishlatiladi.selectedAlbum— Bu parametrAssetPathEntityturidagi ob'ekt bo'lib, tanlangan albumni ifodalaydi.AssetPathEntityphoto_managerpaketidan keladi va albumlar yoki fotosuratlar to'plamini boshqarish uchun ishlatiladi.
Event — Bu foydalanuvchi yoki tizim tomonidan sodir bo'lgan voqea yoki signalni ifodalaydi. Bloc arxitekturasida event ilovaning holatini o'zgartirishga sabab bo'ladigan harakatni ifodalaydi. Eventlar asosan quyidagi holatlarda yuzaga keladi:
- Foydalanuvchi harakati: Foydalanuvchi ekran elementlarini bossa (masalan, tugma bosilishi), ma'lumot yuborishi, o'zgartirishi yoki boshqa harakatlar amalga oshirsa.
- Tizim tomonidan sodir bo'lgan voqealar: Masalan, tizim ishga tushganda, serverdan ma'lumot olish yoki boshqa tizim voqealari.
Misollar:
GetAlbumsEvent: Albumlar ro'yxatini olish uchun ishlatiladigan voqea.GetAlbumsItemEvent: Tanlangan albumdagi rasm yoki fayllarni olish uchun ishlatiladigan voqea.
Blocda eventlar UI qatlamidan keladi va Bloc ularni qayta ishlaydi, ya'ni voqealar Bloc
ga yuboriladi va Bloc tomonidan kerakli holat (state) yangilanadi.
abstract class AlbumsState<T> {}AlbumsStatesinfi abstrakt sinf bo'lib, boshqa holatlarni yaratishda umumiy asos bo'ladi.T— bu generik parametr, ya'ni bu holatga kiradigan ma'lumot turini belgilash uchun ishlatiladi.Tma'lum bir holat uchun turli xil qiymatlar (masalan, rasm yoki albomlar ro'yxati) bo'lishi mumkin. Shu orqali turli xil ma'lumotlar uchun mos holatlarni yaratish mumkin.
class AlbumsInitial extends AlbumsState {}AlbumsInitial— bu dastlabki holat (initial state). Ilova yoki ekranning boshlang'ich holati bo'lib, dastur ishga tushganda yoki ma'lumotlar hali yuklanmagan paytda ishlatiladi.- Bu holat dastur ishga tushishi bilan boshlanadi va uni faqat
Blocni boshlashda ishlatish mumkin.
class AlbumsLoading<T> extends AlbumsState {}AlbumsLoading— bu holat ma'lumotlar yuklanayotganini bildiradi. Agar serverdan yoki lokal ma'lumotlar bazasidan albomlar yoki boshqa ma'lumotlar olinayotgan bo'lsa, bu holat ishlatiladi.- Foydalanuvchi interfeysida bu holat uchun loading indikatorini (masalan, aylanuvchi yuklash iconi) ko'rsatish mumkin.
class AlbumsSuccess<T> extends AlbumsState {
final T data;
AlbumsSuccess(this.data);
}AlbumsSuccess— bu holat, agar ma'lumotlar muvaffaqiyatli yuklansa, ishlatiladi. Bu holatda,Tma'lumotlar muvaffaqiyatli qaytarilgan turini bildiradi.data— bu parametrTturidagi ma'lumotni saqlaydi. Masalan, albomlar ro'yxati yoki tanlangan albumdagi rasmlar.
- Bu holat UI'da ma'lumotlar muvaffaqiyatli yuklangandan keyin ko'rsatiladigan natijani (masalan, albomlar ro'yxatini) bildiradi.
class AlbumsItemSuccess<T> extends AlbumsState {
final T data;
AlbumsItemSuccess(this.data);
}AlbumsItemSuccess— bu sinfAlbumsSuccessbilan juda o'xshash, lekin faqat bir albumdagi ma'lumotlarni yoki rasm elementlarini muvaffaqiyatli olish uchun ishlatiladi.data— bu yerda hamTturidagi ma'lumot (masalan, albomdagi rasmlar) saqlanadi.
class AlbumsError<T> extends AlbumsState {
final String error;
AlbumsError(this.error);
}AlbumsError— bu holat ma'lumotlarni yuklash yoki boshqa operatsiyalar bajarilishida xato yuzaga kelganida ishlatiladi.error— bu xatolikni ta'riflovchi xabar (string). Bu xatolik foydalanuvchiga ko'rsatiladi (masalan, tarmoq xatoliklari yoki server xatoliklari).
- UI'da xatolikni ko'rsatish uchun foydalaniladi (masalan, "Ma'lumotni yuklashda xatolik yuz berdi").
- Generik parametr:
Tgenerik parametr sifatida ishlatilgan, bu esa har bir holatni turli ma'lumot turlari bilan moslashtirish imkonini beradi. Masalan,AlbumsSuccess<List<AssetPathEntity>>yokiAlbumsError<String>. - Bloc holatlari: Bu holatlar Bloc arxitekturasida ishlatiladi. Har bir holat ilovaning
turli bosqichlarini ifodalaydi:
AlbumsInitial— dastlabki holat.AlbumsLoading— ma'lumotlar yuklanayotganda.AlbumsSuccessvaAlbumsItemSuccess— muvaffaqiyatli holatlar (albomlar va rasmlar yuklangan).AlbumsError— xatolik yuz berganda.
Bu holatlar yordamida ilovadagi har bir turli holatni boshqarish va foydalanuvchiga tegishli
ma'lumotlarni ko'rsatish mumkin.
BlocBuilder:
AlbumsBlocdan holatni olish va UI'ni qayta qurish.GetAlbumsEventni yuborish orqali albomlar ma'lumotlarini yuklash boshlanadi.
BlocBuilder<AlbumsBloc, AlbumsState>(bloc: context.read<AlbumsBloc>()..add(GetAlbumsEvent()),
builder: (context, state) {
if (state is GalleryInitial) {
return Center(
child: Text(
"Initial",
style: TextStyle(fontSize: 50, color: Colors.red),
),
);
}
// loading holati
if (state is AlbumsLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
// Success holati
if (state is AlbumsSuccess) {
// agar state yani holat AlbumsSuccess bo'lsa
// state.data qilb malumot olinadi bu malumot AlbumsBloc dan keladi
return CustomSliverAppBar(
title: "Album",
child: SliverList(
delegate: SliverChildBuilderDelegate(
childCount: 1,
(context, index) {
return GridView.builder(
itemCount: state.data.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: .8,
crossAxisSpacing: 5,
),
itemBuilder: (context, index) {
// kelganm ma'lumotlarni list ning index ni itemsOfAlbums ga beramiz va itemlar chiziladi
AssetPathEntity album = state.data[index];
return ItemsOfAlbumsWidget(
album: album,
);
});
}),
));
}
if (state is AlbumsError) {
Center(
child: Text(
"Error",
style: TextStyle(fontSize: 50, color: Colors.red),
),
);
}
return Center(
child: Text("Album"),
);
},
);state is GalleryInitial:
- Agar
GalleryInitialholati bo'lsa, "Initial" matni ko'rsatiladi. Bu holat odatda dastur boshlanganda yoki ma'lumotlar hali mavjud bo'lmagan paytda bo'lishi mumkin.
if(state is GalleryInitial) {
return Center(
child: Text(
"Initial",
style: TextStyle(fontSize: 50, color: Colors.red),
),
);
}state is AlbumsLoading:
- Agar albomlar yuklanayotgan bo'lsa, foydalanuvchiga
CircularProgressIndicator(loading indicator) ko'rsatiladi.
// loading holati
if (state is AlbumsLoading) {
return Center(
child: CircularProgressIndicator(),
);
}state is AlbumsSuccess:
- Agar albomlar muvaffaqiyatli yuklangan bo'lsa,
GridView.builderyordamida albomlar ro'yxati ko' rsatiladi. CustomSliverAppBaryordamida yuqori qismda albomlar nomi va yuklangan albomlar grid ko'rinishida ekranga chiqariladi.
if (state is AlbumsSuccess) {
// agar state yani holat AlbumsSuccess bo'lsa
// state.data qilb malumot olinadi bu malumot AlbumsBloc dan keladi
return CustomSliverAppBar(
title: "Album",
child: SliverList(
delegate: SliverChildBuilderDelegate(
childCount: 1,
(context, index) {
return GridView.builder(
itemCount: state.data.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: .8,
crossAxisSpacing: 5,
),
itemBuilder: (context, index) {
// kelganm ma'lumotlarni list ning index ni itemsOfAlbums ga beramiz va itemlar chiziladi
AssetPathEntity album = state.data[index];
return ItemsOfAlbumsWidget(
album: album,
);
});
}),
));
}state is AlbumsError:
- Agar xatolik yuzaga kelsa, "Error" matni ko'rsatiladi.
Default Holat:
- Agar boshqa holatlar mavjud bo'lmasa, "Album" matni ko'rsatiladi.
```dart if (state is AlbumsError) { Center( child: Text( "Error", style: TextStyle(fontSize: 50, color: Colors.red), ), ); } ```
Bu kod turli holatlarni boshqarib, foydalanuvchiga to'g'ri interfeysni taqdim etadi: ma'lumot yuklanmoqda, muvaffaqiyatli yuklandi yoki xatolik yuzaga keldi.
class MediaEntityProvider extends StatelessWidget {
final AssetEntity entity;
const MediaEntityProvider({super.key, required this.entity});
@override
Widget build(BuildContext context) {
return Image(
image: AssetEntityImageProvider(
entity,
isOriginal: false,
thumbnailSize: ThumbnailSize(200, 200),
),
fit: BoxFit.cover,
);
}
}Widget mediaEntityProvider(AssetEntity entity) {
return Image(
image: AssetEntityImageProvider(
entity,
isOriginal: false,
thumbnailSize: ThumbnailSize(200, 200),
),
fit: BoxFit.cover,
);
}| Metrika | Class (StatelessWidget) | Function (Widget qaytaruvchi) |
|---|---|---|
| Clean Code | ✅ Tozaroq va professionalroq | |
| Performance | ⚖️ Bir xil ishlaydi | ⚖️ Bir xil ishlaydi |
| Reusable | ✅ Oson qayta foydalaniladi | ❌ Funksiya faqat ma'lum joyda foydalanish uchun qulay |
| State Management | ✅ GetX, Provider, Bloc bilan integratsiya qilish oson | |
| Constructor Args | ✅ Parametrlar bilan ishlash qulay | ❌ Parametrlar uzatilishi uncha oson emas |
| Hot Reload | ✅ Tezroq aniqlanadi va qayta yuklanadi | |
| Xavfsizlik | ✅ Xatolarni ushlab turadi va kodni himoyalaydi | ❌ Potensial xatolarni oldindan ko'rsatmaydi |
✅ Class (StatelessWidget) shakli clean code tamoyillariga ko'proq mos keladi.
- Professional loyihalarda
Widgetlarni klass shaklida yozish tavsiya etiladi. - Xato qilish ehtimolini kamaytiradi, ayniqsa murakkab
stateboshqaruvi bilan ishlaganda. StatelessWidgetFlutter lifecycle (hayot tsikli) funksiyalaridan to'liq foydalanish imkonini beradi.
💡 Ikkala usul ham ishlash tezligida deyarli bir xil. Ammo klass shaklida yozilgani kuzatish (tracking) va optimallashtirish uchun qulayroq bo'ladi.
✅ Class shakli xavfsizroq, chunki:
- Flutter lifecycle funksiyalari bilan ishlashga imkon beradi (
initState,disposeva boshqalar). - Parametrlarni
finalqilib o'rnatish mumkin, bu esa kodni o'zgarmas (immutable) qiladi. - Null safety xatolaridan himoya qilish osonroq.
Agar bir marta ishlatiladigan oddiy widget kerak bo'lsa, funksiya variantini ishlatish mumkin.
Ammo professional darajada, qayta foydalanish uchun yoki murakkab UI yaratishda StatelessWidget yoki StatefulWidget ishlatish to'g'ri yo'l hisoblanadi.
class MediaEntityProvider extends StatelessWidget {
final AssetEntity entity;
const MediaEntityProvider({super.key, required this.entity});
@override
Widget build(BuildContext context) {
return Image(
image: AssetEntityImageProvider(
entity,
isOriginal: false,
thumbnailSize: ThumbnailSize(200, 200),
),
fit: BoxFit.cover,
);
}
}Failure kalss xato yuz berganda uni saqlaydi va xatolik turi sifatida ishlatiladi
class Failure {
final String message;
Failure({required this.message});
}Failuresinfi dasturda xatoliklarni ifodalash uchun ishlatiladi. Masalan, biror operatsiya yoki tarmoq so'rovi muvaffaqiyatsiz bo'lsa, ushbu sinf yordamida xatolikni saqlash va foydalanuvchiga xatolik haqida ma'lumot berish mumkin. Bu xatolikni tizimga qaytarishda yoki foydalanuvchiga ko'rsatishda yordam beradi.
Agar tarmoq so'rovi muvaffaqiyatsiz bo'lsa, siz Failure sinfini quyidagicha ishlatishingiz mumkin:
Failure failure = Failure(message: "Tarmoq so'rovi xatoligi.");Bu ikki funksiya groupImagesByDate , dateFormat abstract class AppServices ichida joylashgan
static Map<String, List<AssetEntity>> groupImagesByDate(List<AssetEntity> images) {
Map<String, List<AssetEntity>> groupedImages = {};
for (var image in images) {
// Sana faqat yili, oyi va kuni
String date = image.createDateTime.toLocal().toString().split(' ')[0];
if (groupedImages[date] == null) {
groupedImages[date] = [];
}
groupedImages[date]!.add(image);
}
return groupedImages;
}- Ma'lumot turi: Ushbu metod
Map<String, List<AssetEntity>>turidagi qiymatni qaytaradi, ya'ni sanalarga asoslangan ravishda tasvirlar (AssetEntityobyektlari) guruhlanadi. - Foydalanuvchi maqsadi: Tasvirlar ro'yxatini sana bo'yicha guruhlash.
- Qanday ishlaydi:
image.createDateTime.toLocal().toString().split(' ')[0]qator yordamida tasvirning yaratilgan sanasi olinadi va faqat yili, oyi va kuni olinadi (YYYY-MM-DDformatida).groupedImagesxaritasida sanaga asoslangan guruhlar yaratilib, har bir guruhga mos tasvirlar qo'shiladi.groupedImagesxaritasi, sanalar va ular bilan bog'langan tasvirlar ro'yxatini o'z ichiga oladi.
Misol: Agar images ro'yxatida 3 ta tasvir bo'lsa va ular turli sanalarda bo'lsa, metod ularni har bir sanaga mos ravishda guruhlaydi.
static String dateFormat(String? dateString) {
if (dateString == null || dateString.isEmpty) {
return 'Unknown Date'; // Default qiymat
}
try {
// Stringni DateTime obyektiga o'zgartirish
DateTime date = DateTime.parse(dateString);
// Sana formatlash
return DateFormat('MMM dd, yyyy').format(date);
} catch (e) {
// Agar format noto'g'ri bo'lsa
return 'Invalid Date';
}
}- Ma'lumot turi: Ushbu metod
Stringturidagi qiymatni qaytaradi, ya'ni sananing formatlangan ko'rinishini. - Foydalanuvchi maqsadi: Berilgan sana satrini o'zgartirib, uni kerakli formatda chiqarish.
- Qanday ishlaydi:
- Agar
dateStringnullyoki bo'sh bo'lsa, metod'Unknown Date'matnini qaytaradi. - Sana stringi
DateTime.parse(dateString)yordamidaDateTimeobyektiga aylantiriladi. DateFormat('MMM dd, yyyy').format(date)yordamida sana kerakli formatda (masalan:Jan 01, 2025) qaytariladi.- Agar sana noto'g'ri formatda bo'lsa, metod
'Invalid Date'xatolik xabarini qaytaradi.
- Agar
Misol: Agar dateString 2025-01-01T12:00:00Z bo'lsa, metod 'Jan 01, 2025' natijasini qaytaradi.
groupImagesByDatemetodi tasvirlarni sanaga qarab guruhlash uchun ishlatiladi.dateFormatmetodi esa sana satrini kerakli formatda chiqarish uchun ishlatiladi.- Ikki metod ham foydalanuvchiga tasvirlar va sana bilan ishlashda yordam beradigan funktsional imkoniyatlar taqdim etadi.
Ushbu kod AppPages sinfini yaratadi, bu sinf ilovada ishlatiladigan sahifalar (routes) va ularning tegishli bloklarini (Blocs) boshqarish uchun javobgardir. Tahlil qilaylik:
static final RouteObserver<Route> observer = RouteObserver();RouteObserver — bu, ilovaning marshrut (yoki sahifa) o'zgarishlarini kuzatish uchun ishlatiladi. Masalan, sahifalar o'zgarganida yoki qayta ko'rsatilganda (push/pop) kuzatuvchi ishlatiladi.
- Foydalanuvchi maqsadi: Sahifalar orasidagi o'zgarishlarni kuzatish va ilova tarixini boshqarish.
static List<String> history = [];Bu ro'yxat, ilovaning tarixini saqlaydi, ya'ni foydalanuvchi qaysi sahifalardan o'tganligini saqlash uchun ishlatiladi.
- Foydalanuvchi maqsadi: Ilova navigatsiyasining tarixini saqlash va unga murojaat qilish.
static List<PageEntity> routes(){
return [
PageEntity(
path: AppRoutes.MAIN,
page: MainScreen(),
bloc: BlocProvider(create: (_) => MainBloc())
),
PageEntity(
path: AppRoutes.GALLERY,
page: GalleryScreen(),
bloc: BlocProvider(create: (_) => GalleryBloc(mediaAssetsUseCase: getIt<MediaAssetsUseCase>()))
),
PageEntity(
path: AppRoutes.ALBUMS,
page: AlbumsScreen(),
bloc: BlocProvider(create: (_) => AlbumsBloc(albumsItemUseCase: getIt<AlbumsItemUseCase>(), albumsUseCase: getIt<AlbumsUseCase>()))
),
];
}Bu metod ilovadagi barcha sahifalar va ular bilan bog'liq `BlocProvider`lar ro'yxatini qaytaradi. Har bir `PageEntity` obyekti sahifaning `path` (yo'l), sahifa widgeti (`page`), va sahifa bilan bog'liq `bloc`ni o'z ichiga oladi. - **Foydalanuvchi maqsadi**: Ilovadagi barcha sahifalar va ularning `Bloc`larini ro'yxatga olish, navigatsiya qilish va kerakli sahifalarni ko'rsatish.
static List<dynamic> blocer(BuildContext context) {
List<dynamic> blocerList = <dynamic>[];
for (var blocer in routes()) {
blocerList.add(blocer.bloc);
}
return blocerList;
}Bu metod routes() ro'yxatidan har bir sahifaning blocini olib, ularni alohida ro'yxatda (List) saqlaydi va qaytaradi. Bu metod ilovadagi barcha Bloclarni olish uchun ishlatiladi.
- Foydalanuvchi maqsadi: Barcha kerakli
Bloclarni olish va ularni boshqarish.
static MaterialPageRoute? generateRouteSettings(RouteSettings settings) {
if (settings.name != null) {
var result = routes().where((element) => element.path == settings.name);
if (result.isNotEmpty) {
if (result.first.path == AppRoutes.initialRoute) {
return MaterialPageRoute<void>(
builder: (_) => MainScreen(), settings: settings);
}
return MaterialPageRoute<void>(
builder: (_) => result.first.page, settings: settings);
}
}
return null;
}Bu metod dinamik marshrutlashni boshqaradi. RouteSettingsdan kelgan name qiymati bo'yicha sahifa topiladi va kerakli sahifa yuklanadi.
- Agar settings.name AppRoutes.MAIN kabi bir yo'lni ko'rsatsa, ilova MainScreenni ochadi.
- Agar settings.name boshqa bir yo'lni ko'rsatsa, routes() ro'yxatidan mos sahifa topiladi va yuklanadi.
- Agar sahifa topilmasa, null qaytariladi.
- Foydalanuvchi maqsadi: Dinamik ravishda sahifalarni yuklash va marshrutlarni boshqarish.
class PageEntity<T> {
String path;
Widget page;
dynamic bloc;
PageEntity({
required this.path,
required this.page,
required this.bloc,
});
}PageEntity — bu sahifa va uning tegishli blocini saqlash uchun ishlatiladigan oddiy sinf.
- path: Sahifaning yo'li (masalan, AppRoutes.MAIN).
- page: Sahifa widgeti (masalan, MainScreen).
- bloc: Sahifa bilan bog'liq Bloc (masalan, MainBloc).
- Foydalanuvchi maqsadi: Har bir sahifa, uning yo'li va tegishli
Blocini saqlash.
- AppPages sinfi ilovadagi sahifalar va ularning
Bloclarini boshqaradi. - routes() metodi sahifalar ro'yxatini yaratadi va kerakli
Bloclar bilan qaytaradi. - generateRouteSettings() metodi dinamik marshrutlashni boshqaradi.
- blocer() metodi ilovadagi barcha
Bloclarni qaytaradi.
Bu tizim sahifalar va ularning kerakli holatlarini boshqarishda yordam beradi va ilovadagi marshrutlarni dinamik ravishda boshqarishni osonlashtiradi.
Ushbu kodda GetIt kutubxonasi yordamida ilovadagi turli xizmatlarni va bloklarni (BLoC) oson boshqarish va ularga murojaat qilish uchun bog'lanishlar (dependencies) o'rnatilgan. Kodning har bir qismi quyidagicha ishlaydi:
final getIt = GetIt.instance;GetIt - bu Dart uchun eng mashhur bog'lanishlarni boshqarish kutubxonasi. Bu orqali ilovada barcha zaruriy ob'ektlarni ro'yxatga olish va ularga osonlik bilan murojaat qilish mumkin.
- Foydalanuvchi maqsadi:
getItyordamida bog'lanishlar (services, repositories, blocs) boshqariladi va ularga oson murojaat qilish mumkin.
Future<void> init() async {
initDataSource();
initRepositories();
initUseCases();
initBlocs();
}Bu funksiya barcha kerakli komponentlarni (DataSources, Repositories, UseCases, Blocs) ro'yxatga olish uchun ishlatiladi. Bu metod ilova ishga tushganida barcha bog'lanishlar va resurslar to'g'ri o'rnatilishi uchun ishlaydi.
- Foydalanuvchi maqsadi: Ilovaning boshlanishida barcha zarur komponentlarni ro'yxatga olish va ularni ishlatishga tayyor qilish.
void initDataSource() {
getIt.registerLazySingleton<MediaLocalDataSourceImpl>(
() => MediaLocalDataSourceImpl());
getIt.registerLazySingleton<AlbumsLocalDataSourceImpl>(
() => AlbumsLocalDataSourceImpl());
}Bu metod ilovadagi lokal ma'lumot manbalarini (DataSources) ro'yxatga oladi.
- MediaLocalDataSourceImpl: Media fayllari bilan ishlash uchun lokal manba.
- AlbumsLocalDataSourceImpl: Albomlar bilan ishlash uchun lokal manba.
- Foydalanuvchi maqsadi: Mahalliy ma'lumot manbalarini (local data sources) ro'yxatga olish va ishlatishga tayyorlash.
void initRepositories() {
getIt.registerLazySingleton<MediaRepositoryImpl>(
() => MediaRepositoryImpl(getIt<MediaLocalDataSourceImpl>()),
);
getIt.registerLazySingleton<AlbumsRepositoryImpl>(
() => AlbumsRepositoryImpl(getIt<AlbumsLocalDataSourceImpl>()),
);
}Bu metodda ilovadagi Repositories ro'yxatga olinadi.
- MediaRepositoryImpl: Media ma'lumotlarini olish va saqlash uchun mas'ul.
- AlbumsRepositoryImpl: Albom ma'lumotlarini olish va saqlash uchun mas'ul.
- Foydalanuvchi maqsadi: Ma'lumotlarni olish va saqlash jarayonlarini boshqaradigan repositoriylarni ro'yxatga olish.
void initUseCases() {
getIt.registerLazySingleton(
() => AlbumsUseCase(getIt<AlbumsRepositoryImpl>()));
getIt.registerLazySingleton(
() => AlbumsItemUseCase(getIt<AlbumsRepositoryImpl>()));
getIt.registerLazySingleton(
() => MediaAssetsUseCase(getIt<MediaRepositoryImpl>()));
}Bu metodda ilovadagi `UseCases` ro'yxatga olinadi. - `AlbumsUseCase`: Albomlar bilan bog'liq biznes mantiqni bajaradigan klass. - `AlbumsItemUseCase`: Albomlar bo'yicha maxsus operatsiyalarni bajaradi. - `MediaAssetsUseCase`: Media aktivlari bilan bog'liq biznes mantiqni bajaradi. - **Foydalanuvchi maqsadi**: Dasturdagi biznes mantiqlarini ishlatish uchun zarur bo'lgan `UseCase`larni ro'yxatga olish.
void initBlocs() {
getIt.registerFactory(() => GalleryBloc(
mediaAssetsUseCase: getIt<MediaAssetsUseCase>(),
));
getIt.registerFactory(() => AlbumsBloc(
albumsItemUseCase: getIt<AlbumsItemUseCase>(),
albumsUseCase: getIt<AlbumsUseCase>(),
));
getIt.registerFactory(() => MainBloc());
}Bu metodda ilovadagi barcha BLoClar ro'yxatga olinadi. BLoClar — bu ilova holatini boshqarish uchun ishlatiladi.
- GalleryBloc: Media aktyorlar bilan bog'liq holatni boshqaradi.
- AlbumsBloc: Albomlar bilan bog'liq holatni boshqaradi.
- MainBloc: Asosiy sahifa uchun holatni boshqaradi.
- Foydalanuvchi maqsadi: Sahifalar va funksiyalar uchun zarur bo'lgan holatni boshqarish uchun
BLoClarni ro'yxatga olish.
Bu kodda GetIt orqali ilovadagi barcha zarur komponentlar
(DataSources, Repositories, UseCases, BLoCs) ro'yxatga olinadi va ular ishlatishga tayyorlanadi.
Bu arxitektura kodni boshqarishni osonlashtiradi va uni test qilishni soddalashtiradi. LazySingleton va Factory
usullari yordamida komponentlar bir marta yaratilib, kerak bo'lganda ishlatiladi. Bu tizim BLoC holatni boshqarish va
ma'lumotlarga ishlov berishni tartibga soladi.

