فلٹر میں غلطیوں کو صحیح طریقے سے ہینڈل کرنے کا طریقہ: سیل شدہ کلاسز، ریکارڈز اور نتائج کی اقسام کے لیے ایک عملی گائیڈ۔

میں سوچتا تھا کہ میں اپنی فلٹر ایپس میں غلطیوں کو سنبھالنے کا اچھا کام کر رہا ہوں۔ میرے پاس ہر جگہ کوشش/کیچ بلاکس تھے۔ میں نے استثناء کو پکڑا، اسے لاگ ان کیا اور صارف کو غلطی کا پیغام دکھایا۔ یہ ٹھوس محسوس ہوا۔

پھر میں نے مزید غور سے دیکھنا شروع کیا کہ اصل میں پیداوار میں کیا ہو رہا ہے۔ ایک خاموش ناکامی تھی جس سے میں بالکل بے خبر تھا۔ ایسے فنکشنز ہیں جو پھینک سکتے ہیں لیکن ٹائپ سسٹم میں کچھ بھی انتباہ نہیں کرتے ہیں۔ خرابی سے نمٹنے کو پورے کوڈ بیس میں متضاد طور پر تقسیم کیا جاتا ہے۔ خرابی کچھ جگہوں پر ہوتی ہے اور کچھ میں نہیں۔

ٹیم کے ایک جونیئر ڈویلپر نے ایک نئی API کال شامل کی اور کوشش/کیچ کے بارے میں مکمل طور پر بھول گیا، اور کسی نے بھی اس کا جائزہ نہیں لیا کیونکہ کوڈ میں یہ نہیں کہا گیا تھا کہ "یہ فنکشن فیل ہو سکتا ہے”۔

تب سے، میں نے غلطی سے نمٹنے کو محض ایک دفاعی عادت کے بجائے ایک تعمیراتی فیصلے کے طور پر سنجیدگی سے لینا شروع کیا۔

اس مضمون میں فی الحال پروڈکشن فلٹر ایپس میں استعمال ہونے والے نمونوں کا احاطہ کیا گیا ہے (نتائج کی اقسام، سیل شدہ کلاسز، ڈارٹ 3 ریکارڈز، پیٹرن میچنگ) اور یہ کہ وہ غلطیوں کو قابل توجہ، واضح اور نظر انداز کرنے کے لیے ناممکن بنانے کے لیے کس طرح مل کر کام کرتے ہیں۔

انڈیکس

کیوں کوشش کرنا / اسے اکیلے حاصل کرنا کافی نہیں ہے۔

کوشش/کیچ کام کرتا ہے۔ میں یہ نہیں کہہ رہا ہوں کہ ایسا نہیں ہے۔ سادہ معاملات میں یہ بالکل ٹھیک ہے۔ تاہم، جیسے جیسے آپ کی ایپ بڑھتی ہے، پہلے سے طے شدہ غلطی سے نمٹنے کی حکمت عملی کے طور پر ٹرائی/کیچ کا استعمال کرتے ہوئے مسائل کا ایک مخصوص سیٹ متعارف کرایا جاتا ہے جو بڑے پیمانے پر ظاہر ہوتے ہیں۔

مسئلہ یہ ہے کہ آپ اسے نہیں دیکھ سکتے۔

جب ایک فنکشن ایک استثناء پھینک سکتا ہے، تو اس کے دستخط میں اس کی نشاندہی کرنے کے لئے کچھ بھی نہیں ہے۔ یہ دیکھو:

Future getUser(String userId) async {
  final response = await dio.get('/users/$userId');
  return User.fromJson(response.data);
}

ایسا لگتا ہے کہ یہ فنکشن ہمیشہ صارف کو واپس کرتا ہے۔ دستخط کے بارے میں کچھ بھی نہیں بتاتا ہے کہ یہ ناکام ہوسکتا ہے۔ اس فنکشن کو کال کرنے والے ڈویلپرز کو معلوم نہیں ہوگا کہ آیا اسے ٹرائی/کیچ میں لپیٹنا ہے جب تک کہ وہ نفاذ کو نہ پڑھ لیں یا اس فنکشن سے پہلے جل گئے ہوں۔

