Not: Bu yazı Flutter Handbook'un makalesinin Türkçe çevirisi niteliğini taşır. Orijinal içeriğe linkten ulaşabilirsiniz.
app flutter.onError kilitlenme işleme ve bağımlılık başlatma gibi çeşitli kurulumlara sahip run_app uygulamasını içerir.
common tüm katmanlar için ortak olan ve tüm katmanlar tarafından erişilebilen kodu içerir.
device cihaz donanımı (örn. sensörler) veya yazılım (takvim, izinler) ile iletişimi temsil eden bir dış katmandır.
source_remote uzak kaynaklarla (web, http istemcileri, soketler) iletişimi temsil eden bir dış katmandır.
source_local yerel kaynaklarla (veritabanı, shared_prefs) iletişimi temsil eden bir dış katmandır.
domain genellikle interactor ve data holderları içeren iç katmandır. Bu katman yalnızca iş mantığını içermeli ve ui, web vb. veya diğer katmanlara özgü bilgileri bilmemelidir.
ui widget'ları ve providerlar tarafından paketlediğimiz katmandır. Provider'lar sunum mantığı içerir ve etki alanına erişirler.
Önceden tanımlanmış katmanlarda sıklıkla kullanacağımız birkaç sınıf vardır.
Repository ve Manager
Repository, uygulamamızın dış kısmıdır. source_remote, source_local veya device'a aittir. Dio, Hive, add2calendar, diğer eklentiler gibi somut uygulamaları kullanır ve bunları uygulamanın geri kalanından soyutlar.
Repository bir arayüzün (interface) arkasında olmalıdır. Bu, YourRepository
interface'ini oluşturmanız ve YourRepositoryImpl'i
YourRepository'e
implemente etmektir.
YourRepository
interface'i domain'e, YourRepositoryImpl
ise dış katmanlara aittir.
Bu şekilde domain repository'e erişebilir.
//domain/repository/meetup_repository/meetup_repository.dart
abstract class MeetupRepository {
Future<List<Meetup>> getListOfMeetups();
}
//source_remote/impl/meetup_repository/meetup_repository_impl.dart
class MeetupRepositoryImpl implements MeetupRepository {
MeetupRepositoryImpl(this._dio);
final Dio _dio;
@override
Future<List<Meetup>> getListOfMeetups() async {
final Response<String> response = await _dio.post<String>('/api/meetups');
return MeetupsResponse.fromJson(jsonDecode(response.data)).meetups;
}
}
Manager, Repository ile aynı şekilde çalışır, Manager'ı yalnızca daha iyi adlandırma için kullanırız. Bazen bu katman, örneğin takvime etkinlik ekleyerek, bluetooth'u açarak veya izinleri yöneterek etkin bir şekilde yönetebilir. Onlara BluetoothRepository demek yerine BluetoothManager gibi bir isim kullanırız.
Interactor (etkileşimci):
Interactor, domaine ait olan iç kısımdır.
Interactor, uygulamanın iş mantığını içerir, repository'ler dahil olmak üzere domainden diğer sınıflara erişebilirler.
Interactor ayrıca daha kolay test için bir interface'in arkasındadır, bu nedenle YourInteractor
ve YourInteractorImpl'i
oluşturuyoruz.
Interactor'ın ana işi, farklı repository'leri birleştirmek ve iş mantığını yönetmektir.
Takvime buluşma etkinliği eklemek için interactor örneği:
//domain/interactor/add_event_to_interactor/add_event_to_interactor.dart
abstract class AddEventToCalendarInteractor {
Future<void> addEventToCalendar(Meetup event);
}
//domain/interactor/add_event_to_interactor/add_event_to_interactor_impl.dart
class AddEventToCalendarInteractorImpl extends AddEventToCalendarInteractor {
AddEventToCalendarInteractorImpl(this._calendarManager, this._meetupRepository);
final CalendarManager _calendarManager;
final MeetupRepository _meetupRepository;
@override
Future<void> addEventToCalendar(Meetup meetup) async {
final dateTimeOfEvent = await _meetupRepository.getMeetupEventDate(meetup);
final CalendarEvent event = CalendarEvent(meetup.name, dateTimeOfEvent);
return await _calendarManager.addEventToCalendar(event);
}
}
Provider ve widgetlar
Provider ve widget'lar, kullanıcı arayüzüne koyduğumuz sunumun bir parçasıdır.
Birlikte çalıştıkları için gezinmeyi kolaylaştıran katmanlar halinde paketlenirler.
Provider, genellikle görünüm durumunu kontrol eden sunum mantığını (presentation logic) içerir.
Widget bu durumu gözlemler ve state değişikliğini yeniden oluşturabilir.(rebuild)
Bu şekilde görünüm pasiftir ve sadece değişikliklere tepki verir.
Bakımı ve testi kolaydır.
Görünüm, çoğunlukla, provider görünüm durumunu gözlemleyen stateless (durumsuz) widget'lardan oluşmalıdır.
Provider örneği:
//ui/meetup/provider/meetup_screen_provider.dart
class MeetupScreenProvider extends ChangeNotifier {
MeetupScreenProvider(this._addEventToCalendarInteractor);
final AddEventToCalendarInteractor _addEventToCalendarInteractor;
final AddMeetupToFavoritesInteractor _addMeetupToFavoritesInteractor;
void onAddToCalendar(Meetup meetup) {
_addEventToCalendarInteractor.addEventToCalendar(meetup);
}
void onMeetupFavorite(Meetup meetup) {
_addMeetupToFavoritesInteractor.addToFavorites(meetup);
}
}
Provider'a tepki veren ve yeniden oluşturan kullanıcı arayüzü örneği:
Consumer<MeetupScreenProvider>(
builder: (context, provider, _) {
return _MeetupList(list: provider.state);
},
),
İstek, yükleme, başarı ve hata gibi değerlerle başlı başına bir state'dir.
Bu nedenle, genellikle istekleri hepsi bir arada providerda diğer tüm state'ler ile birlikte tek bir mega durumda paketlemiyoruz.
Loading indicator veya hataları göstermek için genellikle tam olarak bir request state'ini (istek durumunu) dinlemeniz gerektiğinden, provider mega state de dinleyen tüm dinleyicileri güncelleyeceğinden sorunlara neden olur.
Bu mega sağlayıcı da büyük ve bakımı zor olabilir.
Modeller
Modeller basit veri yapılarıdır.
Genellikle common klasörünün (/common /models) altında bulunurlar.
Birden çok katman tarafından kullanılan modellerdir.
Örneğin, source_remote tarafından kullanılan @JsonSerializable özelliğine sahip kullanıcınız olabilir, ancak aynı model domain ve UI tarafından da kullanılır.
Ayrıca belirli bir katmanın (/source_remote/model veya ui/my_feature/model) veya belirli bir özelliğin (/domain/manager/permission_manager/device_permissions.dart) parçası olarak modelleriniz olabilir.
Tüm bu modelleri ayırt etmek için bazı katmanlar için özel adlandırmalar vardır.
UI modellerine Ui (ör. ArticleUi) ekliyoruz ve domain modeli ekranda gösterilmesi gerekene uymadığında bunu kullanıyoruz.
Bu modeller sadece kullanıcı ara yüzünde kullanılır.
Diğer dış katmanlardan gelen modeller, Veri Aktarım Nesnesi anlamına gelen Dto ile eklenir (örneğin, ArticleDto).
Bunları, dış katmanlardan aldığımız yapı (API gibi) çalışmak istediğimiz formatta olmadığında kullanırız, bu nedenle ArticleDto (API modeli) ve Article (uygulamamız için model) oluştururuz.
İç katmanın dış katmanların özelliklerini bilmemesi gerektiğini söylediğimiz gibi, burada da aynısı geçerli.
Domain, Ui veya Dto modellerini asla bilmemelidir.
Repository veya provider da map'lenmeleri gerekir.
Data holders
DataHolder
, verileri bellekte tutan tek bir sınıftır (singleton class).
Bir interface'i yoktur ve yalnızca veri almak veya veri ayarlamak için veri ve yöntemlere sahiptir.
Data holder'lar domain'in bir parçasıdır ve repository veya diğer dış katmanları çağırmazlar.
Mappers
Bunlar, modelleri farklı katmanlar arasında eşleyecek statik yöntemlere sahip sınıflardır.
ArticleDto -> Article mapping için ArticleMapper'ı oluşturuyoruz.
Tam tersi için Article -> ArticleDto, ArticleDtoMapper'ı oluşturuyoruz.
Mapper (eşleştirici) birden çok yönteme sahip olabilir:
class ArticleMapper {
Article map(ArticleDTO dto){...}
Article mapFromXyz(XyzDTO dto){...}
List<Article> mapToList(List<ArticleDTO> dto){...}
}
Mapper'lar, ui, source_remote, device ve diğer dış katmanların (/source_remote/mapper) parçasıdır. Stateless widget'ınız kullanıcı arayüzü veya DTO'lar gerektirmiyorsa, mapper'lara ihtiyacınız yoktur.
Top comments (0)