Sparround

Result sinfi: sealed class və tam switch

Rəsmi Flutter sənədi Result-un konkret formasını verir və o, Dart 3-ün sealed siniflərinə söykənir:

  • sealed class Result<T> — bazadır; sealed olduğu üçün onun bütün alt tipləri eyni kitabxanada olmalıdır və kompilyator onları tanıyır.
  • Ok<T> — uğurlu nəticə, value daşıyır.
  • Error<T> — uğursuzluq, Exception daşıyır.
  • İki const factory konstruktoru: Result.ok(value)Result.error(exception).

sealed-in verdiyi əsas şey: switch bütün alt tipləri əhatə etmədikdə kompilyator xəbərdarlıq edir. Yəni "xəta halını yazmağı unutdum" səhvi kompilyasiya vaxtında tutulur, məhsul rejimində deyil.

OkError isə final elan olunur — onların da alt tipi olmur, deməli switch üçün iki hal həmişə kifayətdir.

dart
// fayl: lib/utils/result.dart

/// Uğur (`Ok`) və uğursuzluq (`Error`) hallarını bir tipdə birləşdirir.
sealed class Result<T> {
  const Result();

  /// Uğurlu nəticə: [value] daşıyır.
  const factory Result.ok(T value) = Ok._;

  /// Uğursuz nəticə: [error] daşıyır.
  const factory Result.error(Exception error) = Error._;
}

/// Uğurlu nəticə.
final class Ok<T> extends Result<T> {
  const Ok._(this.value);
  final T value;
}

/// Uğursuz nəticə.
final class Error<T> extends Result<T> {
  const Error._(this.error);
  final Exception error;
}

// ══ İstifadə: switch ifadəsi ══
final Result<UserProfile> result = await repository.fetchProfile();

switch (result) {
  case Ok<UserProfile>():
    userProfile = result.value;
  case Error<UserProfile>():
    error = result.error;
}

// ══ Daha yığcam: pattern ilə dəyəri birbaşa çıxarmaq ══
final message = switch (result) {
  Ok(:final value) => 'Salam, ${value.name}',
  Error(:final error) => 'Xəta: $error',
};

// ══ Yalnız bir hal lazımdırsa: if-case ══
if (result case Ok(:final value)) {
  print(value.name);
}

// ══ Kompilyator nə tutur ══
// Aşağıdaki switch `Error` halını əhatə etmir:
//   final x = switch (result) {
//     Ok(:final value) => value.name,
//   };
// → "The type 'Result<UserProfile>' is not exhaustively matched"
// Yəni xəta halını unutmaq KOMPİLYASİYA xətasıdır.

Rəsmi sənədin verdiyi `Result` sinfi və `switch` ilə istifadəsi.

Adlandırma tələsi. Rəsmi nümunədə uğursuzluq sinfinin adı Error-dur — və bu ad Dart-ın öz dart:core Error sinfi ilə üst-üstə düşür. Həmin faylda Error yazdıqda artıq Result-un Error-u nəzərdə tutulur; dart:core-un Error-una müraciət etmək üçün prefiksli import lazım gəlir.

Bu, kompilyasiya xətası deyil, lakin praktik qarışıqlıq mənbəyidir — xüsusilə catch (e) { if (e is Error) ... } kimi kodda.

Üç praktik variant:

  • Rəsmi adları saxlamaq (Ok / Error) və faylı kiçik, təmiz saxlamaq.
  • Ok / Err — qısa və qarışmır.
  • Success<T> / FailureResult<T> — daha uzun, lakin oxuyarkən şübhə qalmır.

Hansı seçilirsə, layihə boyu bir ad dəsti olsun; iki fərqli Result tipi olan kod bazası ən pis haldır.

ƏlavəFaydasıEhtiyat
`bool get isOk`Sadə şərtlərdə oxunaqlıdırDəyəri çıxarmır — sonra yenə `switch` lazım olur
`T? get valueOrNull`Fallback dəyər üçün rahat: `valueOrNull ?? []`Xətanı səssizcə udur — yalnız xəta əhəmiyyətsiz olduqda
`Result<R> map<R>(R Function(T) f)`Uğurlu dəyəri çevirir, xətanı ötürür`f` exception atarsa, o, bükülmür — özün tutmalısan
`R fold<R>(R Function(T) onOk, R Function(Exception) onError)`Hər iki halı bir ifadədə emal edir`switch` çox vaxt daha oxunaqlıdır — `fold` iki lambda tələb edir
`Result<void>`Dəyər qaytarmayan əməliyyatlar üçün (silmək, göndərmək)`Ok(null)` deyil, `Result<void>.ok(null)` yazılır — sintaksis qarışa bilər

Result-un qatlar arasında yolu. Praktikada belə görünür:

1. Repository exception-ları tutur və Result qaytarır: Future<Result<List<Order>>>. 2. Use-case (varsa) Result-u ötürür ya da bir neçəsini birləşdirir. 3. Notifier Result-u switch ilə açır və state-ə (ya da AsyncValue-a) çevirir. 4. Widget artıq Result görmür — yalnız state görür.

Sonuncu bənd vacibdir: Result UI-a çatmamalıdır. Widget-də switch (result) yazmaq iki qatı qarışdırır — widget xəta tipləri barədə qərar verməyə başlayır.

Riverpod ilə birlikdə. AsyncValue da sealed-dir (Riverpod 3), ona görə iki mexanizm bir-birini təkrarlaya bilər. Praktik bölgü:

  • Oxuma: Result-a ehtiyac yoxdur — AsyncNotifier.build exception-ı AsyncValue.error-a çevirir.
  • Yazma / əmr: Result faydalıdır — nəticəni notifier-in metodu qaytarır, view isə ona görə snackbar göstərir ya da naviqasiya edir.

Praktika. lib/utils/result.dart faylını yuxarıdaki formada yarat (ad dəstini özün seç, lakin bir dəfə seç). Sonra bir yazma əməliyyatını (submit, delete, redeem kimi) Result üzərinə köçür: repository Result qaytarır, notifier-in metodu onu view-a ötürür, view isə uğur/xəta üçün fərqli snackbar göstərir.

Hazır sayılır: notifier-də try/catch yoxdur, widget-də Result tipi görünmür, və switch-dən bir halı silsən kompilyasiya xətası alırsan (bunu bir dəfə sınayıb geri qaytar — mexanizmin işlədiyini gözlə görmək faydalıdır).

📚 Mənbələr və sənədlər