鸿蒙HarmonyOS开发之MVVM模式下数据封装请求简装版本

1.创建项目结构演示

在这里插入图片描述

2.统一接口请求结果

export class ApiResult {
  code: number
  msg ?: string;
  data ?: any;

  constructor() {
    this.code = 0;
    this.msg = undefined;
    this.data = Object;
  }
}

数据模型---tagbean
export class TagListBean {
  list: Array<TagBean>
}

export class TagBean {
  id: number= 0;
  name: string= '';
  icon: string= '';
}

3.网络封装

3.1HttpUtil

export class HttpUtil {
  private static mInstance: HttpUtil;

  // 防止实例化
  private constructor() {

  }

  static getInstance(): HttpUtil {
    if (!HttpUtil.mInstance) {
      HttpUtil.mInstance = new HttpUtil();
    }
    return HttpUtil.mInstance;
  }

  request (option: RequestOptions): Promise<ApiResult> {
    return httpCore.request(option);
  }

  /**
   * 封装Post网络请求
   * @param option
   * @returns
   */
  Post(option:RequestOptions){
    if(option != null){
      option.method = RequestMethod.POST
    }
    return this.request(option);
  }

  /**
   * 封装Get网络请求
   * @param option
   * @returns
   */
  Get(option:RequestOptions){
    if(option != null){
      option.method = RequestMethod.GET
    }
    return this.request(option);
  }



}

export const httpUtil = HttpUtil.getInstance();

3.2Http请求器

export class HttpCore {
  /**
   * 发送请求
   * @param requestOption
   * @returns Promise
   */
  request(requestOptions: RequestOptions): Promise<ApiResult> {
    let p = new Promise<ApiResult>(async (resolve, reject) => {
      // 每一个httpRequest对应一个HTTP请求任务,不可复用
      let httpRequest = http.createHttp();
     // let token : string = await getToken();
      let promise = httpRequest.request(requestOptions.url, {
        method: requestOptions.method,
        connectTimeout: 60000,
        readTimeout: 60000,
        header:{
          'Content-Type': 'application/json',
         // 'token': token,
          'client_type': 'HarmonyOS'
        },
        extraData: requestOptions.extraData
      });
      promise.then((data) => {
        //TODO:此处补充数据拆包的操作
        let resultObj = JSON.parse(data.result.toString());
        //弹窗提示接口返回msg
        setTimeout(() => {
          promptAction.showToast({
            message: resultObj.msg
          })
        }, 500);
        resolve(resultObj);

        //如果业务码为10000 则返回ApiReslut
        // if (resultObj.code == 10000) {
        //   console.log(JSON.stringify(resultObj))
        // }
        // if (resultObj.code == 0){
        //   router.replaceUrl({
        //     url: "pages/MainPage/Login"
        //   }).then(() => {
        //     console.log('router successful')
        //   }).catch(err => {
        //     console.log('router err')
        //   })
        // }
        //如果返回数据包含token信息 则刷新token
        if (resultObj.token != undefined)  {
          PreferenceUtil.writeString('token',resultObj.token)
        }
      }).catch((err) => {
        //这里做全局异常统一处理  根据Http状态码做出处理
        console.info('error:' + JSON.stringify(err));
        reject(err);
      });
      httpRequest.destroy();
    })
    return p;
  }
}


async function getToken(): Promise<string> {
  return new Promise<string>(async (resolve, reject) => {
    try {
      const data = await PreferenceUtil.readString("token")
      if (typeof data === 'string') {
        resolve(data);
      } else {
        reject(new Error('Invalid token'));
      }
    } catch (err) {
      reject(err);
    }
  });

}
export const httpCore = new HttpCore();

3.3网络请求配置

export interface RequestOptions {

  /**
   * Request url.
   */
  url?: string;

  /**
   * Request method.
   */
  method?: RequestMethod; // default is GET

