DEV Community

zhonghua
zhonghua

Posted on • Edited on

HarmonyOS Sports Project Development: Packaging an Extremely Useful RCP Network Library (Part 3)

Core Technologies of HarmonyOS ## Sports Development ## Remote Communication Kit (Remote Communication Services)

In the previous articles, we have introduced in detail how to package a complete RCP network library and explored its core functions and advanced features. In this part, we will demonstrate how to use this network library in HarmonyOS sports projects to implement specific network request functions.

Preface

In HarmonyOS sports projects, network requests are a key part of implementing functionality. Whether it is obtaining sports data, synchronizing user information, or loading sports video resources, a stable, efficient, and easy-to-use network library is required. In this part, we will show how to use the packaged RCP network library to achieve these functions through practical code examples.

I. Packaging Exception Handling

In actual development, exception handling is an indispensable part of network requests. By defining custom exception classes, we can better manage various errors that may occur in network requests.

(I) Custom Exception Class

A ApiException class has been defined to encapsulate error information in API requests.

export class ApiException extends Error {
  apiCode?: number;
  apiMessage?: string;

  constructor(apiCode?: number, apiMessage?: string) {
    super();
    this.name = "ApiException";
    this.apiCode = apiCode;
    this.apiMessage = apiMessage;
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Custom Properties: apiCode and apiMessage are used to store the error code and error message returned by the API.
  2. Constructor: The constructor initializes the exception object and sets the error code and error message.

II. Packaging Network Requests

To simplify the implementation of network requests, you have packaged an ApiRequest class to manage network requests and error handling uniformly.

(I) Singleton Pattern

The ApiRequest class uses the singleton pattern to ensure that there is only one global instance.


import { DataResult, ErrorCodes, ErrorData, LibToast, NetworkException, SuccessData } from "lib_base";
import { RcpNetworkService } from "lib_base/src/main/ets/utils/rcpnet/RcpService";
import { ApiResult } from "../data/models/ApiResult";
import { ApiException } from "./ApiException";

export interface IApiRequestHandleBusinessError{
  handleBusinessError(apiException: ApiException): boolean
}

export class ApiRequest{

  private static instance: ApiRequest


  static getInstance (): ApiRequest {
    if (!ApiRequest.instance) {
      ApiRequest.instance = new ApiRequest()
    }
    return ApiRequest.instance
  }

  private _net?: RcpNetworkService | undefined;
  private _i_api_equest_handle_business_error?: IApiRequestHandleBusinessError | undefined;

  public set i_api_equest_handle_business_error(value: IApiRequestHandleBusinessError | undefined) {
    this._i_api_equest_handle_business_error = value;
  }

  public set net(value: RcpNetworkService | undefined) {
    this._net = value;
  }

  public getService() : RcpNetworkService{
    return this._net!;
  }




}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Singleton Pattern: The getInstance method ensures the global uniqueness of the ApiRequest instance.
  2. Dependency Injection: The set method is used to inject RcpNetworkService and IApiRequestHandleBusinessError, enhancing the flexibility of the class.

(II) Unified Request Method

The ApiRequest class provides a unified request method remoteApi to handle network requests and encapsulate the return results.

export interface IApiRequestHandleBusinessError {
  handleBusinessError(apiException: ApiException): boolean;
}

export class ApiRequest {

  public static async remoteApi<T>(api:()=>Promise<ApiResult<T>>): Promise<DataResult<ApiResult<T>|null>>{
    try{
      const data = await api()
      if(data.success && data.success){
        let bean = new SuccessData<ApiResult<T>>(data)
        return bean;
      } else {
        let bean = ErrorData.fromError(new ApiException(data.code,data.msg) as Error,data.code?.toString(),data.msg)
        return bean;
      }
    }catch (e) {
      if(e instanceof NetworkException){
        let bean = ErrorData.fromError(e)
        // Request was canceled by the framework, client does not enter exception handling to avoid business pop-ups
        if(e.code !== ErrorCodes.REQUEST_CANCEL){
          LibToast.show(e.message)
        }
        return bean;
      }
      throw e as Error
    }

  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Request Execution: The specific network request is executed through the api function passed in.
  2. Success Handling: If the request is successful, a SuccessData object is returned.
  3. Failure Handling: If the request fails, an ErrorData object is returned based on the error type.
  4. Network Exception Handling: NetworkException is caught, and a prompt message is displayed based on the error code.

(III) Business Logic Handling

The ApiRequest class also provides a service method to handle business logic and errors.

export class ApiRequest {
  // ... Other code ...

  public static async service<T>(source:()=>Promise<DataResult<ApiResult<T>|null>>,
    request : (data:DataResult<T|null>)=>void,
    apiError : (apiError:ErrorData)=>void,
    netError? : (netError:ErrorData)=>void,
  ){
    let data = await source();
    if(data instanceof SuccessData){
      request(data.data)
    }else {
      let error = data as ErrorData
      if(error.error&&error.error instanceof ApiException){
        // Business exception
        if(ApiRequest.instance._i_api_equest_handle_business_error){
          ApiRequest.instance._i_api_equest_handle_business_error.handleBusinessError(error.error)
        }
        apiError(error)
      }else {
        // Network exception
        if(netError){
          netError(error)
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Request Execution: The network request is executed through the source function.
  2. Success Handling: If the request is successful, the request callback function is called to handle the data.
  3. Error Handling: The apiError or netError callback function is called based on the error type.
  4. Business Logic Separation: Business logic errors are handled through the IApiRequestHandleBusinessError interface.

III. Packaging Common Request Headers

In actual development, many requests need to carry common request headers, such as device information, user tokens, etc. You have packaged the common request headers through the CommonHeaderInterceptor class.

export async function getCommonHeaders(): Promise<Record<string, string>> {
  return {
    "device": LibDevice.getDeviceInfo(),
    "machineCode": await USystem.getUniqueKey(),
    "terminalType": "1", // Example terminal type
    "timestamp": new Date().toISOString(),
    "versionNumber": LibDevice.getAppVersionCode().toString(),
    "osId": Platform.appPlatform, // Example osId
    "versionCode": LibDevice.getAppVersionName(),
    "token": AccountManager.getToken()
  };
}

export class CommonHeaderInterceptor implements rcp.Interceptor {
  async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
    const commonHeaders = await getCommonHeaders();
    const keys = Object.keys(commonHeaders);
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      if (!context.request.headers) {
        context.request.headers = {};
      }
      context.request.headers[key] = commonHeaders[key];
    }
    return next.handle(context);
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Common Header Information: Common header information is obtained through the getCommonHeaders function.
  2. Interceptor Implementation: In the intercept method, common header information is added to the request headers.
  3. Dynamic Addition: Common header information is dynamically added through a loop to ensure that each request carries the necessary information.

IV. Packaging Response Converter

To better handle response data, a CommonTextResponseConverter class is packaged to handle response content.

export class CommonTextResponseConverter extends TextResponseConverter {
  convert(response: rcp.Response): string | object | null {
    if (response.toJSON()) {
      return response.toJSON();
    } else {
      return super.convert(response);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. JSON Conversion: Prioritize converting the response content to JSON format.
  2. Fallback Handling: If it cannot be converted to JSON, call the convert method of the parent class to handle the response content.

V. Practical Application Case: Obtaining Sports Data

Assuming we need to obtain the user's sports data from the server, such as sports records and sports plans, we will implement this function through the packaged RCP network library.

(I) Packaging Request Method

export async function getAllLookSubjectList(params: Record<"parentId", string>, requestKeyFun?: (str: string) => void): Promise<ApiResult<Subject[]>> {
  return ApiRequest.getInstance().getService().request<ApiResult<Subject[]>>>({
    act: AllLOOK_SUBJECT_LIST,
    method: RequestMethod.POST,
    contentType: RcpContentType.JSON,
    content: params
  }, requestKeyFun);
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Request Configuration: The basic information of the request, such as API path, request method, and content type, is configured through RequestOptions.
  2. Request Sending: The request is sent using the ApiRequest.getInstance().getService().request method.
  3. Callback Function: The request key value is provided through requestKeyFun for operations such as canceling the request.

(II) Calling Request Method

In actual pages or components, we can call the packaged request method to obtain sports data.

aboutToAppear(): void {
  super.aboutToAppear();
  this.showLoading();
  ApiRequest.service<Subject[]>(() => {
    return LookResponsitory.getInstance().getAllLookSubjectList("1", (requestKey) => {
      if (requestKey) {
        this.addRequestId(requestKey);
      }
    });
  }, (data) => {
    this.hideLoading();
    if (data.data && data.data.length > 0) {
      this.tabs = data.data;
    } else {
      this.setDefalutTab();
    }
  }, (apiError) => {
    this.hideLoading();
    this.setDefalutTab();
    // Business exception
    // LibToast.show(apiError.message ?? "Get exception");
  }, () => {
    this.hideLoading();
    this.setDefalutTab();
    // Network and other exceptions
    // LibToast.show("Network exception");
  });
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Request Execution: The network request is executed through the ApiRequest.service method.
  2. Success Handling: If the request is successful, handle the returned data.
  3. Error Handling: Call the corresponding callback function to handle the error based on the error type.
  4. Loading Status: Display the loading status at the start of the request and hide it at the end of the request.

VI. Summary

Through the practical case in this part, we have demonstrated how to use the packaged RCP network library to implement specific network request functions. By defining exception classes, packaging request methods, handling common request headers and response converters, and implementing specific request logic, we can efficiently complete network request tasks. The packaged network library not only provides basic request and response handling functions but also has advanced features such as error handling, log recording, session management, and network status detection, which can meet the needs of most network request scenarios.

Top comments (0)