Baştan sona Twitter'ı yazalım #2 (Flutter, Riverpod, Fpdart, Appwrite)

Gauloran

Moderasyon Ekibi Lideri
7 Tem 2013
8,203
4
678
Merhaba en son twitterı yazmak için ilk konuda backend tarafında appwrite'dan ve genel konfigürasyondan bahsetmiştim. Şimdi yazma tarafına geçebiliriz.

fe5OM8.png
fe5OM8.png
fe5Pe1.png


Öncelikle tahmin edeceğiniz üzere kullanıcı girişi ve üyelik olayını halletmemiz gerekli. API dosyamızı yazmadan önce basitçe tasarım kısmını halledelim. Projeyi yazarken feature first yaklaşımını tercih ettiğimizden features klasörü altında auth klasörünü açıyoruz Türkçe yazmak istiyorsanız giris veya kullanicigirisleri tarzı isimler de projeyi takip etmeniz açısından kolay olabilir. Açtığınız bu klasöre controller, widgets ve view olmak üzere 3 klasör daha açın.

controller kısmında bir auth_controller.dart dosyası oluşturun veya siz verdiğiniz isme göre değiştirin. aynı şekilde view klasörü içerisinde de tasarımla ilgili şeyler yapacağımız için login_view.dart ve signup_view.dart dosyalarını oluşturun. Dosyaları istediğiniz gibi adlandırabilirsiniz fakat random isimler vermeyin sonradan buralar karışır.

login_view.dart kısmında kullanıcının giriş yapacağı ekranı yazalım.

Kod:
class LoginView extends ConsumerStatefulWidget {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  static route() => MaterialPageRoute(
        builder: (context) => const LoginView(),
      );
  const LoginView({super.key});

  @override
  ConsumerState<LoginView> createState() => _LoginViewState();
}


burada flutter riverpod package'ından yararlandığımız için ConsumerStatefulWidget oluşturuyoruz ve daha sonra LoginView.route() diyerek bir MaterialPageRoute döndürebilmek için daha doğrusu navigasyon işlemlerini kısaltacak bir hamle yapıyoruz bunun kullanımını signup sayfasından loginpage'e geçişte kullanacağımız zaman anlayacaksınız. Devamında da constructor vermişiz. createState metodu var ardından bunlar zaten statefulconsumer yazarsanız vscode üzerinde otomatik olarak oluşturulacak kodlar yalnızca ismini LoginView vereceksiniz ya da istediğiniz giriş sayfasının ismi neyse.

Kod:
class _LoginViewState extends ConsumerState<LoginView> {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  final appbar =
      UIConstants.appBar(); //! OPTIMIZATION.FOR OTHERWISE EVERY TIME BUILD FUNC. IS CALLED APPBAR WILL BE CREATED AGAIN

  final emailController = TextEditingController();
  final passwordController = TextEditingController();

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


Bu kısımda ise öncelikle her yerde ayrı ayrı appbar tanımlamak yerine UIConstants.appBar() diyerek bir dosyadan appbar çekmişiz. Yani yaptığımız şey lib klasörü altında core klasörü içerisinde constants klasörü içerisindeki ui_constants.dart dosyasına erişmek. (Siz klasörleri bu şekilde oluşturmayabilir kafanıza göre yapıp isimlendirebilirsiniz ama böyle yapmanızı tavsiye ederim çünkü sabitlerle ilgili şeyler bu klasöre falan gibisinden). Üstte 2 tane controller oluşturmuşuz bunlar TextFieldlar için yaptığımız şeyler daha doğrusu biz kendimiz ayrı bir widget yazıp ona AuthField diyeceğiz ama aynı mantık. Dispose etmeyi de unutmuyoruz.

ui_constants.dart dosyasını oluşturun burada appbar'ı bir kodlayalım

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]import 'package:flutter_svg/flutter_svg.dart';
import 'package:kooginapp/features/explore/view/explore_view.dart';
import 'package:kooginapp/features/notifications/views/notification_view.dart';
import 'package:kooginapp/features/posts/widgets/post_list.dart';
import '../theme/theme.dart';
import 'constants.dart';

class UIConstants {

  static AppBar appBar() {
    return AppBar(
      title: SvgPicture.asset(
        AssetsConstants.kooginLogo,
        colorFilter: const ColorFilter.mode(Pallete.greyColor, BlendMode.srcIn),
        height: 35,
      ),
      centerTitle: true,
    );
  }

  static const List<Widget> bottomTabBarPages = [
    PostList(),
    ExploreView(),
    NotificationView(),
  ];
}


burada static olarak oluşturuyoruz ki direkt olarak erişebilelim biraz üstte erişimimiz bu sayede. AppBar döndüren bir appBar() oluşturuyoruz ve projenizde assets klasörü açın içerisine logo olarak kullanmak istediğiniz ki bu x logosu olacak çünkü twitter clone'u yazıyoruz onu koyun .svg formatında olsun. yani yapacağınız işlem assets klasörü içerisine verdiğiniz isimle x logosunu svg formatında koymak. Daha sonra kullanırken AssetsConstants.sectiginizisim şeklinde burada SvgPicture.asset metodunu kullanarak yani svg package ı kullanarak svg resimlerini assetimizden gösterebiliyoruz amacımız appbarın title kısmında centerTitle'ı true yaparak (title'ı ortalayarak) x logosunu appbar'ın ortasında göstermek.

aynı şekilde constants klasörünüzün içerisine asset_constants.dart diyerek static olarak 3-5 resim yolu yapabilirsiniz her seferinde path'i yazmak yerine oradan çekersiniz.

Kod:
//! THIS IS FOR OUR SVG FILES PATHS[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]class AssetsConstants {
  static const String _svgsPath = 'assets/svgs';
  static const String kooginLogo = '$_svgsPath/koogin.svg';
  static const String homeFilledIcon = '$_svgsPath/home_filled.svg';
  static const String homeOutlinedIcon = '$_svgsPath/home_outlined.svg';
  static const String notifFilledIcon = '$_svgsPath/notif_filled.svg';
  static const String notifOutlinedIcon = '$_svgsPath/notif_outlined.svg';
  static const String searchIcon = '$_svgsPath/search.svg';
  static const String gifIcon = '$_svgsPath/gif.svg';
  static const String emojiIcon = '$_svgsPath/emoji.svg';
  static const String galleryIcon = '$_svgsPath/gallery.svg';
  static const String commentIcon = '$_svgsPath/comment.svg';
  static const String retweetIcon = '$_svgsPath/retweet.svg';
  static const String likeOutlinedIcon = '$_svgsPath/like_outlined.svg';
  static const String likeFilledIcon = '$_svgsPath/like_filled.svg';
  static const String viewsIcon = '$_svgsPath/views.svg';
  static const String verifiedIcon = '$_svgsPath/verified.svg';
  static const String noProfilePicture = 'https://static.vecteezy.com/system/resources/thumbnails/002/318/271/small_2x/user-profile-icon-free-vector.jpg';
}


ben bu şekilde yaptım.