  /**
   * Additional data of the request.
   * extraData can be a string or an Object (API 6) or an ArrayBuffer(API 8).
   */
  extraData?: string | Object | ArrayBuffer;

  /**
   * Request url queryParams  .
   */
  queryParams ?: Record<string, string>;

  /**
   * HTTP request header.
   */
  header?: Object; // default is 'content-type': 'application/json'

}

export enum RequestMethod {
  OPTIONS = "OPTIONS",
  GET = "GET",
  HEAD = "HEAD",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
  TRACE = "TRACE",
  CONNECT = "CONNECT"
}

3.4利用注解的方式封装Get post del等方法

import { ApiResult } from './ApiResult';
import { httpUtil } from './HttpUtil';
import { RequestOptions } from './RequestOptions';

/**
 * 利用Map保存参数和值的映射关系  为避免参数名及方法名重复 采用组合Key的方法
 */
type FunParamMapKey = {
  target: Object; //所在类
  methodName: string; //所在方法
  index: number; //参数名索引值
}

let funParamMap = new Map<string, string>();


// @Get注解 拿到url 从函数的@Param拿到参数名及参数值 利用HttpUtil进行网络请求
//Get 需要拼接URl
export function Get(url: string) {
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    let URL: string = url;
    let options: RequestOptions = {
      url: URL,
      queryParams: {}
    };
    descriptor.value = async function (...args: any[]) {
      //对于方法中的每一个(methodName,arg)遍历加入到网络请求配置中
      args.forEach((arg, index) => {
        // 通过已知的信息构造组合Key对象
        let obj: FunParamMapKey = { target: target, methodName: methodName, index: index };
        // 通过组合Key(通过对内容的比较而不是值 节省内存)从内存map中获取@Param修饰的内容
        let paramName = funParamMap[JSON.stringify(obj)];
        // 将正确的参数名及值添加至网络请求参数中
        if (typeof paramName !== 'undefined') {
          if (typeof arg === 'string' || typeof arg === 'object' || arg instanceof ArrayBuffer || typeof arg === 'number') {
            options.queryParams[paramName] = arg
          } else {
            console.log('参数类型不对')
            throw new Error(`Invalid parameter type at index ${index}.`);
          }
        }
      });
      //拼接请求参数
      const urlParams = Object.keys(options.queryParams).map(key => `${key}=${options.queryParams[key]}`).join('&')
      console.log('urlParams:', urlParams)
      if (URL.includes("?")) {
        options.url = `${URL}${urlParams}`
      } else {
        options.url = `${URL}?${urlParams}`
      }

      const p = new Promise<ApiResult>((resolve, reject) => {
        httpUtil.Get(options).then((response) => {
          const result: ApiResult = response;
          resolve(result);
        }).catch((error) => {
          reject(error);
        });
      });
      return await p;
    };
  };
}

// @Post注解 拿到url 从函数的@Param拿到参数名及参数值 利用HttpUtil进行Post网络请求
export function Post(url: string) {
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    let options: RequestOptions = {
      url: url,
      extraData: {}
    };
    descriptor.value = async function (...args: any[]) {
      //对于方法中的每一个(methodName,arg)遍历加入到网络请求配置中
      args.forEach((arg, index) => {
        console.log("参数值",JSON.stringify(arg))
        // 通过已知的信息构造组合Key对象
        let obj: FunParamMapKey = { target: target, methodName: methodName, index: index };
        // 通过组合Key(通过对内容的比较而不是值 节省内存)从内存map中获取@Param修饰的内容
        let paramName = funParamMap[JSON.stringify(obj)];
        console.log("参数名:",paramName)
        // 将正确的参数名及值添加至网络请求参数中
        if (typeof paramName !== 'undefined') {
          if (typeof arg === 'string' || typeof arg === 'object' || arg instanceof ArrayBuffer) {
            options.extraData[paramName] = arg;
          } else {
            throw new Error(`Invalid parameter type at index ${index}.`);
          }
        }else {
          //如果获取不到形参名 及未被@Param标记 并且参数的类型是对象
          if (typeof  arg === 'object') {
            options.extraData = JSON.stringify(arg)
          }
        }
      });
      console.log('extraData', JSON.stringify(options.extraData))
      const p = new Promise<ApiResult>((resolve, reject) => {
        httpUtil.Post(options).then((response) => {
          const result: ApiResult = response;
          resolve(result);
        }).catch((error) => {
          reject(error);
        });
      });
      return await p;
    };
  };
}
/**
 * @Param 注解将想要获取的Param添加至内存Map中
 * @param paramName
 * @returns
 */
