شرح كيفية استخدام الapi مع repostery في Flutter وتنظيم الكود

 

شرح كيفية استخدام الapi مع repostery في Flutter وتنظيم الكود

شرح كيفية استخدام الapi مع repostery في Flutter وتنظيم الكود

يعد استخدام الapi شيئ اساسي في عمل الشركات لانك تتعامل مع سيرفرات في نقل واستقبال البيانات ويكون هناك شخص Backend مختص بهذه العمليه وياتي دورنا كمطورين flutter لربط الapi مع الapplication ويوجد اكثر من طريقة للربط ولكن الاشهر هيا الdio والتي تم شرحها في دروس سابقة وفي هذا الدرس سوف نشرحها ولكن بشكل منظم اكثر وهيا باستخدام ملف الrepostery لتنظيم العملية .


يعد محرك Flutter ومكتبة Foundation وعناصر واجهة المستخدم جزءًا من إطار عمل Flutter ، والذي تم إنشاؤه بلغة برمجة Dart. يختلف نهج Flutter في التطوير عن الآخرين من حيث أنه يستخدم كتابة تعريفية لواجهة المستخدم. هناك حاجة للبدء من النهاية هنا ، مما يعني أنه قبل البدء في تصميم أي قطعة ، يجب أن يكون لدى المستخدم فكرة كاملة عن نوع واجهة المستخدم التي ستكون عليها. يعتبر العديد من المطورين أن كتابة واجهة المستخدم هذه أكثر وضوحًا ، ولكنها توفر بعض التحديات والمشاكل للمطورين في البداية.


تهيئة class لاستقبال العمليات من الapi

عليك ان تقوم اولا بتهيئة ملف الdio لكي يستقبل ويرسل معك البيانات يمكنك نسخ الكود التالي , وهنا قمنا بعمل get و post و delete وهذه اغلب انواع البيانات التي نتعامل معها خلال الapi يمكنك نسخ الكود التالي بدون تعديل واستخدامه .


تهيئة class لاستقبال العمليات من الapi

dio.dart


import 'package:dio/dio.dart';
import 'dart:async';

abstract class DioHelper {
  Future<dynamic> post({
    required String url,
    dynamic data,
    String? token,
  });

  Future<dynamic> get({
    required String url,
    dynamic query,
    String? token,
  });

  Future<dynamic> delete({
    required String url,
    dynamic data,
    String? token,
  });
}

class DioImpl extends DioHelper {
  final Dio dio = Dio(
    BaseOptions(
      baseUrl: baseUrl,
      receiveDataWhenStatusError: true,
    ),
  );

  @override
  Future post({
    required String url,
    dynamic data,
    String? token,
  }) async {
    dio.options.headers = {
      'Content-Type': 'application/json',
      if (token != null) 'Authorization': 'Bearer $token'
    };

    if (url.contains('??')) {
      url = url.replaceAll('??', '?');
    }
    print('URL => ${dio.options.baseUrl + url}');
    print('Header => ${dio.options.headers.toString()}');
    print('Body => $data');
    return await request(
      () async => await dio.post(url, data: data),
    );
  }

  @override
  Future get({required String url, query, String? token}) async {
    dio.options.headers = {
      'Content-Type': 'application/json',
      if (token != null) 'Authorization': 'Bearer $token'
    };

    if (url.contains('??')) {
      url = url.replaceAll('??', '?');
    }
    print('URL => ${dio.options.baseUrl + url}');
    print('Header => ${dio.options.headers.toString()}');
    print('Body => $query');
    return await request(
          () async => await dio.get(url, queryParameters: query),
    );
  }

  @override
  Future delete({required String url, data, String? token}) async {
    dio.options.headers = {
      'Content-Type': 'application/json',
      if (token != null) 'Authorization': 'Bearer $token'
    };

    if (url.contains('??')) {
      url = url.replaceAll('??', '?');
    }

    return await request(
          () async => await dio.delete(url, queryParameters: data),
    );
  }
}

extension on DioHelper {
  Future request(Future<Response> Function() request) async {
    try {
      final r = await request.call();
            print("Response => ${r.data}");

      return r;
    } on DioError catch (e) {
            print("Error => ${e.response!.data['error']}");

      throw ServerException(
        error: e.response!.data['error'],
        code: e.response!.statusCode!,
      );
    } catch (e) {
      throw Exception();
    }
  }
  


كيفية تخطي الerror الذي يتم الحصول عليه من الapi في بعض الوقت

احيانا نحصل على مشاكل في الapi ولهذا نحتاج ان نقوم بتهيئة ملفات للتعامل مع فشل البيانات التي نحصل عليها من الapi وكما هو موضح في الصوره , يمكنك نسخ الاكواد التاليه وعمل ملفات بالاسماء الخاصه بهم كما هو موضح في الصوره والاكواد لا يوجد بها اي تغيير .


كيفية تخطي الerror الذي يتم الحصول عليه من الapi في بعض الوقت

Exception.dart



class ServerException implements Exception {
  final String error;
  final int code;

  ServerException({
    required this.error,
    required this.code,
  });
}

class CacheException implements Exception {
  final dynamic error;

  CacheException(this.error);
}

Failure.dart

abstract class Failure extends Equatable {
  @override
  List<Object> get props => [];
}

// General failures
class ServerFailure extends Failure {
  final String error;
  final int code;