ui_constants.dart dosyasına dönecek olursak orada bir de alt kısımda bir tabbar yapacağımız için orada kullanacağımız sayfalar için yazdığımız Widgetlar içeren bir liste. Bunu şimdilik yazmayın isterseniz zamanı geldiğinde buradan bakıp yazarsınız devam edelim.

login_view.dart dosyasını yazmaya devam edecek olursak:

Kod:
void onLogin() {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]    // ignore: unused_local_variable
    final res = ref
        .read(authControllerProvider.notifier)
        .login(email: emailController.text, password: passwordController.text, context: context);
  }

  @override
  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider);
    final appThemeState = ref.watch(appThemeStateNotifier);


burada onLogin fonksiyonu oluşturuyoruz bunu şimdilik yapmanıza gerek yok zamanı gelince gelip bakabilirsiniz mantığımız ise şu API'mızı yazdıktan sonra controllerımızı da yazdıktan sonra artık UI tarafında controller sayesinde giriş isteği yaptırabilmek için riverpod solution sayesinde kullandığımız metod. ref.read veya ref.watch şeklinde kullanılıyor yani anlayacağınız şey login yaptırmayı çağırma kodu olarak düşünün.


devam edelim

Kod:
 @override[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider);
    final appThemeState = ref.watch(appThemeStateNotifier);
    return Scaffold(
        appBar: appbar,
        body: isLoading
            ? const Loader()


burada isLoading ve appThemeState diye iki şeyle karşılaşacaksınız. appThemeState ilerde karanlık ve aydınlık tema için :D onu da eksik etmedik twitter cloneumuzu aydınlık ve karanlık tema olarak çift tema seçeneği yapıyoruz. isLoading ise authControllerProvider'ı izliyor oraları daha oluşturmadığımız için şimdi kafaya takmayın sadece inceleyin zamanı gelince buradan bakıp yazarsınız. Devam edin. Burada amacıımız eğer isLoading yani yüklenme olayı varsa Loader (circularprogressbarindicator) tarzında bir şey göstermek

Kod:
 ? const Loader()[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]            : Center(
                child: SingleChildScrollView(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: Column(
                      children: [
                        AuthField(
                          controller: emailController,
                          hintText: AppTexts.email,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AuthField(
                          controller: passwordController,
                          hintText: AppTexts.password,
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        Align(
                          alignment: Alignment.topRight,
                          child: RoundedSmallButton(
                            onTap: onLogin,
                            label: AppTexts.done,
                          ),
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        RichText(
                          text: TextSpan(
                            text: AppTexts.dontHaveAc,
                            style: TextStyle(fontSize: 16,
                            
                            color: appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blackColor,
                            ),
                            children: [
                              TextSpan(
                                text: AppTexts.signUp,
                                style: const TextStyle(
                                  color: Pallete.blueColor,
                                  fontSize: 16,
                                ),
                                recognizer: TapGestureRecognizer()
                                  ..onTap = () {
                                    Navigator.pushReplacement(
                                      context,
                                      SignUpView.route(),
                                    );
                                  },
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ));
  }
}


Burada appThemeState'e erişip eğer karanlık temaysa şu rengi ver değilse şu rengi ver gibisinden mantık yapıyoruz onu şimdilik zaten kullanmayacaksınız. Bir iki tane sizedbox widgetı koyuyoruz 2 tane de TextField benzeri yapacağımız kendi AuthField'ımızı veriyoruz zamanı geldiğinde gelip eklersiniz şimdilik sizedbox'ları atın veya hatta direkt halledin onu şu şekilde

direkt widgets klasörüne gidin auth_field.dart diye dosyanızı oluşturun isim olarak farklı bir şey vermek istiyorsanız giris_kutucugu tarzı bir şey verebilirsiniz

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]import 'package:kooginapp/core/theme/pallete.dart';

class AuthField extends StatelessWidget {
  final TextEditingController controller;
  final String hintText;
  const AuthField({super.key, required this.controller, required this.hintText});

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      decoration: InputDecoration(
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(5),
          borderSide: const BorderSide(
            color: Pallete.blueColor,
            width: 4,
          ),
        ),
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(5),
          borderSide: const BorderSide(
            color: Pallete.greyColor,
          ),
        ),
        contentPadding: const EdgeInsets.all(22),
        hintText: hintText,
        hintStyle: const TextStyle(
          fontSize: 18,
          color: Pallete.greyColor,
        )
      ),
    );
  }
}


burada yaptığımız şey stless bir widget oluşturup TextFormField döndürmek ve bu textformfield'ın controller'ını class çağrıldığında istemek ve oradan çekmek ayrıca hintText'i de aynı şekilde bu isimlendirmeleri tabii değiştirebilirsiniz. bunları named required olarak veriyoruz. Ardından biraz decoration eklemişiz borderside atayıp azıcık da contentPadding vermişiz bu widget bu kadar.

login_view.dart'a dönecek olursak burada bir RichText içerisinde TextSpan kullandık o da hesabın yok mu ? hesap oluştur tarzında bir yazı göstermek istiyoruz ama renkler farklı. O nedenle textspan tercih ettik. textleri direkt olarak "yazı yazı" şeklinde vermek yerine app_texts.dart diye bir dosya oluşturup static olarak oraya tanımlayıp oradan çekiyoruz.

En sonunda signupview'a geçiş için kullandığımız aslında size loginview içerisinde gösterdiğim şu static route olayını kullanıyoruz. Sayfa geçişinin de kodunu yazmış olduk. Şimdi signupview'ı yazmamız gerek yani kullanıcının kayıt olacağı sayfayı

f0LfYq.png


şu anda aldığımız login view tasarım sayfası bu şekilde. Benzer işlemleri signup için de yapacağız. Kodlar neredeyse aynı olacak

Kod:
import 'package:flutter/gestures.dart';[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kooginapp/core/common/loading_page.dart';
import 'package:kooginapp/core/constants/app_texts.dart';
import 'package:kooginapp/core/providers/providers.dart';
import 'package:kooginapp/features/auth/controller/auth_controller.dart';
import 'package:kooginapp/features/auth/view/login_view.dart';
import 'package:kooginapp/features/auth/widgets/age_field.dart';
import 'package:kooginapp/features/auth/widgets/auth_field.dart';

import '../../../core/common/common.dart';
import '../../../core/constants/constants.dart';
import '../../../core/theme/theme.dart';

class SignUpView extends ConsumerStatefulWidget {
  static route() => MaterialPageRoute(
        builder: (context) => const SignUpView(),
      );
  const SignUpView({super.key});

  @override
  ConsumerState<SignUpView> createState() => _SignUpViewState();
}

class _SignUpViewState extends ConsumerState<SignUpView> {
  final appbar = UIConstants.appBar();

  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final ageController = TextEditingController();

  String selectedCountry = 'United States';

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

  void onSignUp() {
    // ignore: unused_local_variable
    final res = ref.read(authControllerProvider.notifier).signUp(
          email: emailController.text,
          password: passwordController.text,
          age: ageController.text,
          country: selectedCountry,
          context: context,
        );
  }

  @override
  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider); //! we are watching authControllerProvider
    final appThemeState = ref.watch(appThemeStateNotifier);

    return Scaffold(
        appBar: appbar,
        body: isLoading
            ? const Loader()
            : Center(
                child: SingleChildScrollView(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: Column(
                      children: [
                        AuthField(
                          controller: emailController,
                          hintText: AppTexts.email,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AuthField(
                          controller: passwordController,
                          hintText: AppTexts.password,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AgeField(controller: ageController, hintText: AppTexts.age),
                        const SizedBox(
                          height: 40,
                        ),
                        DropdownButton<String>(
                          value: selectedCountry,
                          items: AppTexts.countries
                              .map<DropdownMenuItem<String>>(
                                (String e) => DropdownMenuItem<String>(
                                  value: e,
                                  child: Text(e, style: const TextStyle(fontSize: 16)),
                                ),
                              )
                              .toList(),
                          onChanged: (String? newValue) {
                            setState(() {
                              selectedCountry = newValue!;
                            });
                          },
                        ),
                         const SizedBox(
                          height: 40,
                        ),
                        Align(
                          alignment: Alignment.topRight,
                          child: RoundedSmallButton(
                            onTap: onSignUp,
                            label: AppTexts.done,
                          ),
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        RichText(
                          text: TextSpan(
                            text: AppTexts.alreadyAcc,
                            style: TextStyle(
                              fontSize: 16,
                              color: appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blackColor,
                            ),
                            children: [
                              TextSpan(
                                text: AppTexts.login,
                                style: const TextStyle(
                                  color: Pallete.blueColor,
                                  fontSize: 16,
                                ),
                                recognizer: TapGestureRecognizer()
                                  ..onTap = () {
                                    Navigator.pushReplacement(
                                      context,
                                      LoginView.route(),
                                    );
                                  },
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ));
  }
}


Burada yaptığımız değişiklik fonksiyon olarak onSignUp fonksiyonunu yazıyoruz controller ve API tarafını hallettiğimizde kullanacağımız şeyi göreceksiniz şimdilik buraları yazmanıza gerek yok tasarım kısmını halledin sadece. devamında yine isLoading ve appThemeState kullanmışız yine aynı kodlar yalnız ben ek olarak kullanıcılar üye olurken yaşını girsin diye oluşturduğum AgeField widgetı da (AuthField'a çok benziyor) bu widgetı ekledim. Klasik birkaç tane sizedbox eklenmiş aslında böyle sizedboxları direkt cayır cayır verip sayı girmek canımı sıkar kartal package'ını kullanmak daha mantıklı olurdu ama devam edelim nasıl olsa projeyi bu haliyle yayınlamayacağım ben yayınlayacaksanız teknik açıdan temel şeyleri vereceğim siz kendiniz düşünürsünüz bazı kısımları :D

Bir de kullanıcıların üye olurken ülke seçmesi için dropdownbutton yazmıştım yine constants klasöründe belirlediğimiz apptexts içerisindeki ülkeleri oraya liste şeklinde yazıp burada mapleyerek tek tek dropdownmenuitem gösterimi yapıyoruz. değiştiğinde de seçilen ülkeyi yani selectedCountry değerini seçilen değere atayıp setState atarsanız kolayca ülke seçildikten sonra değişen değerin aktarılmasını sağlayabilirsiniz. Aslında setState kullanmayı da pek sevmem ama :D
Geriye kalan kodlar loginview UI için yazdığımız kodlarla aynı fakat navigasyon yönü farklı çünkü zaten hesabınız varsa loginview.route'ına gitmeniz gerekir kodlar aynı sadece yazılacak yer sizinle konu başında önceden loginview için yazdığımız static route metodunu kullanıyor olmamız.

Böylece kayıt ekranını da bitirmiş oluyoruz.

f0LoPi.png


Backend tarafına gelecek olursak öncelikle Appwrite içerisinde bir db oluşturmalıyız. Çünkü kullanıcıları kayıt ettiğimizde auth kullanıcıları oluşturacağız fakat db oluşturup users veya kafanıza göre kullanicilar koleksiyonu oluşturup kullanıcıların bilgilerini kaydetmemiz gerekecek. Bundan dolayı öncelikle Appwrite'ınızda bazı ayarlamalar yapmanız gerekir. İlk konuda anlattığım şeyleri yapanlar docker'ı çalıştırdıktan sonra appwrite'ı da runladığınızda localhost yazarak tarayıcıdan appwrite'a girip uygulamanızın ismini girebilirsiniz ardından database kısmına gelip bir db oluşturun ismi fark etmez içerisine de users adllı bir koleksiyon oluşturun create collection diyerek.

f0LVDQ.png


Bu appwrite içerisinde users koleksiyonu için tanımlamanız gereken attributelar çünkü model sınıfımızı oluşturduğumuzda bu bize lazım olacak. Devam edelim.

Artık backendi ilgilendiren kodları yazmanın zamanı geldi aslında UI tarafına dalmadan belli başlı providerları belirlemek gerekiyordu şimdi lib içerisinde providers diye bir klasör açın ve bu klasör içerisinde providers.dart dosyasını oluşturun.

Kod:
final appwriteClientProvider = Provider((ref) {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  Client client = Client();
  return client
      .setEndpoint(AppwriteConstants.endPoint)
      .setProject(AppwriteConstants.projectId)
      .setSelfSigned(status: true);
});


Burada appwrite bağlantısını yapmak için Client oluşturuyoruz ve appwrite tarafında endPoint olarak ve projectId olarak tanımladığımız gerekli değerleri giriyoruz bunun için appwrite constants diye bir dart dosyası oluşturup o dosya içerisinde Appwrite ile ilgili bilgileri içeren şeyleri kullanıyoruz.

Kod:
class AppwriteConstants {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  static const String databaseId = 'dbidiniz';
  static const String projectId = 'projeidiniz';
  static const String endPoint = 'IPADRESINIZ/v1';
  static const String usersCollection = 'userskoleksiyonuidsi';
  static const String postCollection = 'postkoleksiyonuidsi';
  static const String imagesBucket = 'resimlericinbucketid';
  static const String kooginstreamdb = 'realtimeicindbid';
  static const String notificationsCollection = 'notificationkoleksiyonuicinid';
  static const String messagesCollection = 'mesajlaricinid';

  static String imageUrl(String imageId) {
    return '$endPoint/storage/buckets/$imagesBucket/files/$imageId/view?project=$projectId&mode=admin';
  }
}


Burada appwrite constats içerisinde gerekli bilgileri "" içerisine neleri kullanacağınızı verdim o kısımları değiştirerek yapabilirsiniz.

provider kısmına devam edecek olursak

Kod:
final appwriteAccountProvider = Provider([/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  (ref) {
    final client = ref.watch(appwriteClientProvider);
    return Account(client);
  },
);


burada da appwrite.dart'tan gelen Account için bir provider oluşturuyoruz provider oluşturmaktaki amaç bunları sistematik bir şekilde kullanırken her yerden erişebiliyoruz.

Kod:
final appwriteDatabaseProvider = Provider((ref) {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Databases(client);
});


db için bir provider aynı şekilde.

Kod:
final appWriteStorageProvider = Provider((ref) {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Storage(client);
});


kullanıcılar profil ve bannerlarını değiştirdiklerinde storage'a kayıt olacağı için appwrite tarafında storage için bu providerımızı da oluşturuyoruz.

Kod:
final appWriteRealTimeProvider = Provider((ref) {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Realtime(client);
});


bu providerımızı realtime için oluşturuyoruz yine Appwrite.dart'dan gelen package sayesinde

Kod:
final appThemeStateNotifier = ChangeNotifierProvider((ref) {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  return AppThemeState();
});


bu da tema için bir ChangeNotifierProvider AppThemeState'e erişmemize yarayacak.

Providerlarımızı oluşturduğumuza göre API ve Controller tarafını yazmaya başlayabiliriz. Auth feature'ına odaklanıyoruz şu anda şimdilik yazmamız gereken auth_api.dart dosyası oluşturuyoruz lib içerisine apis diye bir klasör açıp içerisine auth_api.dart veya istediğiniz isimle dosyayı oluşturun.

Öncelikle özelliklerimiz için yazacağımız backend related kodu rahatlatmak adına soyut sınıf oluşturmamız gerekli daha sonra eklemeler yaparken kafamız karışmaz.

Kod:
abstract class IAuthAPI {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  FutureEither<model.User> signUp({required String email, required String password});
  FutureEither<model.Session> login({required String email, required String password});
  Future<model.User?> currentUserAccount();
  FutureEitherVoid logout();
}


burada signup login ve currentUserAccount metodlarını oluşturuyoruz. Soyut sınıfımızı oluşturduktan sonra yapacağımız işlem aslında asıl class'ı yazarken kullanacağımız metodları implemente edebilmek ve kolaylık olması. Burada model.User? appwrite tarafından gelen bir şey bunun ismi önceden Account'tu sonradan değiştirdiler.
import 'package:appwrite/models.dart' as model;

import ederken bu şekilde import ederseniz kullanımı daha kolay olur ve karışıklık olmaz. FutureEither diye bir tür yok dartta ama bu typedef ile tanımladığımız bizim türümüz.

Kod:
typedef FutureEither<T> = Future<Either<Failure, T>>;[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]typedef FutureEitherVoid = FutureEither<void>;


projenizin lib içerisinde utils klasörü içerisinde bir type_defs.dart veya verdiğiniz bir isimle dosyayı oluşturup yukarıdaki gibi tanımlamalar yapabilirsiniz. Amaç kod yazarken çok fazla fpdart package'ından Future<Either<Failure, T>> yapısını kullanacağımız için direkt FutureEither diyerek kolaylık sağlamak.

şimdi auth_api.dart'a dönecek olursak

Kod:
class AuthAPI implements IAuthAPI {[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  final Account _account;

  AuthAPI({required Account account}) : _account = account;

  @override
  Future<model.User?> currentUserAccount() async {
    try {
      return await _account.get();
    } on AppwriteException catch (_) {
      return null;
    } catch (_) {
      return null;
    }
  }


Burada artık AuthAPI'mızı yazıyoruz gereksinim olarak appwrite'dan gelen Account sınıfından _account tanımladık. Ve ilk iş yapan fonksiyonumuzu yazmaya başlıyoruz. Bu fonksiyon bize Future olarak yani bir süre sonra model.User? döndürecek ve adı currentUserAccount yani ismi istediğiniz gibi değiştirin şu anki kullanıcıya erişmek için yazdığımız bir fonksiyon. try catch yapısı var fonksiyon asenkron olacak bize döndüreceği şey _account.get(); yani gereksinim olarak aldığımız appwrite packageından gelen account nesnesinin türlü türlü metodları var ve bu get metodu bize şu anki giriş yapılmış kullanıcıyı döndürüyor. Biz de onu döndürüyoruz. bu fonksiyon bu kadar şimdi diğerine gelelim

Kod:
@override[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  FutureEither<model.User> signUp({required String email, required String password}) async {
    try {
      final account = await _account.create(userId: ID.unique(), email: email, password: password);
      return right(account);
    } on AppwriteException catch (e, stackTrace) {
      return left(Failure(e.message ?? 'Some unexpected error occurred', stackTrace));
    } catch (e, stackTrace) {
      return left(Failure(e.toString(), stackTrace));
    }
  }


bu fonksiyon ise signUp fonksiyonumuz kullanıcıların üye olmasını sağlayacak. fpdart package'ı kullanarak typedeflediğimiz model.User döndürecek ve parametre olarak email ve password istiyor sadece. yine try catch bloğu oluşturup _account.create metodunu kullanıyoruz userId olarak kendine özgü bir id oluşturması için ID.unique() email olarak gelen parametredeki email ve şifre için de aynı şeyi yapıyoruz. Eğer olur da bir appwriteexceptionu gerçekleşirse bunu yakalayıp fpdart packageından yararlanarak left döndürüyoruz yani başarısız işlem demek. İlgili hata mesajını yazdırıyoruz.

Kod:
  @override[/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER]  FutureEither<model.Session> login({required String email, required String password}) async {
    try {
      final session = await _account.createEmailPasswordSession(email: email, password: password);
      return right(session);
    } on AppwriteException catch (e, stackTrace) {
      return left(Failure(e.message ?? 'Some unexpected error occurred', stackTrace));
    } catch (e, stackTrace) {
      return left(Failure(e.toString(), stackTrace));
    }
  }


benzer işlemi login fonksiyonu oluştururken de yapıyoruz. Bu sefer createEmailPasswordSession fonksiyonunu kullanıyoruz. auth_api.dart ile aslında bir fonksiyon haricinde işimiz bitiyor. Şimdi yapmamız gereken auth_controller.dart dosyasını oluşturmak. Bunu da bir sonraki konuda yapalım böylece auth_controller.dart'ı da yazdıktan sonra kullanıcılar başarıyla üye olup uygulamaya başarıyla giriş yapabilir hale gelmiş olacak ve diğer özelliklere odaklanacağız.

Bir sonraki konuda görüşelim. <3 Gauloran

 

Privarp

Anka Team Junior
8 Nis 2022
121
39
Merhaba en son twitterı yazmak için ilk konuda backend tarafında appwrite'dan ve genel konfigürasyondan bahsetmiştim. Şimdi yazma tarafına geçebiliriz.

fe5OM8.png
fe5OM8.png
fe5Pe1.png


Öncelikle tahmin edeceğiniz üzere kullanıcı girişi ve üyelik olayını halletmemiz gerekli. API dosyamızı yazmadan önce basitçe tasarım kısmını halledelim. Projeyi yazarken feature first yaklaşımını tercih ettiğimizden features klasörü altında auth klasörünü açıyoruz Türkçe yazmak istiyorsanız giris veya kullanicigirisleri tarzı isimler de projeyi takip etmeniz açısından kolay olabilir. Açtığınız bu klasöre controller, widgets ve view olmak üzere 3 klasör daha açın.

controller kısmında bir auth_controller.dart dosyası oluşturun veya siz verdiğiniz isme göre değiştirin. aynı şekilde view klasörü içerisinde de tasarımla ilgili şeyler yapacağımız için login_view.dart ve signup_view.dart dosyalarını oluşturun. Dosyaları istediğiniz gibi adlandırabilirsiniz fakat random isimler vermeyin sonradan buralar karışır.

login_view.dart kısmında kullanıcının giriş yapacağı ekranı yazalım.

Kod:
class LoginView extends ConsumerStatefulWidget {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  static route() => MaterialPageRoute(
        builder: (context) => const LoginView(),
      );
  const LoginView({super.key});

  @override
  ConsumerState<LoginView> createState() => _LoginViewState();
}



burada flutter riverpod package'ından yararlandığımız için ConsumerStatefulWidget oluşturuyoruz ve daha sonra LoginView.route() diyerek bir MaterialPageRoute döndürebilmek için daha doğrusu navigasyon işlemlerini kısaltacak bir hamle yapıyoruz bunun kullanımını signup sayfasından loginpage'e geçişte kullanacağımız zaman anlayacaksınız. Devamında da constructor vermişiz. createState metodu var ardından bunlar zaten statefulconsumer yazarsanız vscode üzerinde otomatik olarak oluşturulacak kodlar yalnızca ismini LoginView vereceksiniz ya da istediğiniz giriş sayfasının ismi neyse.

Kod:
class _LoginViewState extends ConsumerState<LoginView> {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  final appbar =
      UIConstants.appBar(); //! OPTIMIZATION.FOR OTHERWISE EVERY TIME BUILD FUNC. IS CALLED APPBAR WILL BE CREATED AGAIN

  final emailController = TextEditingController();
  final passwordController = TextEditingController();

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



Bu kısımda ise öncelikle her yerde ayrı ayrı appbar tanımlamak yerine UIConstants.appBar() diyerek bir dosyadan appbar çekmişiz. Yani yaptığımız şey lib klasörü altında core klasörü içerisinde constants klasörü içerisindeki ui_constants.dart dosyasına erişmek. (Siz klasörleri bu şekilde oluşturmayabilir kafanıza göre yapıp isimlendirebilirsiniz ama böyle yapmanızı tavsiye ederim çünkü sabitlerle ilgili şeyler bu klasöre falan gibisinden). Üstte 2 tane controller oluşturmuşuz bunlar TextFieldlar için yaptığımız şeyler daha doğrusu biz kendimiz ayrı bir widget yazıp ona AuthField diyeceğiz ama aynı mantık. Dispose etmeyi de unutmuyoruz.

ui_constants.dart dosyasını oluşturun burada appbar'ı bir kodlayalım

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]import 'package:flutter_svg/flutter_svg.dart';
import 'package:kooginapp/features/explore/view/explore_view.dart';
import 'package:kooginapp/features/notifications/views/notification_view.dart';
import 'package:kooginapp/features/posts/widgets/post_list.dart';
import '../theme/theme.dart';
import 'constants.dart';

class UIConstants {

  static AppBar appBar() {
    return AppBar(
      title: SvgPicture.asset(
        AssetsConstants.kooginLogo,
        colorFilter: const ColorFilter.mode(Pallete.greyColor, BlendMode.srcIn),
        height: 35,
      ),
      centerTitle: true,
    );
  }

  static const List<Widget> bottomTabBarPages = [
    PostList(),
    ExploreView(),
    NotificationView(),
  ];
}



burada static olarak oluşturuyoruz ki direkt olarak erişebilelim biraz üstte erişimimiz bu sayede. AppBar döndüren bir appBar() oluşturuyoruz ve projenizde assets klasörü açın içerisine logo olarak kullanmak istediğiniz ki bu x logosu olacak çünkü twitter clone'u yazıyoruz onu koyun .svg formatında olsun. yani yapacağınız işlem assets klasörü içerisine verdiğiniz isimle x logosunu svg formatında koymak. Daha sonra kullanırken AssetsConstants.sectiginizisim şeklinde burada SvgPicture.asset metodunu kullanarak yani svg package ı kullanarak svg resimlerini assetimizden gösterebiliyoruz amacımız appbarın title kısmında centerTitle'ı true yaparak (title'ı ortalayarak) x logosunu appbar'ın ortasında göstermek.

aynı şekilde constants klasörünüzün içerisine asset_constants.dart diyerek static olarak 3-5 resim yolu yapabilirsiniz her seferinde path'i yazmak yerine oradan çekersiniz.

Kod:
//! THIS IS FOR OUR SVG FILES PATHS[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]class AssetsConstants {
  static const String _svgsPath = 'assets/svgs';
  static const String kooginLogo = '$_svgsPath/koogin.svg';
  static const String homeFilledIcon = '$_svgsPath/home_filled.svg';
  static const String homeOutlinedIcon = '$_svgsPath/home_outlined.svg';
  static const String notifFilledIcon = '$_svgsPath/notif_filled.svg';
  static const String notifOutlinedIcon = '$_svgsPath/notif_outlined.svg';
  static const String searchIcon = '$_svgsPath/search.svg';
  static const String gifIcon = '$_svgsPath/gif.svg';
  static const String emojiIcon = '$_svgsPath/emoji.svg';
  static const String galleryIcon = '$_svgsPath/gallery.svg';
  static const String commentIcon = '$_svgsPath/comment.svg';
  static const String retweetIcon = '$_svgsPath/retweet.svg';
  static const String likeOutlinedIcon = '$_svgsPath/like_outlined.svg';
  static const String likeFilledIcon = '$_svgsPath/like_filled.svg';
  static const String viewsIcon = '$_svgsPath/views.svg';
  static const String verifiedIcon = '$_svgsPath/verified.svg';
  static const String noProfilePicture = 'https://static.vecteezy.com/system/resources/thumbnails/002/318/271/small_2x/user-profile-icon-free-vector.jpg';
}



ben bu şekilde yaptım.

ui_constants.dart dosyasına dönecek olursak orada bir de alt kısımda bir tabbar yapacağımız için orada kullanacağımız sayfalar için yazdığımız Widgetlar içeren bir liste. Bunu şimdilik yazmayın isterseniz zamanı geldiğinde buradan bakıp yazarsınız devam edelim.

login_view.dart dosyasını yazmaya devam edecek olursak:

Kod:
void onLogin() {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]    // ignore: unused_local_variable
    final res = ref
        .read(authControllerProvider.notifier)
        .login(email: emailController.text, password: passwordController.text, context: context);
  }

  @override
  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider);
    final appThemeState = ref.watch(appThemeStateNotifier);



burada onLogin fonksiyonu oluşturuyoruz bunu şimdilik yapmanıza gerek yok zamanı gelince gelip bakabilirsiniz mantığımız ise şu API'mızı yazdıktan sonra controllerımızı da yazdıktan sonra artık UI tarafında controller sayesinde giriş isteği yaptırabilmek için riverpod solution sayesinde kullandığımız metod. ref.read veya ref.watch şeklinde kullanılıyor yani anlayacağınız şey login yaptırmayı çağırma kodu olarak düşünün.


devam edelim

Kod:
 @override[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider);
    final appThemeState = ref.watch(appThemeStateNotifier);
    return Scaffold(
        appBar: appbar,
        body: isLoading
            ? const Loader()



burada isLoading ve appThemeState diye iki şeyle karşılaşacaksınız. appThemeState ilerde karanlık ve aydınlık tema için :D onu da eksik etmedik twitter cloneumuzu aydınlık ve karanlık tema olarak çift tema seçeneği yapıyoruz. isLoading ise authControllerProvider'ı izliyor oraları daha oluşturmadığımız için şimdi kafaya takmayın sadece inceleyin zamanı gelince buradan bakıp yazarsınız. Devam edin. Burada amacıımız eğer isLoading yani yüklenme olayı varsa Loader (circularprogressbarindicator) tarzında bir şey göstermek

Kod:
 ? const Loader()[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]            : Center(
                child: SingleChildScrollView(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: Column(
                      children: [
                        AuthField(
                          controller: emailController,
                          hintText: AppTexts.email,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AuthField(
                          controller: passwordController,
                          hintText: AppTexts.password,
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        Align(
                          alignment: Alignment.topRight,
                          child: RoundedSmallButton(
                            onTap: onLogin,
                            label: AppTexts.done,
                          ),
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        RichText(
                          text: TextSpan(
                            text: AppTexts.dontHaveAc,
                            style: TextStyle(fontSize: 16,
                            
                            color: appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blackColor,
                            ),
                            children: [
                              TextSpan(
                                text: AppTexts.signUp,
                                style: const TextStyle(
                                  color: Pallete.blueColor,
                                  fontSize: 16,
                                ),
                                recognizer: TapGestureRecognizer()
                                  ..onTap = () {
                                    Navigator.pushReplacement(
                                      context,
                                      SignUpView.route(),
                                    );
                                  },
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ));
  }
}



Burada appThemeState'e erişip eğer karanlık temaysa şu rengi ver değilse şu rengi ver gibisinden mantık yapıyoruz onu şimdilik zaten kullanmayacaksınız. Bir iki tane sizedbox widgetı koyuyoruz 2 tane de TextField benzeri yapacağımız kendi AuthField'ımızı veriyoruz zamanı geldiğinde gelip eklersiniz şimdilik sizedbox'ları atın veya hatta direkt halledin onu şu şekilde

direkt widgets klasörüne gidin auth_field.dart diye dosyanızı oluşturun isim olarak farklı bir şey vermek istiyorsanız giris_kutucugu tarzı bir şey verebilirsiniz

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]import 'package:kooginapp/core/theme/pallete.dart';

class AuthField extends StatelessWidget {
  final TextEditingController controller;
  final String hintText;
  const AuthField({super.key, required this.controller, required this.hintText});

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      decoration: InputDecoration(
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(5),
          borderSide: const BorderSide(
            color: Pallete.blueColor,
            width: 4,
          ),
        ),
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(5),
          borderSide: const BorderSide(
            color: Pallete.greyColor,
          ),
        ),
        contentPadding: const EdgeInsets.all(22),
        hintText: hintText,
        hintStyle: const TextStyle(
          fontSize: 18,
          color: Pallete.greyColor,
        )
      ),
    );
  }
}



burada yaptığımız şey stless bir widget oluşturup TextFormField döndürmek ve bu textformfield'ın controller'ını class çağrıldığında istemek ve oradan çekmek ayrıca hintText'i de aynı şekilde bu isimlendirmeleri tabii değiştirebilirsiniz. bunları named required olarak veriyoruz. Ardından biraz decoration eklemişiz borderside atayıp azıcık da contentPadding vermişiz bu widget bu kadar.

login_view.dart'a dönecek olursak burada bir RichText içerisinde TextSpan kullandık o da hesabın yok mu ? hesap oluştur tarzında bir yazı göstermek istiyoruz ama renkler farklı. O nedenle textspan tercih ettik. textleri direkt olarak "yazı yazı" şeklinde vermek yerine app_texts.dart diye bir dosya oluşturup static olarak oraya tanımlayıp oradan çekiyoruz.

En sonunda signupview'a geçiş için kullandığımız aslında size loginview içerisinde gösterdiğim şu static route olayını kullanıyoruz. Sayfa geçişinin de kodunu yazmış olduk. Şimdi signupview'ı yazmamız gerek yani kullanıcının kayıt olacağı sayfayı

f0LfYq.png


şu anda aldığımız login view tasarım sayfası bu şekilde. Benzer işlemleri signup için de yapacağız. Kodlar neredeyse aynı olacak

Kod:
import 'package:flutter/gestures.dart';[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kooginapp/core/common/loading_page.dart';
import 'package:kooginapp/core/constants/app_texts.dart';
import 'package:kooginapp/core/providers/providers.dart';
import 'package:kooginapp/features/auth/controller/auth_controller.dart';
import 'package:kooginapp/features/auth/view/login_view.dart';
import 'package:kooginapp/features/auth/widgets/age_field.dart';
import 'package:kooginapp/features/auth/widgets/auth_field.dart';

import '../../../core/common/common.dart';
import '../../../core/constants/constants.dart';
import '../../../core/theme/theme.dart';

class SignUpView extends ConsumerStatefulWidget {
  static route() => MaterialPageRoute(
        builder: (context) => const SignUpView(),
      );
  const SignUpView({super.key});

  @override
  ConsumerState<SignUpView> createState() => _SignUpViewState();
}

class _SignUpViewState extends ConsumerState<SignUpView> {
  final appbar = UIConstants.appBar();

  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final ageController = TextEditingController();

  String selectedCountry = 'United States';

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

  void onSignUp() {
    // ignore: unused_local_variable
    final res = ref.read(authControllerProvider.notifier).signUp(
          email: emailController.text,
          password: passwordController.text,
          age: ageController.text,
          country: selectedCountry,
          context: context,
        );
  }

  @override
  Widget build(BuildContext context) {
    final isLoading = ref.watch(authControllerProvider); //! we are watching authControllerProvider
    final appThemeState = ref.watch(appThemeStateNotifier);

    return Scaffold(
        appBar: appbar,
        body: isLoading
            ? const Loader()
            : Center(
                child: SingleChildScrollView(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: Column(
                      children: [
                        AuthField(
                          controller: emailController,
                          hintText: AppTexts.email,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AuthField(
                          controller: passwordController,
                          hintText: AppTexts.password,
                        ),
                        const SizedBox(
                          height: 25,
                        ),
                        AgeField(controller: ageController, hintText: AppTexts.age),
                        const SizedBox(
                          height: 40,
                        ),
                        DropdownButton<String>(
                          value: selectedCountry,
                          items: AppTexts.countries
                              .map<DropdownMenuItem<String>>(
                                (String e) => DropdownMenuItem<String>(
                                  value: e,
                                  child: Text(e, style: const TextStyle(fontSize: 16)),
                                ),
                              )
                              .toList(),
                          onChanged: (String? newValue) {
                            setState(() {
                              selectedCountry = newValue!;
                            });
                          },
                        ),
                         const SizedBox(
                          height: 40,
                        ),
                        Align(
                          alignment: Alignment.topRight,
                          child: RoundedSmallButton(
                            onTap: onSignUp,
                            label: AppTexts.done,
                          ),
                        ),
                        const SizedBox(
                          height: 40,
                        ),
                        RichText(
                          text: TextSpan(
                            text: AppTexts.alreadyAcc,
                            style: TextStyle(
                              fontSize: 16,
                              color: appThemeState.isDarkModeEnabled ? Pallete.whiteColor : Pallete.blackColor,
                            ),
                            children: [
                              TextSpan(
                                text: AppTexts.login,
                                style: const TextStyle(
                                  color: Pallete.blueColor,
                                  fontSize: 16,
                                ),
                                recognizer: TapGestureRecognizer()
                                  ..onTap = () {
                                    Navigator.pushReplacement(
                                      context,
                                      LoginView.route(),
                                    );
                                  },
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ));
  }
}



Burada yaptığımız değişiklik fonksiyon olarak onSignUp fonksiyonunu yazıyoruz controller ve API tarafını hallettiğimizde kullanacağımız şeyi göreceksiniz şimdilik buraları yazmanıza gerek yok tasarım kısmını halledin sadece. devamında yine isLoading ve appThemeState kullanmışız yine aynı kodlar yalnız ben ek olarak kullanıcılar üye olurken yaşını girsin diye oluşturduğum AgeField widgetı da (AuthField'a çok benziyor) bu widgetı ekledim. Klasik birkaç tane sizedbox eklenmiş aslında böyle sizedboxları direkt cayır cayır verip sayı girmek canımı sıkar kartal package'ını kullanmak daha mantıklı olurdu ama devam edelim nasıl olsa projeyi bu haliyle yayınlamayacağım ben yayınlayacaksanız teknik açıdan temel şeyleri vereceğim siz kendiniz düşünürsünüz bazı kısımları :D

Bir de kullanıcıların üye olurken ülke seçmesi için dropdownbutton yazmıştım yine constants klasöründe belirlediğimiz apptexts içerisindeki ülkeleri oraya liste şeklinde yazıp burada mapleyerek tek tek dropdownmenuitem gösterimi yapıyoruz. değiştiğinde de seçilen ülkeyi yani selectedCountry değerini seçilen değere atayıp setState atarsanız kolayca ülke seçildikten sonra değişen değerin aktarılmasını sağlayabilirsiniz. Aslında setState kullanmayı da pek sevmem ama :D
Geriye kalan kodlar loginview UI için yazdığımız kodlarla aynı fakat navigasyon yönü farklı çünkü zaten hesabınız varsa loginview.route'ına gitmeniz gerekir kodlar aynı sadece yazılacak yer sizinle konu başında önceden loginview için yazdığımız static route metodunu kullanıyor olmamız.

Böylece kayıt ekranını da bitirmiş oluyoruz.

f0LoPi.png


Backend tarafına gelecek olursak öncelikle Appwrite içerisinde bir db oluşturmalıyız. Çünkü kullanıcıları kayıt ettiğimizde auth kullanıcıları oluşturacağız fakat db oluşturup users veya kafanıza göre kullanicilar koleksiyonu oluşturup kullanıcıların bilgilerini kaydetmemiz gerekecek. Bundan dolayı öncelikle Appwrite'ınızda bazı ayarlamalar yapmanız gerekir. İlk konuda anlattığım şeyleri yapanlar docker'ı çalıştırdıktan sonra appwrite'ı da runladığınızda localhost yazarak tarayıcıdan appwrite'a girip uygulamanızın ismini girebilirsiniz ardından database kısmına gelip bir db oluşturun ismi fark etmez içerisine de users adllı bir koleksiyon oluşturun create collection diyerek.

f0LVDQ.png


Bu appwrite içerisinde users koleksiyonu için tanımlamanız gereken attributelar çünkü model sınıfımızı oluşturduğumuzda bu bize lazım olacak. Devam edelim.

Artık backendi ilgilendiren kodları yazmanın zamanı geldi aslında UI tarafına dalmadan belli başlı providerları belirlemek gerekiyordu şimdi lib içerisinde providers diye bir klasör açın ve bu klasör içerisinde providers.dart dosyasını oluşturun.

Kod:
final appwriteClientProvider = Provider((ref) {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  Client client = Client();
  return client
      .setEndpoint(AppwriteConstants.endPoint)
      .setProject(AppwriteConstants.projectId)
      .setSelfSigned(status: true);
});



Burada appwrite bağlantısını yapmak için Client oluşturuyoruz ve appwrite tarafında endPoint olarak ve projectId olarak tanımladığımız gerekli değerleri giriyoruz bunun için appwrite constants diye bir dart dosyası oluşturup o dosya içerisinde Appwrite ile ilgili bilgileri içeren şeyleri kullanıyoruz.

Kod:
class AppwriteConstants {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  static const String databaseId = 'dbidiniz';
  static const String projectId = 'projeidiniz';
  static const String endPoint = 'IPADRESINIZ/v1';
  static const String usersCollection = 'userskoleksiyonuidsi';
  static const String postCollection = 'postkoleksiyonuidsi';
  static const String imagesBucket = 'resimlericinbucketid';
  static const String kooginstreamdb = 'realtimeicindbid';
  static const String notificationsCollection = 'notificationkoleksiyonuicinid';
  static const String messagesCollection = 'mesajlaricinid';

  static String imageUrl(String imageId) {
    return '$endPoint/storage/buckets/$imagesBucket/files/$imageId/view?project=$projectId&mode=admin';
  }
}



Burada appwrite constats içerisinde gerekli bilgileri "" içerisine neleri kullanacağınızı verdim o kısımları değiştirerek yapabilirsiniz.

provider kısmına devam edecek olursak

Kod:
final appwriteAccountProvider = Provider([/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  (ref) {
    final client = ref.watch(appwriteClientProvider);
    return Account(client);
  },
);



burada da appwrite.dart'tan gelen Account için bir provider oluşturuyoruz provider oluşturmaktaki amaç bunları sistematik bir şekilde kullanırken her yerden erişebiliyoruz.

Kod:
final appwriteDatabaseProvider = Provider((ref) {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Databases(client);
});



db için bir provider aynı şekilde.

Kod:
final appWriteStorageProvider = Provider((ref) {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Storage(client);
});



kullanıcılar profil ve bannerlarını değiştirdiklerinde storage'a kayıt olacağı için appwrite tarafında storage için bu providerımızı da oluşturuyoruz.

Kod:
final appWriteRealTimeProvider = Provider((ref) {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  final client = ref.watch(appwriteClientProvider);
  return Realtime(client);
});



bu providerımızı realtime için oluşturuyoruz yine Appwrite.dart'dan gelen package sayesinde

Kod:
final appThemeStateNotifier = ChangeNotifierProvider((ref) {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  return AppThemeState();
});



bu da tema için bir ChangeNotifierProvider AppThemeState'e erişmemize yarayacak.

Providerlarımızı oluşturduğumuza göre API ve Controller tarafını yazmaya başlayabiliriz. Auth feature'ına odaklanıyoruz şu anda şimdilik yazmamız gereken auth_api.dart dosyası oluşturuyoruz lib içerisine apis diye bir klasör açıp içerisine auth_api.dart veya istediğiniz isimle dosyayı oluşturun.

Öncelikle özelliklerimiz için yazacağımız backend related kodu rahatlatmak adına soyut sınıf oluşturmamız gerekli daha sonra eklemeler yaparken kafamız karışmaz.

Kod:
abstract class IAuthAPI {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  FutureEither<model.User> signUp({required String email, required String password});
  FutureEither<model.Session> login({required String email, required String password});
  Future<model.User?> currentUserAccount();
  FutureEitherVoid logout();
}



burada signup login ve currentUserAccount metodlarını oluşturuyoruz. Soyut sınıfımızı oluşturduktan sonra yapacağımız işlem aslında asıl class'ı yazarken kullanacağımız metodları implemente edebilmek ve kolaylık olması. Burada model.User? appwrite tarafından gelen bir şey bunun ismi önceden Account'tu sonradan değiştirdiler.
import 'package:appwrite/models.dart' as model;

import ederken bu şekilde import ederseniz kullanımı daha kolay olur ve karışıklık olmaz. FutureEither diye bir tür yok dartta ama bu typedef ile tanımladığımız bizim türümüz.

Kod:
typedef FutureEither<T> = Future<Either<Failure, T>>;[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]typedef FutureEitherVoid = FutureEither<void>;



projenizin lib içerisinde utils klasörü içerisinde bir type_defs.dart veya verdiğiniz bir isimle dosyayı oluşturup yukarıdaki gibi tanımlamalar yapabilirsiniz. Amaç kod yazarken çok fazla fpdart package'ından Future<Either<Failure, T>> yapısını kullanacağımız için direkt FutureEither diyerek kolaylık sağlamak.

şimdi auth_api.dart'a dönecek olursak

Kod:
class AuthAPI implements IAuthAPI {[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  final Account _account;

  AuthAPI({required Account account}) : _account = account;

  @override
  Future<model.User?> currentUserAccount() async {
    try {
      return await _account.get();
    } on AppwriteException catch (_) {
      return null;
    } catch (_) {
      return null;
    }
  }



Burada artık AuthAPI'mızı yazıyoruz gereksinim olarak appwrite'dan gelen Account sınıfından _account tanımladık. Ve ilk iş yapan fonksiyonumuzu yazmaya başlıyoruz. Bu fonksiyon bize Future olarak yani bir süre sonra model.User? döndürecek ve adı currentUserAccount yani ismi istediğiniz gibi değiştirin şu anki kullanıcıya erişmek için yazdığımız bir fonksiyon. try catch yapısı var fonksiyon asenkron olacak bize döndüreceği şey _account.get(); yani gereksinim olarak aldığımız appwrite packageından gelen account nesnesinin türlü türlü metodları var ve bu get metodu bize şu anki giriş yapılmış kullanıcıyı döndürüyor. Biz de onu döndürüyoruz. bu fonksiyon bu kadar şimdi diğerine gelelim

Kod:
@override[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  FutureEither<model.User> signUp({required String email, required String password}) async {
    try {
      final account = await _account.create(userId: ID.unique(), email: email, password: password);
      return right(account);
    } on AppwriteException catch (e, stackTrace) {
      return left(Failure(e.message ?? 'Some unexpected error occurred', stackTrace));
    } catch (e, stackTrace) {
      return left(Failure(e.toString(), stackTrace));
    }
  }



bu fonksiyon ise signUp fonksiyonumuz kullanıcıların üye olmasını sağlayacak. fpdart package'ı kullanarak typedeflediğimiz model.User döndürecek ve parametre olarak email ve password istiyor sadece. yine try catch bloğu oluşturup _account.create metodunu kullanıyoruz userId olarak kendine özgü bir id oluşturması için ID.unique() email olarak gelen parametredeki email ve şifre için de aynı şeyi yapıyoruz. Eğer olur da bir appwriteexceptionu gerçekleşirse bunu yakalayıp fpdart packageından yararlanarak left döndürüyoruz yani başarısız işlem demek. İlgili hata mesajını yazdırıyoruz.

Kod:
  @override[/I][/FONT][/CENTER][/I][/FONT][/CENTER]
[FONT=trebuchet ms][I][CENTER][FONT=trebuchet ms][I][CENTER]  FutureEither<model.Session> login({required String email, required String password}) async {
    try {
      final session = await _account.createEmailPasswordSession(email: email, password: password);
      return right(session);
    } on AppwriteException catch (e, stackTrace) {
      return left(Failure(e.message ?? 'Some unexpected error occurred', stackTrace));
    } catch (e, stackTrace) {
      return left(Failure(e.toString(), stackTrace));
    }
  }



benzer işlemi login fonksiyonu oluştururken de yapıyoruz. Bu sefer createEmailPasswordSession fonksiyonunu kullanıyoruz. auth_api.dart ile aslında bir fonksiyon haricinde işimiz bitiyor. Şimdi yapmamız gereken auth_controller.dart dosyasını oluşturmak. Bunu da bir sonraki konuda yapalım böylece auth_controller.dart'ı da yazdıktan sonra kullanıcılar başarıyla üye olup uygulamaya başarıyla giriş yapabilir hale gelmiş olacak ve diğer özelliklere odaklanacağız.

Bir sonraki konuda görüşelim. <3 Gauloran


kaliteli konu hocam elinize sağlık
 

'Anka

Basın&Medya Ekibi Kıdemli
8 Eyl 2021
3,380
118
2,572
Oku oku bitmiyor maşallah. 😀 Elinize sağlık güzel bir konu olmuş
 

Fearlessturks

Yeni üye
19 Kas 2019
4
1
Öncelikle elinize sağlık ben web geliştirme ile ilgileniyorum sizce mobil geliştirmeye geçerken react native'mi ? Yoksa flutter'mı
 

klamhat

Üye
8 Ocak 2011
103
45
Mobil Yazılım geliştirme hep ilgimi çekmiştir ama benlik değil gibi.. Ellerinize sağlık
 

Gauloran

Moderasyon Ekibi Lideri
7 Tem 2013
8,203
4
678
Öncelikle elinize sağlık ben web geliştirme ile ilgileniyorum sizce mobil geliştirmeye geçerken react native'mi ? Yoksa flutter'mı

react native daha populer google'in kendi projelerini terk etme gibi bir huyu da var eger ben daha populer olan pazari daha kalabalik olana yonelecegim diyorsan react native ama ben kasiniyorum fantezi yapacagim diyorsan flutter.
 
Ü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.