اب تصور کریں کہ اس فنکشن کو پوری ایپ میں 10 مختلف جگہوں پر بلایا گیا ہے۔ کچھ ڈویلپرز غلطی سے نمٹنے کو یاد رکھتے ہیں۔ دوسرے ایسا نہیں کرتے۔ کوئی کمپائلر انتباہات نہیں ہیں، کوئی لنٹ قواعد نہیں ہیں، اور تضادات کو پکڑنے کے لئے کچھ بھی نہیں ہے۔ غلطی تب تک نظر نہیں آئے گی جب تک صارف کریش کی اطلاع نہیں دیتا۔

دوسرا مسئلہ یہ ہے کہ مستثنیات متعدی ہیں۔

جب کوئی فنکشن ہوتا ہے، تمام کال کرنے والوں کو اسے ہینڈل کرنا چاہیے۔ اور اس بھیجنے والے سے تمام بھیجنے والے۔ ذمہ داری کو سنبھالنے میں غلطی اکثر کوڈ بیس کے ذریعے غیر مستقل طور پر پھیل جاتی ہے۔ کچھ پرتیں خاموشی سے مستثنیات کو نگل جاتی ہیں۔ دوسرے دوبارہ پھینک دیتے ہیں۔ ایپ کے ذریعے غلطی کے بہاؤ کا اندازہ لگانا مشکل ہو جاتا ہے۔

تیسرا مسئلہ یہ ہے کہ تمام غلطیاں غیر معمولی نہیں ہوتیں۔

نیٹ ورک کی درخواست کی ناکامی موبائل ایپس میں کوئی غیر معمولی واقعہ نہیں ہے۔ یہ متوقع ہے۔ اسے ایک استثناء کے طور پر ماننا ایک ناقص ذہنی نمونہ ہے، ایک اسامانیتا جو معمول کے بہاؤ میں خلل ڈالتی ہے۔ یہ ایک عام تلاش ہے اور اسے کسی بھی دوسری تلاش کی طرح برتا جانا چاہیے۔

یہ نتائج کی اقسام کی کلیدی بصیرت ہے۔ غلطی کوئی رکاوٹ نہیں ہے، یہ ایک قدر ہے۔

اقدار کے طور پر غلطیاں: کلیدی خیالات

خیال سادہ ہے۔ کسی فنکشن کی بجائے کسی قدر واپس کرنے یا استثناء کو بڑھانے کے، یہ ہمیشہ ایک قدر لوٹاتا ہے، لیکن وہ قدر کامیابی یا ناکامی کی نشاندہی کر سکتی ہے۔

// Instead of this — may or may not throw
Future getUser(String userId);

// We write this — always returns a result
Future> getUser(String userId);

اب فنکشن کا دستخط ایماندار ہے۔ "یہ کامیاب ہوسکتا ہے یا یہ ناکام ہوسکتا ہے، اور آپ کو دونوں سے نمٹنے کی ضرورت ہے،” یہ آپ کو بتاتا ہے۔ مرتب کرنے والا صارف کو دونوں صورتوں کو سنبھالنے پر مجبور کرتا ہے۔ غلطی سے ناکامی کے راستے کو اوور رائڈ کرنے کا کوئی طریقہ نہیں ہے۔

یہ نمونہ Rust اور Kotlin جیسی زبانوں سے آتا ہے، جو ان کی معیاری لائبریریوں میں بنی ہیں۔ ڈارٹ میں، ہم اسے خود بناتے ہیں۔ ڈارٹ 3 مہر بند کلاسز اور پیٹرن میچنگ کے ساتھ چیزوں کو پہلے سے کہیں زیادہ صاف ستھرا بناتا ہے۔

