Clean Architecture açıklaması supabase + bloc

Gauloran

Global Moderatör
7 Tem 2013
8,124
610
local
Yazılım geliştirirken herkes elinize sağlık der ama kimse projenizin aslında çöp olduğunu söylemez. Bu konu projenizi çöp olmaktan kurtaracak ve projenizin bakımı kolay olacak geliştirilebilir olacak. Clean Architecture yaklaşımı uygulamanın iş mantığını logiclerini cartını curtunu kullanıcı arayüzünü ve veri katmanını birbirinden ayırarak birbirinden bağımsız şekilde organize etmeye yarar. Her bir katmanın tek sorumluluğu olunca kodunuz okunabilir olur projeniz geliştirilebilir olur hatta esnek ve test edilebilir olur. Aşağıda verilen kodlarda tamamen temiz kod yazılmamış olup clean arc.ın anlaşılması için temel birkaç bir şey karalanmıştır.

UnQ3Egy2Vo-L.png
I9Kq5.png
!!!GİZLİLİĞİ KALKMIŞ BİLGİLER!!!
Kod:
Presentation katmanı projenizin UI tarafıdır. Widgetları içerir ve state yönetim şeklini içerir.
Bir eviniz varsa ve evinize misafir geldiyse presentation katmanı evinizin oturma odasıdır.
Domain katmanı ise evinizin blueprintlerini içeren eviniz yapılırken muhtemelen malzeme çalan kişilerin hesapladığı blueprintlerdir.

7ecec434137d1fcbe023db38e06c1260.jpg


Bu yaklaşımda Domain katmanı, Data katmanı ve Presentation katmanı vardır. Bu 3 birbirinden bağımsız katmandan domain katmanı iş mantığını tanımladığımız blueprint diyebileceğimiz katmandır. Data katmanındaysa veritabanı erişimi yani dış verilerin alındığı katmandır bu katmanda repositories ve data source gibi klasörler işinizi görecek. Bir diğer katman olan son kullanıcı katmanı presentation layer UI'ın yer aldığı katmandır. Bu katmana örneğin Flutterdaki widgetlarla kullanıcıların etkileşim halinde olabileceği katman diyebiliriz.

Diyelim ki Flutter kullanarak bir mobil uygulama yapacaksınız projeyi açtınız ve uygulamanızda kullanıcıların üye olması veya giriş yapması için bir şeyler yazacaksınız. Direkt main.dart'ın yanına dosyalar açıp çatır çutur yazarsanız tebrikler! Muhtemelen projenizi sadece siz anlayacaksınız ve çöp ettiniz! Bunları önlemek için bu yaklaşımla olaya mimari yaklaşımla gitmelisiniz. Öncelikle uygulamanızda ne olacak sign up sign in yani buna auth diyebiliriz. Bu sizin uygulamanızın bir özelliği sayılmaz mı? Sonuç olarak giriş yapma ve kayıt olma özelliği var.

Projenizde öncelikle features diye bir klasör açın. İçerisine de auth diye bir klasör açın. Sonuç olarak başka klasörler de açabilirsiniz ilerde uygulamanıza başka özellikler eklerseniz vs. Auth klasörü içerisinde şimdi 3 tane klasör açacaksınız


!!!GİZLİLİĞİ KALKMIŞ BİLGİLER!!!
Kod:
Uygulamanızda features klasörü içerisinde uygulamanızın özellikleri olacak. Ve bu özelliklere göre
data, domain ve presentation (3 katman içerecek ve bu katmanlar birbirinden bağımsız olacak) ama birlikte de iş yapabilecekler.

1-data
2-domain
3-presentation

f9242f19dc3f4efbea6efca419bea93e.jpg


3 katmanı koyduğunuza göre tebrikler çöp kod yazmaktan kurtulmaya bir adım daha yaklaştınız! En üst katmandan açıklamaya başlayayım. Presentation katmanı en üst kullanıcının gördüğü katman olacak. Bu katmanda pages yani uygulamanızın UI'ı olacak sayfaların tasarımı bu katmanda olacak ve Flutter bazında yeniden kullanılabilen widgetlar bu katmanda olmalıdır.

features>auth>presentation>pages>
login_page.dart
signup_page.dart

Presentation katmanında uygulamanızın state yönetimini yapmanız gerekecek artık hangi state yönetme yöntemini kullanıyorsanız onun adını verdiğiniz bir klasör açın presentation içerisine. Örneğin bloc.

presentation>
bloc>
auth_bloc.dart
auth_event.dart
auth_state.dart

diyelim ki Bloc kullanıyorum ama bloc içerisindeki 2 yaklaşımdan cubitli yaklaşımı değil de bloc yaklaşımını kullanıp actionslarla uygulama state'ini yönetmek istiyorum. O zaman uygun klasör yapım bu şekilde olmalı. İstediğiniz state management yöntemini kullanın. Domain katmanına bakacak olursak bu katmanda UseCaseler ve Entitiler ve bir de Repository klasörü açacaksınız. UseCaseler state managementınızın tetikleyeceği şeyler gibi düşünün her seferinde 1 işlem yapacak olan örneğin kullanıcı kaydı olmak için user_sign_up.dart bir usecasedir ve user_login.dart başka bir usecasedir.

