Flutter Giriş
Bu eğitimde, Bloc kütüphanesini kullanarak Flutter ile bir Giriş Akışı oluşturacağız.

Önemli Konular
Section titled “Önemli Konular”- BlocProvider, alt widget'larına bir bloc sağlayan Flutter widget'ı.
- context.read ile olay ekleme.
- Equatable ile gereksiz yeniden oluşturmaları önleme.
- RepositoryProvider, alt widget'larına bir repository sağlayan Flutter widget'ı.
- BlocListener, bloc'taki durum değişikliklerine yanıt olarak listener kodunu çağıran bir Flutter widget'ı.
- context.select ile bir bloc durumunun bir kısmına dayalı olarak kullanıcı arayüzünü güncelleme.
Proje Kurulumu
Section titled “Proje Kurulumu”Yepyeni bir Flutter projesi oluşturarak başlayacağız
flutter create flutter_loginSonrasında tüm bağımlılıklarımızı kurabiliriz
flutter pub getAuthentication Repository
Section titled “Authentication Repository”İlk yapacağımız şey, kimlik doğrulama alanını yönetmekten sorumlu olacak bir
authentication_repository paketi oluşturmaktır.
Tüm dahili paketleri içerecek olan projenin kökünde bir
packages/authentication_repository dizini oluşturarak başlayacağız.
Yüksek seviyede, dizin yapısı şöyle görünmelidir:
├── android├── ios├── lib├── packages│ └── authentication_repository└── testSırada, authentication_repository paketi için bir pubspec.yaml
oluşturabiliriz:
name: authentication_repositorydescription: Dart package which manages the authentication domain.publish_to: none
environment: sdk: ^3.12.0Sırada,
packages/authentication_repository/lib/src/authentication_repository.dart
içinde yer alacak AuthenticationRepository sınıfının kendisini uygulamamız
gerekiyor.
import 'dart:async';
enum AuthenticationStatus { unknown, authenticated, unauthenticated }
class AuthenticationRepository { final _controller = StreamController<AuthenticationStatus>();
Stream<AuthenticationStatus> get status async* { await Future<void>.delayed(const Duration(seconds: 1)); yield AuthenticationStatus.unauthenticated; yield* _controller.stream; }
Future<void> logIn({ required String username, required String password, }) async { await Future.delayed( const Duration(milliseconds: 300), () => _controller.add(AuthenticationStatus.authenticated), ); }
void logOut() { _controller.add(AuthenticationStatus.unauthenticated); }
void dispose() => _controller.close();}AuthenticationRepository, bir kullanıcı oturum açtığında veya kapattığında
uygulamayı bilgilendirmek için kullanılacak AuthenticationStatus
güncellemelerinin bir Stream'ini sunar.
Ayrıca, basitlik adına stub olarak yapılmış ancak örneğin FirebaseAuth veya
başka bir kimlik doğrulama sağlayıcısı ile kimlik doğrulama yapacak şekilde
kolayca genişletilebilen logIn ve logOut metotları vardır.
Son olarak, dışa açık aktarımları içerecek olan
packages/authentication_repository/lib/authentication_repository.dart
dosyasını oluşturmamız gerekir:
export 'src/authentication_repository.dart';AuthenticationRepository için bu kadar, sırada UserRepository üzerinde
çalışacağız.
User Repository
Section titled “User Repository”AuthenticationRepository'de olduğu gibi, packages dizini içinde bir
user_repository paketi oluşturacağız.
├── android├── ios├── lib├── packages│ ├── authentication_repository│ └── user_repository└── testSırada, user_repository için pubspec.yaml oluşturacağız:
name: user_repositorydescription: Dart package which manages the user domain.publish_to: none
environment: sdk: ^3.12.0
dependencies: equatable: ^2.1.0-dev.0 uuid: ^3.0.0user_repository, kullanıcı alanından sorumlu olacak ve mevcut kullanıcıyla
etkileşim kurmak için API'ler sunacaktır.
İlk tanımlayacağımız şey, packages/user_repository/lib/src/models/user.dart
dosyasındaki kullanıcı modelidir:
import 'package:equatable/equatable.dart';
class User extends Equatable { const User(this.id);
final String id;
@override List<Object> get props => [id];
static const empty = User('-');}Basitlik adına, bir kullanıcının yalnızca bir id özelliği vardır, ancak
pratikte firstName, lastName, avatarUrl gibi ek özelliklerimiz olabilir...
Sırada, birden fazla modeli içe aktarmak için tek bir içe aktarma ifadesi
kullanabilmemiz adına tüm modelleri dışa aktaracak olan
packages/user_repository/lib/src/models içinde bir models.dart
oluşturabiliriz.
export 'user.dart';Modeller tanımlandığına göre, UserRepository sınıfını
packages/user_repository/lib/src/user_repository.dart içinde uygulayabiliriz.
import 'dart:async';
import 'package:user_repository/src/models/models.dart';import 'package:uuid/uuid.dart';
class UserRepository { User? _user;
Future<User?> getUser() async { if (_user != null) return _user; return Future.delayed( const Duration(milliseconds: 300), () => _user = User(const Uuid().v4()), ); }}Bu basit örnek için, UserRepository mevcut kullanıcıyı alacak tek bir
getUser metodu sunar. Bunu stub olarak yapıyoruz, ancak pratikte mevcut
kullanıcıyı backend'den sorgulayacağımız yer burasıdır.
user_repository paketi neredeyse bitti -- yapmamız gereken tek şey, dışa açık
aktarımları tanımlayan user_repository.dart dosyasını
packages/user_repository/lib içinde oluşturmaktır:
export 'src/models/models.dart';export 'src/user_repository.dart';authentication_repository ve user_repository paketleri tamamlandığına göre,
Flutter uygulamasına odaklanabiliriz.
Bağımlılıkları Kurma
Section titled “Bağımlılıkları Kurma”Projemizin kökündeki oluşturulmuş pubspec.yaml dosyasını güncelleyerek
başlayalım:
name: flutter_logindescription: A new Flutter project.version: 1.0.0+1publish_to: none
environment: sdk: ^3.12.0
dependencies: authentication_repository: path: packages/authentication_repository bloc: ^9.0.0 equatable: ^2.1.0-dev.0 flutter: sdk: flutter flutter_bloc: ^9.1.0 formz: ^0.8.0 user_repository: path: packages/user_repository
dev_dependencies: bloc_lint: ^0.3.0 bloc_test: ^10.0.0 flutter_test: sdk: flutter mocktail: ^1.0.0
flutter: uses-material-design: trueBağımlılıkları şu komutu çalıştırarak kurabiliriz:
flutter pub getAuthentication Bloc
Section titled “Authentication Bloc”AuthenticationBloc, kimlik doğrulama durumundaki değişikliklere
(AuthenticationRepository tarafından sunulan) tepki vermekten ve sunum
katmanında tepki verebileceğimiz durumları yaymaktan sorumlu olacaktır.
AuthenticationBloc'un uygulaması lib/authentication içindedir çünkü kimlik
doğrulamayı uygulama katmanımızda bir özellik olarak ele alıyoruz.
├── lib│ ├── app.dart│ ├── authentication│ │ ├── authentication.dart│ │ └── bloc│ │ ├── authentication_bloc.dart│ │ ├── authentication_event.dart│ │ └── authentication_state.dart│ ├── main.dartauthentication_event.dart
Section titled “authentication_event.dart”AuthenticationEvent örnekleri, AuthenticationBloc'un girdisi olacak ve yeni
AuthenticationState örnekleri yaymak için işlenip kullanılacaktır.
Bu uygulamada, AuthenticationBloc iki farklı olaya tepki verecektir:
AuthenticationSubscriptionRequested: bloc'aAuthenticationStatusstream'ine abone olmasını bildiren başlangıç olayıAuthenticationLogoutPressed: bloc'a kullanıcının çıkış yapma eylemini bildirir
part of 'authentication_bloc.dart';
sealed class AuthenticationEvent { const AuthenticationEvent();}
final class AuthenticationSubscriptionRequested extends AuthenticationEvent {}
final class AuthenticationLogoutPressed extends AuthenticationEvent {}Sırada, AuthenticationState'e bir göz atalım.
authentication_state.dart
Section titled “authentication_state.dart”AuthenticationState örnekleri, AuthenticationBloc'un çıktısı olacak ve sunum
katmanı tarafından tüketilecektir.
AuthenticationState sınıfının üç adlandırılmış kurucu (constructor) vardır:
-
AuthenticationState.unknown(): bloc'un mevcut kullanıcının kimlik doğrulamasının yapılıp yapılmadığını henüz bilmediğini belirten varsayılan durum. -
AuthenticationState.authenticated(): kullanıcının şu anda kimlik doğrulamasının yapıldığını belirten durum. -
AuthenticationState.unauthenticated(): kullanıcının şu anda kimlik doğrulamasının yapılmadığını belirten durum.
part of 'authentication_bloc.dart';
class AuthenticationState extends Equatable { const AuthenticationState._({ this.status = AuthenticationStatus.unknown, this.user = User.empty, });
const AuthenticationState.unknown() : this._();
const AuthenticationState.authenticated(User user) : this._(status: AuthenticationStatus.authenticated, user: user);
const AuthenticationState.unauthenticated() : this._(status: AuthenticationStatus.unauthenticated);
final AuthenticationStatus status; final User user;
@override List<Object> get props => [status, user];}AuthenticationEvent ve AuthenticationState uygulamalarını gördüğümüze göre,
şimdi AuthenticationBloc'a bir göz atalım.
authentication_bloc.dart
Section titled “authentication_bloc.dart”AuthenticationBloc, kullanıcının bir giriş sayfasında mı yoksa bir ana sayfada
mı başlatılacağı gibi şeyleri belirlemek için kullanılan uygulamanın kimlik
doğrulama durumunu yönetir.
import 'dart:async';
import 'package:authentication_repository/authentication_repository.dart';import 'package:bloc/bloc.dart';import 'package:equatable/equatable.dart';import 'package:user_repository/user_repository.dart';
part 'authentication_event.dart';part 'authentication_state.dart';
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> { AuthenticationBloc({ required this._authenticationRepository, required this._userRepository, }) : super(const AuthenticationState.unknown()) { on<AuthenticationSubscriptionRequested>(_onSubscriptionRequested); on<AuthenticationLogoutPressed>(_onLogoutPressed); }
final AuthenticationRepository _authenticationRepository; final UserRepository _userRepository;
Future<void> _onSubscriptionRequested( AuthenticationSubscriptionRequested event, Emitter<AuthenticationState> emit, ) { return emit.onEach( _authenticationRepository.status, onData: (status) async { switch (status) { case AuthenticationStatus.unauthenticated: return emit(const AuthenticationState.unauthenticated()); case AuthenticationStatus.authenticated: final user = await _tryGetUser(); return emit( user != null ? AuthenticationState.authenticated(user) : const AuthenticationState.unauthenticated(), ); case AuthenticationStatus.unknown: return emit(const AuthenticationState.unknown()); } }, onError: addError, ); }
void _onLogoutPressed( AuthenticationLogoutPressed event, Emitter<AuthenticationState> emit, ) { _authenticationRepository.logOut(); }
Future<User?> _tryGetUser() async { try { final user = await _userRepository.getUser(); return user; } catch (_) { return null; } }}AuthenticationBloc, hem AuthenticationRepository hem de UserRepository'e
bir bağımlılığa sahiptir ve başlangıç durumunu AuthenticationState.unknown()
olarak tanımlar.
Kurucu gövdesinde, AuthenticationEvent alt sınıfları, ilgili olay
işleyicilerine eşlenir.
_onSubscriptionRequested olay işleyicisinde, AuthenticationBloc,
AuthenticationRepository'nin status stream'ine abone olmak ve her bir
AuthenticationStatus'a yanıt olarak bir durum yaymak için emit.onEach
kullanır.
emit.onEach, dahili olarak bir stream subscription oluşturur ve hem
AuthenticationBloc hem de status stream'i kapatıldığında onu iptal etmeyi
üstlenir.
status stream'i bir hata yayarsa, addError, hatayı ve stackTrace'i dinleyen
herhangi bir BlocObserver'a iletir.
status stream'i AuthenticationStatus.unknown veya unauthenticated
yaydığında, ilgili AuthenticationState yayılır.
AuthenticationStatus.authenticated yayıldığında, AuthentictionBloc,
UserRepository aracılığıyla kullanıcıyı sorgular.
main.dart
Section titled “main.dart”Sırada, varsayılan main.dart'ı şununla değiştirebiliriz:
import 'package:flutter/widgets.dart';import 'package:flutter_login/app.dart';
void main() => runApp(const App());app.dart, tüm uygulama için kök App widget'ını içerecektir.
import 'package:authentication_repository/authentication_repository.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_login/authentication/authentication.dart';import 'package:flutter_login/home/home.dart';import 'package:flutter_login/login/login.dart';import 'package:flutter_login/splash/splash.dart';import 'package:user_repository/user_repository.dart';
class App extends StatelessWidget { const App({super.key});
@override Widget build(BuildContext context) { return MultiRepositoryProvider( providers: [ RepositoryProvider( create: (_) => AuthenticationRepository(), dispose: (repository) => repository.dispose(), ), RepositoryProvider(create: (_) => UserRepository()), ], child: BlocProvider( lazy: false, create: (context) => AuthenticationBloc( authenticationRepository: context.read<AuthenticationRepository>(), userRepository: context.read<UserRepository>(), )..add(AuthenticationSubscriptionRequested()), child: const AppView(), ), ); }}
class AppView extends StatefulWidget { const AppView({super.key});
@override State<AppView> createState() => _AppViewState();}
class _AppViewState extends State<AppView> { final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState!;
@override Widget build(BuildContext context) { return MaterialApp( navigatorKey: _navigatorKey, builder: (context, child) { return BlocListener<AuthenticationBloc, AuthenticationState>( listener: (context, state) { switch (state.status) { case AuthenticationStatus.authenticated: _navigator.pushAndRemoveUntil<void>( HomePage.route(), (route) => false, ); case AuthenticationStatus.unauthenticated: _navigator.pushAndRemoveUntil<void>( LoginPage.route(), (route) => false, ); case AuthenticationStatus.unknown: break; } }, child: child, ); }, onGenerateRoute: (_) => SplashPage.route(), ); }}Varsayılan olarak, BlocProvider lazy'dir ve Bloc'a ilk kez erişilene kadar
create'i çağırmaz. AuthenticationBloc'un her zaman AuthenticationStatus
stream'ine hemen abone olması gerektiğinden
(AuthenticationSubscriptionRequested olayı aracılığıyla), lazy: false
ayarlayarak bu davranışı açıkça devre dışı bırakabiliriz.
AppView, NavigatorState'e erişmek için kullanılan bir GlobalKey
tuttuğundan bir StatefulWidget'tır. Varsayılan olarak, AppView,
SplashPage'i (daha sonra göreceğimiz) render edecek ve
AuthenticationState'teki değişikliklere göre farklı sayfalara yönlendirmek
için BlocListener kullanır.
Splash
Section titled “Splash”Splash özelliği, kullanıcının kimliğinin doğrulanıp doğrulanmadığını belirlerken uygulama başlatılır başlatılmaz render edilecek basit bir görünüm içerecektir.
lib└── splash ├── splash.dart └── view └── splash_page.dartimport 'package:flutter/material.dart';
class SplashPage extends StatelessWidget { const SplashPage({super.key});
static Route<void> route() { return MaterialPageRoute<void>(builder: (_) => const SplashPage()); }
@override Widget build(BuildContext context) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); }}Login özelliği bir LoginPage, LoginForm ve LoginBloc içerir ve
kullanıcıların uygulamaya giriş yapmak için bir kullanıcı adı ve parola
girmesine izin verir.
├── lib│ ├── login│ │ ├── bloc│ │ │ ├── login_bloc.dart│ │ │ ├── login_event.dart│ │ │ └── login_state.dart│ │ ├── login.dart│ │ ├── models│ │ │ ├── models.dart│ │ │ ├── password.dart│ │ │ └── username.dart│ │ └── view│ │ ├── login_form.dart│ │ ├── login_page.dart│ │ └── view.dartLogin Modelleri
Section titled “Login Modelleri”username ve password için yeniden kullanılabilir ve standart modeller
oluşturmak amacıyla package:formz
kullanıyoruz.
Username
Section titled “Username”import 'package:formz/formz.dart';
enum UsernameValidationError { empty }
class Username extends FormzInput<String, UsernameValidationError> { const Username.pure() : super.pure(''); const Username.dirty([super.value = '']) : super.dirty();
@override UsernameValidationError? validator(String value) { if (value.isEmpty) return UsernameValidationError.empty; return null; }}Basitlik adına, kullanıcı adının boş olmadığından emin olmak için yalnızca doğrulama yapıyoruz, ancak pratikte özel karakter kullanımı, uzunluk vb. zorunlu kılabilirsiniz...
Password
Section titled “Password”import 'package:formz/formz.dart';
enum PasswordValidationError { empty }
class Password extends FormzInput<String, PasswordValidationError> { const Password.pure() : super.pure(''); const Password.dirty([super.value = '']) : super.dirty();
@override PasswordValidationError? validator(String value) { if (value.isEmpty) return PasswordValidationError.empty; return null; }}Yine, parolanın boş olmadığından emin olmak için basit bir kontrol yapıyoruz.
Models Barrel
Section titled “Models Barrel”Daha önce olduğu gibi, Username ve Password modellerini tek bir içe aktarma
ile içe aktarmayı kolaylaştıran bir models.dart barrel'ı vardır.
export 'password.dart';export 'username.dart';Login Bloc
Section titled “Login Bloc”LoginBloc, LoginForm'un durumunu yönetir ve kullanıcı adı ile parola
girdisinin yanı sıra formun durumunun da doğrulanmasını üstlenir.
login_event.dart
Section titled “login_event.dart”Bu uygulamada üç farklı LoginEvent türü vardır:
LoginUsernameChanged: bloc'a kullanıcı adının değiştirildiğini bildirir.LoginPasswordChanged: bloc'a parolanın değiştirildiğini bildirir.LoginSubmitted: bloc'a formun gönderildiğini bildirir.
part of 'login_bloc.dart';
sealed class LoginEvent extends Equatable { const LoginEvent();
@override List<Object> get props => [];}
final class LoginUsernameChanged extends LoginEvent { const LoginUsernameChanged(this.username);
final String username;
@override List<Object> get props => [username];}
final class LoginPasswordChanged extends LoginEvent { const LoginPasswordChanged(this.password);
final String password;
@override List<Object> get props => [password];}
final class LoginSubmitted extends LoginEvent { const LoginSubmitted();}login_state.dart
Section titled “login_state.dart”LoginState, formun durumunu ve kullanıcı adı ile parola girdi durumlarını
içerecektir.
part of 'login_bloc.dart';
final class LoginState extends Equatable { const LoginState({ this.status = FormzSubmissionStatus.initial, this.username = const Username.pure(), this.password = const Password.pure(), this.isValid = false, });
final FormzSubmissionStatus status; final Username username; final Password password; final bool isValid;
LoginState copyWith({ FormzSubmissionStatus? status, Username? username, Password? password, bool? isValid, }) { return LoginState( status: status ?? this.status, username: username ?? this.username, password: password ?? this.password, isValid: isValid ?? this.isValid, ); }
@override List<Object> get props => [status, username, password];}login_bloc.dart
Section titled “login_bloc.dart”LoginBloc, LoginForm'daki kullanıcı etkileşimlerine tepki vermekten ve
formun doğrulanmasını ve gönderilmesini ele almaktan sorumludur.
import 'package:authentication_repository/authentication_repository.dart';import 'package:bloc/bloc.dart';import 'package:equatable/equatable.dart';import 'package:flutter_login/login/login.dart';import 'package:formz/formz.dart';
part 'login_event.dart';part 'login_state.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> { LoginBloc({required this._authenticationRepository}) : super(const LoginState()) { on<LoginUsernameChanged>(_onUsernameChanged); on<LoginPasswordChanged>(_onPasswordChanged); on<LoginSubmitted>(_onSubmitted); }
final AuthenticationRepository _authenticationRepository;
void _onUsernameChanged( LoginUsernameChanged event, Emitter<LoginState> emit, ) { final username = Username.dirty(event.username); emit( state.copyWith( username: username, isValid: Formz.validate([state.password, username]), ), ); }
void _onPasswordChanged( LoginPasswordChanged event, Emitter<LoginState> emit, ) { final password = Password.dirty(event.password); emit( state.copyWith( password: password, isValid: Formz.validate([password, state.username]), ), ); }
Future<void> _onSubmitted( LoginSubmitted event, Emitter<LoginState> emit, ) async { if (state.isValid) { emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); try { await _authenticationRepository.logIn( username: state.username.value, password: state.password.value, ); emit(state.copyWith(status: FormzSubmissionStatus.success)); } catch (_) { emit(state.copyWith(status: FormzSubmissionStatus.failure)); } } }}LoginBloc, form gönderildiğinde logIn'i çağırdığı için
AuthenticationRepository'e bir bağımlılığa sahiptir. Bloc'un başlangıç durumu
pure'dur, yani ne girdilere ne de forma dokunulmuş veya etkileşime
geçilmiştir.
username veya password her değiştiğinde, bloc, Username/Password
modelinin dirty bir varyantını oluşturur ve form durumunu Formz.validate
API'si aracılığıyla günceller.
LoginSubmitted olayı eklendiğinde, formun mevcut durumu geçerliyse, bloc
logIn'e bir çağrı yapar ve isteğin sonucuna göre durumu günceller.
Sırada, LoginPage ve LoginForm'a bir göz atalım.
Login Page
Section titled “Login Page”LoginPage, Route'u sunmaktan ve ayrıca LoginBloc'u oluşturup LoginForm'a
sağlamaktan sorumludur.
import 'package:authentication_repository/authentication_repository.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_login/login/login.dart';
class LoginPage extends StatelessWidget { const LoginPage({super.key});
static Route<void> route() { return MaterialPageRoute<void>(builder: (_) => const LoginPage()); }
@override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(12), child: BlocProvider( create: (context) => LoginBloc( authenticationRepository: context.read<AuthenticationRepository>(), ), child: const LoginForm(), ), ), ); }}Login Form
Section titled “Login Form”LoginForm, LoginBloc'u kullanıcı olayları hakkında bilgilendirmeyi yönetir
ve ayrıca BlocBuilder ve BlocListener kullanarak durum değişikliklerine
yanıt verir.
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_login/login/login.dart';import 'package:formz/formz.dart';
class LoginForm extends StatelessWidget { const LoginForm({super.key});
@override Widget build(BuildContext context) { return BlocListener<LoginBloc, LoginState>( listener: (context, state) { if (state.status.isFailure) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( const SnackBar(content: Text('Authentication Failure')), ); } }, child: Align( alignment: const Alignment(0, -1 / 3), child: Column( mainAxisSize: MainAxisSize.min, children: [ _UsernameInput(), const Padding(padding: EdgeInsets.all(12)), _PasswordInput(), const Padding(padding: EdgeInsets.all(12)), _LoginButton(), ], ), ), ); }}
class _UsernameInput extends StatelessWidget { @override Widget build(BuildContext context) { final displayError = context.select( (LoginBloc bloc) => bloc.state.username.displayError, );
return TextField( key: const Key('loginForm_usernameInput_textField'), onChanged: (username) { context.read<LoginBloc>().add(LoginUsernameChanged(username)); }, decoration: InputDecoration( labelText: 'username', errorText: displayError != null ? 'invalid username' : null, ), ); }}
class _PasswordInput extends StatelessWidget { @override Widget build(BuildContext context) { final displayError = context.select( (LoginBloc bloc) => bloc.state.password.displayError, );
return TextField( key: const Key('loginForm_passwordInput_textField'), onChanged: (password) { context.read<LoginBloc>().add(LoginPasswordChanged(password)); }, obscureText: true, decoration: InputDecoration( labelText: 'password', errorText: displayError != null ? 'invalid password' : null, ), ); }}
class _LoginButton extends StatelessWidget { @override Widget build(BuildContext context) { final isInProgressOrSuccess = context.select( (LoginBloc bloc) => bloc.state.status.isInProgressOrSuccess, );
if (isInProgressOrSuccess) return const CircularProgressIndicator();
final isValid = context.select((LoginBloc bloc) => bloc.state.isValid);
return ElevatedButton( key: const Key('loginForm_continue_raisedButton'), onPressed: isValid ? () => context.read<LoginBloc>().add(const LoginSubmitted()) : null, child: const Text('Login'), ); }}BlocListener, giriş gönderimi başarısız olursa bir SnackBar göstermek için
kullanılır. Ek olarak, context.select, gereksiz yeniden oluşturmaları
önleyerek her widget için LoginState'in belirli bölümlerine verimli bir
şekilde erişmek için kullanılır. onChanged geri çağrısı, kullanıcı adı/parola
değişikliklerini LoginBloc'a bildirmek için kullanılır.
_LoginButton widget'ı yalnızca formun durumu geçerliyse etkindir ve form
gönderilirken yerinde bir CircularProgressIndicator gösterilir.
Başarılı bir logIn isteği üzerine, AuthenticationBloc'un durumu
authenticated'a değişecek ve kullanıcı, kullanıcının id'sini ve çıkış yapmak
için bir düğmeyi görüntülediğimiz HomePage'e yönlendirilecektir.
├── lib│ ├── home│ │ ├── home.dart│ │ └── view│ │ └── home_page.dartHome Page
Section titled “Home Page”HomePage, mevcut kullanıcı id'sine
context.select((AuthenticationBloc bloc) => bloc.state.user.id) aracılığıyla
erişebilir ve bunu bir Text widget'ı aracılığıyla görüntüler. Ek olarak, çıkış
düğmesine dokunulduğunda, AuthenticationBloc'a bir
AuthenticationLogoutPressed olayı eklenir.
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_login/authentication/authentication.dart';
class HomePage extends StatelessWidget { const HomePage({super.key});
static Route<void> route() { return MaterialPageRoute<void>(builder: (_) => const HomePage()); }
@override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [_UserId(), _LogoutButton()], ), ), ); }}
class _LogoutButton extends StatelessWidget { const _LogoutButton();
@override Widget build(BuildContext context) { return ElevatedButton( child: const Text('Logout'), onPressed: () { context.read<AuthenticationBloc>().add(AuthenticationLogoutPressed()); }, ); }}
class _UserId extends StatelessWidget { const _UserId();
@override Widget build(BuildContext context) { final userId = context.select( (AuthenticationBloc bloc) => bloc.state.user.id, );
return Text('UserID: $userId'); }}Bu noktada oldukça sağlam bir giriş uygulamamız var ve Bloc kullanarak sunum katmanımızı iş mantığı katmanından ayırdık.
Bu örneğin tam kaynak kodu (birim ve widget testleri dahil) buradan ulaşılabilir.