export function Param(paramName: string) {
  return function (target: any, methodName: string, parameterIndex: number) {
    let obj: FunParamMapKey = { target: target, methodName: methodName, index: parameterIndex };
    funParamMap[JSON.stringify(obj)] = paramName;
  };
}

3.5统一接口请求api

import { AppConfig } from '../config/AppConfig';
import { Get, Param, Post } from '../http_other/AOPs';
import { ApiResult } from '../http_other/ApiResult';

export class LoginApi {
  @Post("http://localhost:8080/login")
  //自定义@Param 作用:获取形参 便于映射到extraData
  login(@Param("username") username: string, @Param("password") password: string):Promise<ApiResult> {
    return
  }


  @Get(AppConfig.APP_BASE_URL + 'v1/getTagList')
  getMenuTree():Promise<ApiResult> {
    return
  }

  @Get("http://localhost:8080/api/menu/getPermTree")
  getPermsSelectTree():Promise<ApiResult> {
    return
  }

  //@Get中的@Param 可以将形参拼接到URl中
  @Get("http://localhost:8080/api/menu/get")
  getMenuById(@Param("Id") id : number):Promise<ApiResult> {
    return
  }
  /**
   * 获取所有角色
   * @returns
   */

  @Get("http://localhost:8080/api/role/getAllRole")
  getAllRole():Promise<ApiResult> {
    return
  }

  //可以直接Post对象
  // @Post("http://localhost:8080/api/role/updateRole")
  // updateRole(role : RoleModel):Promise<ApiResult> {
  //   return
  // }

}
export const  loginApi = new LoginApi();

3.6统一的app全局配置信息config

export class AppConfig{

  /**
   *公共基础url
   */
  static APP_BASE_URL="https://local/a.com/api/"
}

3.7数据回调封装


export interface DataCallback<T>{
  onSuccess(result:T)
  onFail(error:GsyNetException)
}

3.8数据异常

export class GsyNetException{

}

3.9

4.ViewModel的创建

import { PageState } from '../components/PageState'
import { TagBean } from '../model/TagListBean';
import { GsyNetException } from '../nethttp/GsyNetException';
import { dynamicListRepository } from '../repository/DynamicListRepository';
import { Logger } from '../utils/Logger';
import { DynamicListDataSource } from './DynamicListDataSource';

@Observed
export class TagListViewModel{
  pageState: number = PageState.LOADING//加载状态
  hasNext:boolean = false;//是否有下一页
  page:number = 1//当前页
  tagList: Array<TagBean> = new Array<TagBean>()//当前数据列表
  tagListDataSource: DynamicListDataSource//page 页调用的数据模型

/**
 * 请求数据
 * @param page
 */
  requestData(page:number){
    var viewModelObj = this

    dynamicListRepository.requestDynamicList({onSuccess(_result: Array<TagBean>){
      if (_result.length>0) {
       let uiModeList: TagBean[]= []
        for(let index in _result){
          let event:TagBean = _result[index]
          uiModeList.push(event)
          viewModelObj.tagList.push(event)
        }
        if(viewModelObj.tagListDataSource==null || page == 1){
          viewModelObj.tagListDataSource = new DynamicListDataSource(uiModeList)
        }else{
          viewModelObj.tagListDataSource.pushData(uiModeList)
        }
        viewModelObj.pageState = PageState.HASDATA
        if(viewModelObj.tagList.length==20){
          viewModelObj.hasNext = false;
        }else{
          viewModelObj.hasNext = true;
        }
      }else if(viewModelObj.tagList.length == 0){
        viewModelObj.pageState = PageState.EMPTY
        Logger.info("数组是空的")
      }else{
        viewModelObj.hasNext = false;
      }

    },
    onFail(_error: GsyNetException){
      this.pageState = PageState.ERROR
    }
    },page)
  }
}