سیل شدہ کلاسوں کا استعمال کرتے ہوئے نتائج کی اقسام بنانا

نتائج کی اقسام جو ہم پیداوار میں استعمال کرتے ہیں وہ ہیں:

// result.dart

// Sealed means every possible subtype is defined
// right here in this file. The compiler knows
// there are exactly two possible outcomes —
// Success and Failure — and nothing else.
sealed class Result {}

// Success carries the value we wanted.
// T is the type parameter — Result means
// Success carries a User, Result> carries a list.
class Success extends Result {
  final T data;
  const Success(this.data);
}

// Failure carries an AppError describing what went wrong.
// We use a typed error class rather than a raw exception
// so the UI can make decisions based on the error type.
class Failure extends Result {
  final AppError error;
  const Failure(this.error);
}

اب ہمیں ایرر کلاس درج کرنے کی ضرورت ہے۔ خام استثنائی پیغام کو منتقل کرنے کے بجائے، آپ مخصوص خامیوں کی وضاحت کرتے ہیں جو آپ کی ایپ پیدا کر سکتی ہے۔

// app_error.dart

// AppError is also sealed — every error type our app
// can produce is defined here. This makes it impossible
// to have an unhandled error type slip through.
sealed class AppError {}

// No internet connection
class NoInternetError extends AppError {}

// The server returned an error response
class ServerError extends AppError {
  final int statusCode;
  final String message;
  const ServerError({required this.statusCode, required this.message});
}

// The data came back in an unexpected format
class ParseError extends AppError {
  final String message;
  const ParseError(this.message);
}

// Something unexpected happened that we didn't anticipate
class UnknownError extends AppError {
  final String message;
  const UnknownError(this.message);
}

اب آئیے اسے اپنے ذخیرے میں آزماتے ہیں۔

// post_repository.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:dio/dio.dart';
import 'result.dart';
import 'app_error.dart';
import 'post.dart';

class PostRepository {
  final Dio _dio;
  PostRepository(this._dio);

  Future>> getPosts() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/posts',
      );

      // Parse the response into a list of Post objects.
      // We wrap this in its own try/catch because parsing
      // can fail independently of the network call —
      // the API might return valid JSON but in an unexpected shape.
      try {
        final List data = response.data as List;
        final posts = data
            .map((json) => Post.fromJson(json as Map))
            .toList();

        // Wrap the success value in Success>
        // and return it. The caller receives a Result,
        // not a raw list, so they know they have to
        // check whether it succeeded or failed.
        return Success(posts);
      } catch (e) {
        return Failure(ParseError('Failed to parse posts: $e'));
      }
    } on DioException catch (e) {
      // Map Dio's exception types to our own AppError types.
      // This keeps Dio-specific types out of the rest of the app.
      // If we ever swap Dio for a different HTTP client,
      // only this file needs to change.
      if (e.type == DioExceptionType.connectionError) {
        return Failure(NoInternetError());
      }

      return Failure(
        ServerError(
          statusCode: e.response?.statusCode ?? 0,
          message: e.message ?? 'Server error',
        ),
      );
    } catch (e) {
      // Catch-all for anything unexpected
      return Failure(UnknownError(e.toString()));
    }
  }
}

غور کریں کہ کیا بدلا ہے۔ فنکشن دستخط Future>> میں اب ایماندار ہوں۔ جو بھی بلاتا ہے۔ getPosts() وہ جانتے ہیں کہ انہیں نتائج مل رہے ہیں۔ آپ ہر وقت کامیاب ہونے کا دکھاوا نہیں کر سکتے۔ اور کوشش/کیچ مکمل طور پر ذخیرہ کے اندر موجود ہے۔ بھیجنے والے کو کچھ بھی نہیں دیا گیا ہے۔

ڈارٹ 3 کی تاریخ اور اضافی چیزیں