domain>
usecases>
user_login.dart
user_sign_up.dart

!!!GİZLİLİĞİ KALKMIŞ BİLGİLER!!!

Kod:
Mobil uygulama projenizin klasör yapısı temel çapta böyle olabilir:
core>common, error, secrets, theme, usecase, utils
features> auth> data, domain, presentation


ArminArlert.jpg

Entities olarak açtığımız klasör ise user.dart diye dosya açarak kullanıcıların model sınıfını burada oluşturuyoruz. Domain katmanında bir de repository olacak örneğin auth feature'ı bazında auth_repository.dart diyebiliriz. Bu repository dosyasında interface oluşturdunuz örneğin soyut bir sınıf ve signUp, login gibi fonksiyonları içeren interface gibi düşünün. OOP mantığıyla ilerleyip öyle yazmaya çalışacaksınız.

Data klasörünün içerisinde de repositories, models ve datasources klasörleri açacaksınız ve fark ettiyseniz Domain katmanında da repository var burada da var bu sayede bu katmanlar birbirinden bağımsız ama Data kısmında soyut sınıf değil de direkt olarak AuthRepositoryImpl diyerek mesela yani implementasyonunu burada yapacaksınız örneğin Domaindeki repositoryde AuthRepository diye sınıf oluşturup yazdınız burada o sınıfın implementasyonunu yazacaksınız ve Constructorında AuthRemoteDataSource isteyeceksiniz. Bu mantıkla gittiğinizde db değişikliği yaptığınızda yeni bir sınıf oluşturup kodları yazıp başka bir yerde değişiklik yapmanıza gerek kalmadan projeyi geliştirmeye devam edebileceksiniz.

Yani domain katmanındaki repository data katmanındaki repositorynizin nasıl göründüğünü nasıl olacağını belirleyecek. Asıl olarak yapma eylemi Data katmanında gerçekleşmekte. Asıl implementasyon bu katmanda gerçekleşiyor.


!!!GİZLİLİĞİ KALKMIŞ BİLGİLER!!!

Kod:
Bu uygulamanızın presentation katmanındaki UI kodları böyle olabilir ve böyle gözükebilir:

Kod:
import 'package:blog_for_modals_app/core/common/widgets/loader.dart';
import 'package:blog_for_modals_app/core/theme/app_pallete.dart';
import 'package:blog_for_modals_app/core/utils/show_snackbar.dart';
import 'package:blog_for_modals_app/features/auth/presentation/bloc/auth_bloc.dart';
import 'package:blog_for_modals_app/features/auth/presentation/pages/login_page.dart';
import 'package:blog_for_modals_app/features/auth/presentation/widgets/auth_field.dart';
import 'package:blog_for_modals_app/features/auth/presentation/widgets/auth_gradient_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SignUpPage extends StatefulWidget {
  static route() => MaterialPageRoute(
        //LoginPage den buraya gelirken Navigator.push diyerek yazarken kolaylık taktiği
        builder: (context) => const SignUpPage(),
      );

  const SignUpPage({super.key});

  @override
  State<SignUpPage> createState() => _SignUpPageState();
}