5.数据观察者创建,用于数据模型的调用

DynamicListDataSource

import { TagBean } from '../model/TagListBean';

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): TagBean | undefined {
    return undefined;
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const position = this.listeners.indexOf(listener);
    if (position >= 0) {
      this.listeners.splice(position, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataMove(from, to);
    })
  }
}
@Observed
export class DynamicListDataSource extends BasicDataSource {
  listData:TagBean[]

  constructor(events:TagBean[]) {
    super()
    this.listData = events
  }

  public totalCount(): number {
    return this.listData.length;
  }

  public getData(index: number): TagBean {
    return this.listData[index];
  }

  public pushData(data:TagBean[]){
    for(var eventUiModel in data){
      this.listData.push(data[eventUiModel])
    }
    this.notifyDataReload()
  }

}

6.DynamicListRepository数据请求的处理


import { DataCallback } from '../nethttp/DataCallback';
import HttpManager, { RequestMethod } from '../nethttp/HttpManager';
import { GsyNetException } from '../nethttp/GsyNetException';
import { AppConfig } from '../config/AppConfig';
import { BaseBean } from '../model/BaseBean';
import { TagBean, TagListBean } from '../model/TagListBean';
import { Logger } from '../utils/Logger';
import { loginApi } from '../api_url/LoginApi';
import { ApiResult } from '../http_other/ApiResult';


class DynamicListRepository {
  //接口数据类型
  //result_res==={"code":10000,"msg":"success","data":{"count":20,"list":[{"id":1,"name":"综合用工","icon":"com/market/system/63e43215431fd53679b840298e952897_20240403172420.png"},{"id":2,"name":"泥瓦工匠","icon":"a6b69caae94c9cab4c705f271b334_20240403172459.png"]}}
  requestDynamicList(callback: DataCallback<Array<TagBean>>, page: number) {
    loginApi.getMenuTree().then((result:ApiResult )=> {
      let res:ApiResult=result//数据承接
      Logger.info("result_data===" + JSON.stringify(result.data))
      Logger.info("result_res===" + JSON.stringify(res))
      //获取实际列表数据
      let listBean:Array<TagBean>=res.data['list'] as Array<TagBean>
      Logger.info("listBean===" + JSON.stringify(listBean))
      callback.onSuccess(listBean)//数据回调
    }).catch(error=>{
      Logger.info("error=====" + JSON.stringify(error))
    })
  }
  }
}

export const dynamicListRepository = new DynamicListRepository();


7.在page页面使用

//声明viewmodel
  @State viewModel: TagListViewModel = null