اس سے پہلے کہ ہم پیٹرن کی مماثلت میں آجائیں، یہ Dart 3 ریکارڈز کے بارے میں بات کرنے کے قابل ہے کیونکہ وہ قدرتی طور پر نتائج کی اقسام کے ساتھ جوڑتے ہیں۔

ریکارڈ ہلکے وزن والے، گمنام آبجیکٹ ہوتے ہیں جو پوری کلاس کی وضاحت کیے بغیر متعدد اقدار کو ایک ساتھ گروپ کرتے ہیں۔ اسے فنکشن سے متعدد اقدار واپس کرنے کا ایک تیز طریقہ سمجھیں۔

// Before records — you needed a class or a Map
// to return multiple values
Map getUserInfo() {
  return {'name': 'Nicholas', 'age': 28};
  // No type safety — 'age' could be anything
}

// With records — type safe, no class needed
(String name, int age) getUserInfo() {
  return ('Nicholas', 28);
  // The compiler knows name is a String and age is an int
}

جب آپ کو قدر کے ساتھ کچھ میٹا ڈیٹا واپس کرنے کی ضرورت ہو تو ریکارڈز غلطی سے نمٹنے کے لیے مفید ہوتے ہیں۔

// A function that returns a post and its fetch timestamp
Future> getPostWithTimestamp(
  String postId,
) async {
  try {
    final response = await _dio.get('/posts/$postId');
    final post = Post.fromJson(response.data);

    // The record (post, DateTime.now()) groups both values
    // without needing a wrapper class
    return Success((post, DateTime.now()));
  } catch (e) {
    return Failure(UnknownError(e.toString()));
  }
}

اور اسے کھائیں:

final result = await repository.getPostWithTimestamp('1');

switch (result) {
  case Success(:final data):
    // Destructure the record directly in the pattern
    final (post, fetchedAt) = data;
    print('Got \({post.title} at \)fetchedAt');
  case Failure(:final error):
    print('Failed: $error');
}

نتائج کی قسم کے لیے ریکارڈز ضروری نہیں ہیں، لیکن آپ کو ایک چھوٹی مددگار طبقے کی ضرورت نہیں ہے جو دو یا تین اقدار کو ایک ساتھ پاس کرنے کے لیے خالصتاً موجود ہو۔ میں اسے باقاعدگی سے صفحہ بندی کرسر یا ریپوزٹری طریقوں میں استعمال کرتا ہوں جن کو کیش میٹا ڈیٹا کے ساتھ ڈیٹا واپس کرنے کی ضرورت ہوتی ہے۔

غلطیوں کے لیے پیٹرن کا ملاپ

یہ وہ جگہ ہے جہاں سب کچھ اکٹھا ہوتا ہے۔ سیل شدہ کلاسز اور پیٹرن میچنگ کا مطلب ہے کہ آپ کمپائلر کو تمام ممکنہ نتائج پر کارروائی کرنے پر مجبور کرتے ہیں۔ ہم غلطی سے ناکامی کی مثالوں کو نظر انداز نہیں کر سکتے۔

final result = await repository.getPosts();

switch (result) {
  // Named field pattern — extracts 'data' directly
  // from Success without a manual cast
  case Success(:final data):
    print('Got ${data.length} posts');

  case Failure(:final error):
    // Now pattern match on the error type
    // to give the user the right message
    switch (error) {
      case NoInternetError():
        print('No internet connection. Please check your connection.');
      case ServerError(:final statusCode, :final message):
        print('Server error \(statusCode: \)message');
      case ParseError(:final message):
        print('Something went wrong parsing the data: $message');
      case UnknownError(:final message):
        print('Unexpected error: $message');
    }
}

دونوں سوئچ کے دروازے مکمل ہیں۔ اگر آپ ایک نیا نتیجہ ذیلی قسم شامل کرتے ہیں اور اسے یہاں ہینڈل کرنا بھول جاتے ہیں، تو آپ کو تالیف کی غلطی ملے گی۔ اگر آپ ایک نیا AppError ذیلی قسم شامل کرتے ہیں اور اسے یہاں ہینڈل کرنا بھول جاتے ہیں، تو آپ کو تالیف کی خرابی ملے گی۔ کمپائلر کوالٹی کنٹرول کے طور پر کام کرتا ہے۔