class _SignUpPageState extends State<SignUpPage> {
  final formKey = GlobalKey<FormState>(); //Form widgetı icin olusturulan GlobalKey
  final emailController = TextEditingController();
  final nameController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    nameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(15.0),
            child: BlocConsumer<AuthBloc, AuthState>(
              listener: (context, state) {
                if (state is AuthFailure) {
                  showSnackBar(context, state.message);
                }
              },
              builder: (context, state) {
                if (state is AuthLoading) {
                  return const Loader();
                }
                return Form(
                  key: formKey,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const Text(
                        'Sign Up.',
                        style: TextStyle(fontSize: 50, fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(
                        height: 30,
                      ),
                      AuthField(
                        hintText: 'Name',
                        controller: nameController,
                      ),
                      const SizedBox(
                        height: 15,
                      ),
                      AuthField(
                        hintText: 'Email',
                        controller: emailController,
                      ),
                      const SizedBox(
                        height: 15,
                      ),
                      AuthField(
                        hintText: 'Password',
                        controller: passwordController,
                        isObscruteText: true,
                      ),
                      const SizedBox(
                        height: 15,
                      ),
                      AuthGradientButton(
                        buttonText: 'Sign Up',
                        onPressed: () {
                          if (formKey.currentState!.validate()) {
                            debugPrint('deneme');
                            context.read<AuthBloc>().add(
                                  AuthSignUp(
                                    email: emailController.text.trim(),
                                    password: passwordController.text.trim(),
                                    name: nameController.text.trim(),
                                  ),
                                );
                          }
                        },
                      ),
                      const SizedBox(
                        height: 15,
                      ),
                      GestureDetector(
                        onTap: () {
                          Navigator.push(
                            context,
                            LoginPage.route(),
                          );
                        },
                        child: RichText(
                          text: TextSpan(
                            text: "Already have an account? ",
                            style: Theme.of(context).textTheme.titleMedium,
                            children: [
                              TextSpan(
                                  text: 'Sign In',
                                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                                        color: AppPallete.gradient2,
                                        fontWeight: FontWeight.bold,
                                      )),
                            ],
                          ),
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ));
  }
}

Domain katmanı altındaki entities klasörünüzdeki modeliniz böyle gözüküyor olabilir:

Kod:
class User {
 final String id;
  final String email;
  final String name;

  User({
    required this.id,
    required this.email,
    required this.name,
  });
}

Domain katmanındaki repositoryinizin kodları böyle olabilir:

Kod:
import 'package:blog_for_modals_app/core/error/failures.dart';
import 'package:blog_for_modals_app/features/auth/domain/entities/user.dart';
import 'package:fpdart/fpdart.dart';

//interface olusturuyoruz
abstract interface class AuthRepository {
  //fpdart packageını yükledik.
  Future<Either<Failure, User>> signUpWithEmailPassword({
    required String name,
    required String email,
    required String password,
  });

  Future<Either<Failure, User>> loginWithEmailPassword({
    required String email,
    required String password,
  });
}

Data katmanınızdaki datasources klasörünüzde auth için auth_remote_data_source'unuzun kodları böyle olabilir (firebase de kullanabilirsiniz auth için ama burada supabase kullandık)

Kod:
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
  final SupabaseClient supabaseClient;
  AuthRemoteDataSourceImpl(this.supabaseClient);

  @override
  Future<UserModel> loginWithEmailPassword({
    required String email,
    required String password,
  }) async {
    try {
      final response = await supabaseClient.auth.signInWithPassword(
        password: password,
        email: email,
      );
      if (response.user == null) {
        throw const ServerException('User is null!');
      }
      return UserModel.fromJson(response.user!.toJson());
    } catch (e) {
      throw ServerException(e.toString());
    }
  }

!!!GİZLİLİĞİ KALKMIŞ BİLGİLER!!!

Er8ey1gVEAESltV.jpg

Burada bloc yönetimi için AuthBloc diye bir dosya açtık.

Bu kodlara göre eventlerin ve statelerin ne olacağı oldukça basit ve anlaşılır halde.

Farklı bir state management yöntemine de gidebilirsiniz sonuç olarak konunun amacı clean architecture yaklaşımına uygun bir proje klasör yapısı çıkartmak ve kodları buna göre yazmak.



Bloc yönetiminiz bu şekilde olabilir:

Kod:
import 'package:blog_for_modals_app/features/auth/domain/entities/user.dart';
import 'package:blog_for_modals_app/features/auth/domain/usecases/user_login.dart';
import 'package:blog_for_modals_app/features/auth/domain/usecases/user_sign_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'auth_event.dart';
part 'auth_state.dart';

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final UserSignUp _userSignUp;
  final UserLogin _userLogin;

  AuthBloc({
    required UserSignUp userSignUp,
    required UserLogin userLogin,
  })  : _userSignUp = userSignUp,
        _userLogin = userLogin,
        super(AuthInitial()) {
    on<AuthSignUp>(_onAuthSignUp);
    on<AuthLogin>(_onAuthLogin);
  }

  void _onAuthSignUp(AuthSignUp event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    final res = await _userSignUp(
      UserSignUpParams(
        email: event.email,
        password: event.password,
        name: event.name,
      ),
    );
    res.fold(
      (failure) => emit(
        AuthFailure(failure.message),
      ),
      (user) => emit(
        AuthSuccess(user),
      ),
    );
  }

  void _onAuthLogin(AuthLogin event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    final res = await _userLogin(
      UserLoginParams(event.email, event.password),
    );

    res.fold(
      (l) => emit(AuthFailure(l.message)),
      (r) => emit(
        AuthSuccess(r),
      ),
    );
  }
}

ben anlamıyorum hiçbir şey ne oluyor?

0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #1
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #2
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #3
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #4
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #5
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #6
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #7
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #8
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #9
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #10
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #11
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #12
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #13
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #14
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #15
Flutter & Dart Fundamentals | Örnek Quiz Uygulaması #16
Flutter uygulamalarında debugging mantığı #17
Flutter & Dart widget tree, element tree, render tree #18

Bloc ile state yönetimine giriş, Cubit mantığı #19
 

Gauloran

Global Moderatör
7 Tem 2013
8,124
610
local
tşkler yorumlariniz icin. daha net anlasilmasi icin ufak bir cizerek anlattim konuyu okuyanlar sonrasinda inceleyebilir

CgR8LbtJIFk.png
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.