//实力化viewmodel并且请求数据
 aboutToAppear() {
    //todo    实力化TagListViewModel
    this.viewModel = new TagListViewModel();
    //todo 请求数据
    this.viewModel.requestData(1)
  }
  //在build使用viewmodel的方法和数据
    build() {
    if (this.viewModel.pageState == PageState.LOADING) {
      Text("正在加载中")
    } else if (this.viewModel.pageState == PageState.HASDATA) {

      Column() {
        //下拉刷新布局
       this.CustomPullRefreshLayout()
        List({ space: 16, scroller: this.listScroller }) {
        //数据量大使用LazyForEach
          LazyForEach(this.viewModel.tagListDataSource, (item: TagBean) => {
            ListItem() {
              Column() {
                Row() {
                  Image(item.icon)
                    .width('50vp')
                    .height('50vp')
                  Text(item.name)
                }
                .width('100%')
                .justifyContent(FlexAlign.Start)
                .alignItems(VerticalAlign.Center)
                .height(60)
                .borderRadius(20)
                .borderWidth(1)
                .borderColor($r('app.color.color_gray'))
                .margin({ top: 10 })
              }.justifyContent(FlexAlign.Start)

            }

          }, item => JSON.stringify(item))
        }
        .listDirection(Axis.Vertical)
        .width('94%')
        .edgeEffect(EdgeEffect.None) // 去掉回弹效果
        .onTouch((event: TouchEvent) => {
          this.listTouchEvent(event);
        })
        .onScrollIndex((start: number, end: number) => {
          console.info('first' + start)
          console.info('last' + end)
          if (end == this.viewModel.tagListDataSource.totalCount() - 1 && start != 0) {
            if (this.viewModel.hasNext) {
              this.loadMore = true;
              this.viewModel.page++
              this.viewModel.requestData(this.viewModel.page)
            } else {
              this.loadMore = false;
            }
          }

        })

        if (this.loadMore) {
          Text("加载更多")
        } else {
          Text("已经到底了")
        }

      }
      .justifyContent(FlexAlign.Center)
      .width('100%')
      .height('100%')

    } else if (this.viewModel.pageState == PageState.EMPTY) {
      Column() {
        Text("加载完整空数据")
        Button("重新加载").onClick((event) => {
          this.viewModel.requestData(1)
        })
      }
    }

  }


pagestate

export class PageState{
  static LOADING:number = 0;//加载中
  static HASDATA:number = 1; //有数据
  static EMPTY:number = 2; //无数据
  static ERROR:number = 3 //加载失败
}

结束语

Page<--------->TagListViewModel------>DynamicListRepository------>Http

本文只是简单的进行操作,还可进行业务逻辑的拆封,例如工具类,配置类,下拉刷新上拉加载更多等,技术能力强的同学可进行无私贡献。如有错误请留言指明。谢谢。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
在Kotlin中使用MVVM模式请求数据通常涉及以下几个步骤: 1. 创建数据模型(Model):定义数据的结构和属性。可以使用Kotlin的数据类(data class)来简化模型的创建。 ```kotlin data class User(val id: String, val name: String, val email: String) ``` 2. 创建视图模型(ViewModel):负责处理数据的获取和变换逻辑,并将数据暴露给视图层。视图模型应该是与界面无关的,不包含任何与界面相关的代码。 ```kotlin class UserViewModel : ViewModel() { private val userRepository = UserRepository() private val _user = MutableLiveData<User>() val user: LiveData<User> get() = _user fun getUser(userId: String) { userRepository.getUser(userId) { user -> _user.postValue(user) } } } ``` 3. 创建仓库(Repository):负责从数据源(如网络、数据库等)获取数据。仓库可以使用异步方式获取数据,如使用协程、RxJava等。 ```kotlin class UserRepository { fun getUser(userId: String, callback: (User) -> Unit) { // 发起网络请求数据库查询等操作获取数据 // 在回调中将获取到的数据传递给调用方 // callback(user) } } ``` 4. 创建视图(View):展示数据和处理用户交互。视图通常是Activity、Fragment或View等。 ```kotlin class MainActivity : AppCompatActivity() { private val userViewModel: UserViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) userViewModel.user.observe(this, { user -> // 更新UI显示数据 }) // 请求数据 userViewModel.getUser("user_id") } } ``` 这样,当调用`getUser`方法时,视图模型会通过仓库获取数据,并将数据更新到`user`LiveData中。视图观察`user`LiveData,一旦数据发生变化,就会触发UI的更新。 请注意,上述代码只是一个简单的示例,实际使用中可能需要根据具体情况进行适当调整。同时,还可以使用各种第三方库来简化开发,如Retrofit进行网络请求,Room进行数据库操作等。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值