آپ بھی استعمال کر سکتے ہیں: when مزید جامع پروسیسنگ کے لیے توسیع کے نمونے:

// A helper extension that makes Result easier to consume
extension ResultExtension on Result {
  // Runs onSuccess if this is a Success,
  // runs onFailure if this is a Failure
  R when({
    required R Function(T data) onSuccess,
    required R Function(AppError error) onFailure,
  }) {
    return switch (this) {
      Success(:final data) => onSuccess(data),
      Failure(:final error) => onFailure(error),
    };
  }

  // Returns the data if Success, null if Failure
  T? getOrNull() => switch (this) {
    Success(:final data) => data,
    Failure() => null,
  };

  // Returns true if this is a Success
  bool get isSuccess => this is Success;

  // Returns true if this is a Failure
  bool get isFailure => this is Failure;
}

استعمال بہت واضح ہو گیا ہے۔

final result = await repository.getPosts();

final posts = result.when(
  onSuccess: (data) => data,
  onFailure: (error) => [],
);

اسے اصل بلاک فنکشن پر لگائیں۔

آئیے ہر چیز کو ایک مکمل بلاک میں جوڑتے ہیں۔ آئیے پوسٹس کی فعالیت کا استعمال کریں جو ہم نے پچھلے سیشن میں بنایا تھا اور نتائج کی اقسام کو استعمال کرنے کے لیے اسے اپ گریڈ کریں۔

ریاستیں اب سیل شدہ کلاسیں حاصل کر رہی ہیں:

// post_state.dart

sealed class PostState {}

class PostInitial extends PostState {}

class PostLoading extends PostState {}

// Success state carries the posts directly
class PostLoaded extends PostState {
  final List posts;
  const PostLoaded(this.posts);
}

// Error state carries a typed AppError, not just a string.
// This means the UI can make decisions based on the
// error type — show a "no internet" message vs a
// "server error" message vs a "try again" message.
class PostError extends PostState {
  final AppError error;
  const PostError(this.error);
}

بلاک:

// post_bloc.dart

class PostBloc extends Bloc {
  final PostRepository _repository;

  PostBloc(this._repository) : super(PostInitial()) {
    on(_onLoadPosts);
  }

  Future _onLoadPosts(
    LoadPosts event,
    Emitter emit,
  ) async {
    emit(PostLoading());

    // getPosts() now returns Result>
    // We pattern match on the result directly —
    // no try/catch needed here because the repository
    // already handles all error cases and wraps them
    // in a Failure. The Bloc just reads the result.
    final result = await _repository.getPosts();

    switch (result) {
      case Success(:final data):
        emit(PostLoaded(data));
      case Failure(:final error):
        emit(PostError(error));
    }
  }
}

بلاک کے پاس کوئی کوشش/کیچ نہیں ہے۔ ریپوزٹری غلطی سے نمٹنے کا مالک ہے۔ بلاک نتائج کو پڑھتا ہے اور صحیح حیثیت کا اخراج کرتا ہے۔ یہ صاف اور سادہ ہے، اور ہر پرت بالکل ایک کام کرتی ہے۔

UI:

// post_screen.dart