  ServerFailure({
    required this.error,
    required this.code,
  });
}

class CacheFailure extends Failure {}

String mapFailureToMessage(Failure failure) {
  switch (failure.runtimeType) {
    case ServerFailure:
      return (failure as ServerFailure).error.toString();
    case CacheFailure:
      return cacheFailureMessage;
    default:
      return 'Unexpected error';
  }
}

int mapFailureToCode(Failure failure) => (failure as ServerFailure).code;


إنشاء class للعنوان النهائي للapi

الان نقوم بعمل ملف يحتوي على كل الend point التي نحتاجها في الكود وايضا في الاعلى نضع الرابط الرئيسي او الbase url كما هو متعارف عليه .


إنشاء class للعنوان النهائي للapi

endPoint.dart


const baseUrl = 'https://###/';

const categoryNameUrl = 'category';
const updateProfileUrl = 'update_profile';
const orderUrl = 'order';
const homeDatakUrl = 'home_data';


كتابة logic الapi بداخل الRepository

الان ناتي لاهم خطوة في المقالة وهيا تهيئة ملف الRepository وهنا نقوم بعمل class الRepository ونضع به ونضع به نوع البيانات التي نريدها والتي نريد ارسالها بالنسبة اذا كان هناك token فلا نحتاج الى ان نقوم بكتابته وبعدها نقوم بعمل class للRepoImplementation وهنا نقوم بكتابة الكود ونرسل له البيانات التي يحتاجها سواء token او data ولكن هنا قمنا بوضع الend url مختلف عن الصفحة السابقة وهذا الامر خاطئ ولكن يمكنك استخدام الendPoint الموجوده من الاعلى في مكان الurl . وهنا وضعنا لكم اكثر من مثال لاكثر من نوع بيانات لايضاح الفكرة .


كتابة logic الapi بداخل الRepository

Repository.class


abstract class Repository {
    Future<Response> getHomeRepo();
  
  	Future<Response> getOrderDetailsRepo({
      required int id,
    });
  
    Future<Response> categoryDetailsRepo({
    required String categoryName,
 	});
  
    Future<Response> updateAccountRepo({
    required String name,
    required String email,
    required String phone,
    File? image,
    });
  
  class RepoImplementation extends Repository {
  final DioHelper dioHelper;
  final CacheHelper cacheHelper;

  RepoImplementation({
    required this.dioHelper,
    required this.cacheHelper,
  });
    
  @override
  Future<Response> getHomeRepo() async {
    return await dioHelper.get(
      url: homeData,
    );
  }
    
  @override
  Future<Response> getOrderDetailsRepo({
    required int id
    }) async {
    return await dioHelper.get(
      url: '$order$id',
      token: token,
    );
  }
    
  @override
  Future<Response> categoryDetailsRepo({
    required String categoryName,
  }) async {
    return await dioHelper.get(
      url: categoryUrl,
      query: {
        'category': categoryName,
      },
    );
  }
    
  @override
  Future<Response> updateAccountRepo({
    required String name,
    required String email,
    required String phone,
    File? image,
  }) async {
    return await dioHelper.post(
      url: updateProfile,
      token: token,
      data: FormData.fromMap(
        {
          'name': name,
          'email': email,
          'phone': phone,
          if (image != null)
            'image': await MultipartFile.fromFile(
              image.path,
              filename: Uri.file(image.path).pathSegments.last,
            ),
        },
      ),
    );
  }
  


التعامل مع الapi بإستخدام الbloc

الان ناتي للكود الموجود داخل ملف الbloc ونقوم بكتابة اسم الmodel كما هو موضح وبعدها ناخذ منه object وبعدها نكتب الmethode المسؤوله عن عملية جلب البيانات كما هو موضح ونطبع العمليات للتاكد من عملها بشكل جيد وتشغيلها بداخل الmain وتحديدا داخل الblocProvider اذا كنت تريد اختبار model .


التعامل مع الapi بإستخدام الbloc

cubit.dart


// categoryDetails ------------------- start

  TopBorrowModel? categoryDetailsModel;

  void categoryDetails({
    required String categoryName,
  }) async {
    categoryDetailsModel = null;
    debugPrint('categoryDetails------------loading');
    emit(CategoryLoading());
    await _repository
        .categoryDetailsRepo(categoryName: categoryName)
        .then((value) {
      // success
      categoryDetailsModel = TopBorrowModel.fromJson(value.data);
      debugPrint('categoryDetails------------success');
      emit(CategorySuccess());
    }).catchError((error) {
      // error
      debugPrint('categoryDetails------------error');
      debugPrint(error.toString());
      emit(Error(error.toString()));
    });
  }

// categoryDetails ------------------- end


إنشاء حالات داخل ملف state

الخطوة الاخيره وهيا من المفتروض عملها قبل الخطوة السابقة وهيا تهيئة ملف الstate عن طريق وضع علامة لتحميل البيانات ونجاحها وفشلها والفشل هنا يكون مره واحده فقط ولن نحتاج لاعادته في المرات المقبلة في العمليات ونكتفي بالنجاح وانتظار تحميل البيانات .

إنشاء حالات داخل ملف state

state.dart


@immutable
abstract class MainState {}

class CategoryLoading extends MainState {}

class CategorySuccess extends MainState {}

class Error extends MainState {
  final String error;
  Error(this.error);
}

}


لمزيد من مقالات فلاتر يمكنك متابعة احد المقالات التاليه :

تعليقات