DEV Community

Cover image for Flutter Architecture 💫 🌌 ✨
Gülsen Keskin
Gülsen Keskin

Posted on

Flutter Architecture 💫 🌌 ✨

Image description

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.

Image description

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

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 YourInteractorve 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }

}
Enter fullscreen mode Exit fullscreen mode

Provider'a tepki veren ve yeniden oluşturan kullanıcı arayüzü örneği:

    Consumer<MeetupScreenProvider>(
      builder: (context, provider, _) {
        return _MeetupList(list: provider.state);
      },
    ),
Enter fullscreen mode Exit fullscreen mode

İ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){...}
}
Enter fullscreen mode Exit fullscreen mode

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.

resource

Top comments (0)