BlocBuilder(
  builder: (context, state) {
    return switch (state) {
      PostInitial() => const Center(
          child: Text('Press the button to load posts'),
        ),

      PostLoading() => const Center(
          child: CircularProgressIndicator(),
        ),

      PostLoaded(:final posts) => ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            final post = posts[index];
            return ListTile(
              leading: Text('${post.id}'),
              title: Text(post.title),
              subtitle: Text(post.body),
            );
          },
        ),

      // Pattern match on the error type to show
      // the right message for each specific error.
      // This is something try/catch cannot give you —
      // typed, structured errors that the UI can act on.
      PostError(:final error) => Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                switch (error) {
                  NoInternetError() =>
                    'No internet connection. Please check your connection.',
                  ServerError(:final statusCode) =>
                    'Server error ($statusCode). Please try again.',
                  ParseError() =>
                    'Something went wrong. Please try again.',
                  UnknownError() =>
                    'An unexpected error occurred.',
                },
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () {
                  context.read().add(LoadPosts());
                },
                child: const Text('Try again'),
              ),
            ],
          ),
        ),
    };
  },
)

UI اب ہر غلطی کی قسم کے لیے مختلف پیغامات دکھاتا ہے۔ انٹرنیٹ کے بغیر صارفین کو سرور کی خرابی کا سامنا کرنے والے صارفین سے مختلف پیغام موصول ہوگا۔ یہ عام "کچھ غلط ہے” پیغام سے کہیں زیادہ بہتر صارف کا تجربہ ہے۔ اور یہ براہ راست داخل کردہ غلطی سے آتا ہے، خام استثنائی پیغام سے نہیں۔

یہ نقطہ نظر کب فائدہ مند ہے اور کب نہیں؟

میں یہاں ایماندار ہونا چاہتا ہوں۔ کیونکہ ہم نے دیکھا ہے کہ ڈویلپرز کو اچھے فن تعمیر کے نام پر سادہ چیزوں سے زیادہ انجینئر کرتے ہیں۔

نتائج کی اقسام استعمال کریں جب:

  • ایک فنکشن کئی منفرد طریقوں سے ناکام ہو سکتا ہے جسے کال کرنے والے کو مختلف طریقے سے ہینڈل کرنا چاہیے۔

  • آپ ایک سٹوریج یا سروس لیئر بنا رہے ہیں جس پر متعدد فنکشنز انحصار کرتے ہیں۔

  • آپ ایک ایسی ٹیم پر کام کرتے ہیں جہاں غلطیوں سے نمٹنا ایک سنگین مسئلہ ہے۔

  • اس میں کچھ بھی شامل ہے جو خطرے میں ہے: رقم، صارف کا ڈیٹا، یا خاموش غلطیاں۔

درج ذیل صورتوں میں ٹرائی/کیچ استعمال کریں:

  • یہ ایک چھوٹی سی خصوصیت کے ساتھ ایک سادہ، ایک وقتی کام ہے۔

  • غلطی سے ہینڈل کرنا یکساں ہے قطع نظر اس کے کہ کیا غلط ہوا ہے۔ پیغامات دکھائیں، لاگ کریں، مکمل کریں۔

  • آپ پروٹو ٹائپنگ کر رہے ہیں یا ابتدائی ترقی میں ہیں، اور فن تعمیر مسلسل بدل رہا ہے۔

  • اضافی پیچیدگی کوڈ بیس کے سائز کی طرف سے جائز نہیں ہے.

نتیجہ کی قسم کا نمونہ شعور میں اضافہ کرتا ہے۔ اس سے انکار کی کوئی وجہ نہیں ہے۔ ایک سادہ کوشش/کیچ کے لیے کم کوڈ کی ضرورت ہوتی ہے۔ منفی پہلو یہ ہے کہ کوشش/کیچ پوشیدہ ہے۔ کال کرنے والے کو غلطی کو سنبھالنے پر مجبور کرنے والی کوئی چیز نہیں ہے۔ نتیجہ کی قسم واضح ہے۔ قسم کا نظام اس کو نافذ کرتا ہے۔

پروڈکشن ایپس کے لیے جو حقیقی صارفین کی خدمت کرتی ہیں اور ان پر ایک سے زیادہ ڈویلپر کام کرتے ہیں، یہ واضح اضافی کوڈ کے قابل ہے۔ ایک ضمنی پروجیکٹ کے لیے جو آپ خود بناتے ہیں، یہ حد سے زیادہ ہو سکتا ہے۔

آخر سے آخر تک مثال

سب کچھ ایک مکمل خصوصیت میں شامل ہے۔ اسے ایک نئے فلٹر پروجیکٹ میں کاپی کریں اور اسے چلائیں۔

فولڈر کی ساخت:

lib/
  core/
    result.dart
    app_error.dart
  models/
    post.dart
  data/
    post_repository.dart
  bloc/
    post_bloc.dart
    post_event.dart
    post_state.dart
  ui/
    post_screen.dart
  main.dart

نتیجہ ڈارٹ:

sealed class Result {}

class Success extends Result {
  final T data;
  const Success(this.data);
}

class Failure extends Result {
  final AppError error;
  const Failure(this.error);
}

extension ResultExtension on Result {
  R when({
    required R Function(T data) onSuccess,
    required R Function(AppError error) onFailure,
  }) {
    return switch (this) {
      Success(:final data) => onSuccess(data),
      Failure(:final error) => onFailure(error),
    };
  }
}

app_error.dart:

sealed class AppError {}

class NoInternetError extends AppError {}

class ServerError extends AppError {
  final int statusCode;
  final String message;
  const ServerError({required this.statusCode, required this.message});
}

class ParseError extends AppError {
  final String message;
  const ParseError(this.message);
}

class UnknownError extends AppError {
  final String message;
  const UnknownError(this.message);
}

post.dart:

class Post {
  final int id;
  final String title;
  final String body;
  final int userId;

  const Post({
    required this.id,
    required this.title,
    required this.body,
    required this.userId,
  });

  factory Post.fromJson(Map json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
      userId: json['userId'] as int,
    );
  }
}

post_repository.dart:

import 'package:dio/dio.dart';
import '../core/result.dart';
import '../core/app_error.dart';
import '../models/post.dart';

class PostRepository {
  final Dio _dio;
  PostRepository(this._dio);

  Future>> getPosts() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/posts',
      );

      try {
        final List data = response.data as List;
        final posts = data
            .map((json) => Post.fromJson(json as Map))
            .toList();
        return Success(posts);
      } catch (e) {
        return Failure(ParseError('Failed to parse posts: $e'));
      }
    } on DioException catch (e) {
      if (e.type == DioExceptionType.connectionError) {
        return Failure(NoInternetError());
      }
      return Failure(
        ServerError(
          statusCode: e.response?.statusCode ?? 0,
          message: e.message ?? 'Server error',
        ),
      );
    } catch (e) {
      return Failure(UnknownError(e.toString()));
    }
  }
}

post_event.dart:

sealed class PostEvent {}

class LoadPosts extends PostEvent {}

post_state.dart:

import '../core/app_error.dart';
import '../models/post.dart';

sealed class PostState {}

class PostInitial extends PostState {}
class PostLoading extends PostState {}

class PostLoaded extends PostState {
  final List posts;
  const PostLoaded(this.posts);
}

class PostError extends PostState {
  final AppError error;
  const PostError(this.error);
}

post_bloc.dart:

import 'package:flutter_bloc/flutter_bloc.dart';
import '../core/result.dart';
import '../data/post_repository.dart';
import 'post_event.dart';
import 'post_state.dart';

class PostBloc extends Bloc {
  final PostRepository _repository;

  PostBloc(this._repository) : super(PostInitial()) {
    on(_onLoadPosts);
  }

  Future _onLoadPosts(
    LoadPosts event,
    Emitter emit,
  ) async {
    emit(PostLoading());

    final result = await _repository.getPosts();

    switch (result) {
      case Success(:final data):
        emit(PostLoaded(data));
      case Failure(:final error):
        emit(PostError(error));
    }
  }
}

post_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/post_bloc.dart';
import '../bloc/post_event.dart';
import '../bloc/post_state.dart';
import '../core/app_error.dart';

class PostScreen extends StatelessWidget {
  const PostScreen({super.key});

  String _errorMessage(AppError error) {
    return switch (error) {
      NoInternetError() =>
        'No internet connection. Please check your connection.',
      ServerError(:final statusCode) =>
        'Server error ($statusCode). Please try again.',
      ParseError() => 'Something went wrong. Please try again.',
      UnknownError() => 'An unexpected error occurred.',
    };
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Posts')),
      body: BlocBuilder(
        builder: (context, state) {
          return switch (state) {
            PostInitial() => const Center(
                child: Text('Press the button to load posts'),
              ),
            PostLoading() => const Center(
                child: CircularProgressIndicator(),
              ),
            PostLoaded(:final posts) => ListView.builder(
                itemCount: posts.length,
                itemBuilder: (context, index) {
                  final post = posts[index];
                  return ListTile(
                    leading: Text('${post.id}'),
                    title: Text(post.title),
                    subtitle: Text(post.body),
                  );
                },
              ),
            PostError(:final error) => Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(_errorMessage(error)),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: () {
                        context.read().add(LoadPosts());
                      },
                      child: const Text('Try again'),
                    ),
                  ],
                ),
              ),
          };
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read().add(LoadPosts()),
        child: const Icon(Icons.download),
      ),
    );
  }
}

main.dart:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/post_bloc.dart';
import 'data/post_repository.dart';
import 'ui/post_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Result Type Demo',
      home: BlocProvider(
        create: (_) => PostBloc(PostRepository(Dio())),
        child: const PostScreen(),
      ),
    );
  }
}

حتمی خیالات

اس میں سے کوئی بھی ہوشیار ہونے یا فی ایک پیٹرن کی پیروی کرنے کے بارے میں نہیں ہے۔ یہ غلطیوں کو قابل توجہ بنانے کے بارے میں ہے۔

غلطی سے نمٹنے کے واحد ٹول کے طور پر try/catch کے ساتھ بنیادی مسئلہ یہ ہے کہ یہ عام نظر آنے والے فنکشن کے دستخط کے پیچھے ناکامی کے امکان کو چھپاتا ہے۔ نتیجے میں آنے والی قسم قسم کے نظام میں اپنے امکانات کو ظاہر کرتی ہے، جہاں مرتب کرنے والا اسے مستقل طور پر سنبھالنے میں مدد کر سکتا ہے۔

مہر بند کلاسز، ٹائپ شدہ غلطیاں، پیٹرن میچنگ، اور ڈارٹ 3 ریکارڈز کا مجموعہ ہمیں درج ذیل سسٹم فراہم کرتا ہے:

  • افعال اس بارے میں ایماندار ہیں کہ وہ کیا واپس کر سکتے ہیں۔

  • غلطی کی تمام اقسام کو واضح طور پر سنبھالا جاتا ہے۔

  • غلطی کی نئی قسم کو شامل کرنے سے کوئی بھی سوئچ خود بخود ٹوٹ جائے گا جو اسے ہینڈل نہیں کرتے ہیں۔

  • UI درست غلطی کے لیے صحیح پیغام دکھا سکتا ہے۔

کاش میں نے اپنی پہلی پروڈکشن ایپ اس طرح بنائی ہوتی۔ اس سے خاموش ناکامیوں اور غلطی کے متضاد حالات کا سراغ لگانے میں کافی وقت بچ جاتا۔

اگر آپ ٹرائی/کیچ سے پہلے ہی واقف ہیں اور ایک قدم آگے بڑھ کر غلطی کو سنبھالنا چاہتے ہیں تو چھوٹی شروعات کریں۔ نتائج کی اقسام کو ایک ذخیرہ میں شامل کریں۔ دیکھیں کیسا محسوس ہوتا ہے۔ ایک بار جب آپ اس وضاحت کا تجربہ کرتے ہیں جو پیٹرن لاتے ہیں، وہ قدرتی طور پر پھیل جاتے ہیں۔

اوپر تک سکرول کریں۔