flutter项目工程化探索

526 阅读4分钟

flutter项目工程化探索

一、目录结构

main.dart      入口文件,全局配置(主题、响应式、标题等)
│  theme.dart     主题文件
│
└─app              项目主目录
    ├─api          api层,配置ajax请求地址、方式
    ├─components   通用组件
    ├─data         本地数据存储
    ├─models       model层,数据格式描述文件
    ├─modules      view层,页面文件
    │  ├─tabs      页面文件
    │  │  ├─bindings    controller注册文件。结合route building使用,路由进入时注册,跳出时注销
    │  │  ├─controllers 页面逻辑。数据、生命周期、方法等
    │  │  └─views       页面视图
    │
    ├─request     网络层,拦截器、请求头、请求状态等统一封装
    │
    ├─routes      路由层
    │      app_pages.dart  配置路由信息,加载binding
    │      app_routes.dart 注册路由
    │
    ├─services    services层,对api层请求的数据进行处理
    │
    ├─controllers 全局数据存放,处理
    │
    └─utils       工具类

二、业务开发基本流程

  • 创建视图层文件,注册路由
  • 根据接口数据生成 model 层数据描述文件
  • 在 api 层请求接口
  • 使用 services 层定义的通用方法或特殊方法对 api 请求的数据进行处理
  • 视图层 controller 引入数据进行业务处理
  • 视图层 view 进行样式渲染

2.1、创建视图层文件,注册路由

使用 get cli 创建页面文件并注册路由,生成的文件存放在 modules/province 下,为 bindings、controllers、views

get create page:province
  • province_view
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/province_controller.dart'; //引入controller,Widget中使用Obx可直接使用
class ProvinceView extends GetView<ProvinceController> {
  const ProvinceView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ProvinceView'),
        centerTitle: true,
      ),
      body: const Center(
        child: Text(
          'ProvinceView is working',
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}
  • province_controller
import 'package:get/get.dart';
class ProvinceController extends GetxController {
  //TODO: Implement ProvinceController

  final count = 0.obs; // 使用 .obs定义的数据可在view中使用
  @override
  void onInit() {     // 页面初始化
    super.onInit();
  }

  @override
  void onReady() {   // 页面渲染完成
    super.onReady();
  }

  @override
  void onClose() {   // 页面关闭
    super.onClose();
  }

  void increment() => count.value++; // 定义方法
}

2.2、生成 model 层数据描述文件

  • 生成
#https://province.com/api/province =》
{"code"1,
 "msg""查询成功",
 "data": [
            {
            "provinceCode"110000,
            "name""北京市",
            "id"1
            },
         ]
}
# 生成model文件
get generate model on models from https://province.com/api/province --skipProvider //根据接口数据生成json描述文件
无法自动设置model名称,你想用什么名称? [province] // 使用接口名称定义文件名

会在 models 文件夹下生成 province_model.dart 文件,内容如下:

class Province {
  int? code;
  String? msg;
  List<Data>? data;
  Province({this.code, this.msg, this.data});
  Province.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    msg = json['msg'];
    if (json['data'] != null) {
      data = <Data>[];
      json['data'].forEach((v) {
        data?.add(Data.fromJson(v));
      });
    }
  }
  Map<String, dynamic> toJson() {
    final data = <String, dynamic>{};
    data['code'] = code;
    data['msg'] = msg;
    data['data'] = data;
    return data;
  }
}
class Data {
  int? provinceCode;
  String? name;
  int? id;
  Data({this.provinceCode, this.name, this.id});
  Data.fromJson(Map<String, dynamic> json) {
    provinceCode = json['provinceCode'];
    name = json['name'];
    id = json['id'];
  }
  Map<String, dynamic> toJson() {
    final data = <String, dynamic>{};
    data['provinceCode'] = provinceCode;
    data['name'] = name;
    data['id'] = id;
    return data;
  }
}

  1. 文件内搜索[Province] 替换为 [ProvinceModel] // 加上 Model 关键字防止接口名称与默认 class 重名
  2. 文件内搜索[Result] 替换为 [ProvinceModelItem] // Result 为接口返回数据的最外层键名

2.3 api 层请求接口

  • 创建文件,名称为模块名.dart
import 'package:wlxy/app/request/request.dart';   // ajax请求工具
import 'package:wlxy/app/models/province_model.dart'; // model层数据描述
import 'package:wlxy/app/services/default.dart';  // service层 数据基础处理,状态码判断、脱壳等,特殊处理需自定义

class ProvinceApi {
  static Future getProvince() async {
    final response = await HttpUtils.get(path: "/api/province");  //请求ajax
    final ProvinceModel result = ProvinceModel.fromJson(response); // 进行数据描述
    return DefaultService.getResult(result);   // 进行基础数据处理
  }
}

2.4 services 层定义数据处理方法

# 基础脱壳
class DefaultService {
  static Future getResult(response) async {
    if (response.code == 1) {
      return response.data;
    } else {
      print(response.msg);
    }
  }
}

2.5 视图层 controller 引入数据进行业务处理

import 'package:get/get.dart';
import 'package:wlxy/app/models/get_province_model.dart'; // 引入数据描述文件
import 'package:wlxy/app/api/province.dart';  //引入api数据
class ProvinceController extends GetxController {
  RxList<GetProvinceItemModel> provinceList = <GetProvinceItemModel>[].obs; //定义页面内接受数据变量
  @override
  void onInit() {
    super.onInit();
    getProvinceData();
  }
  getProvinceData() async {
    provinceList.value = await ProvinceApi.getProvince();
  }
}

2.6 视图层 view 进行数据渲染

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/province_controller.dart';
class ProvinceView extends GetView<ProvinceController> {
  const ProvinceView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('JSON序列化Demo '),
        centerTitle: true,
      ),
      // provinceList2view
      body: Obx(
        () => ListView.builder(
          itemCount: controller.provinceList.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(controller.provinceList[index].name!),
              subtitle:
                  Text(controller.provinceList[index].provinceCode.toString()),
            );
          },
        ),
      ),
    );
  }
}

三、全局状态管理

  • 生成全局状态文件
    get create controller:counter
import 'package:get/get.dart';
class CounterController extends GetxController {
  /// 定义了该变量为响应式变量,当该变量数值变化时,页面的刷新方法将自动刷新
  var count = 100.obs;

  /// 自增方法
  void increase() => ++count;
}

  • 页面内使用
import 'package:wlxy/app/controllers/counter_controller.dart'; // 引入全局状态文件
...
 Widget build(BuildContext context) {
final counterGlobal = Get.put(CounterController()); // 注册到当前页面中
...
Obx(()=>Text(
       '全局num:${counterGlobal.count}',            // 渲染数据
        style: const TextStyle(fontSize: 20),
       )
    ),
... 
    ElevatedButton(
          onPressed: () => counterGlobal.increment(), // 使用全局状态中定义的方法更改状态
          child: const Text('全局num+1'),
        ),

四、本地数据

  • 本地数据相关操作在data目录下,根据数据内容定义文件名,例如:user_data.dart
// 使用shared_preferences 存储用户信息
import 'dart:convert';
import 'package:wlxy/app/utils/j_sp_util.dart'; // 本地数据处理通用方法库

class UserData {
  String? name;
  String? token;
  UserData({this.name, this.token});
  // json转换为UserData
  UserData.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    token = json['token'];
  }
  // UserData转换为json
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['name'] = name;
    data['token'] = token;
    return data;
  }

  // 获取Map类型
  static Future<UserData?> getUserData() async {
    String? user = await JSpUtil.getLocalStorage('user');
    if (user != null) {
      return UserData.fromJson(json.decode(user));
    }
    return null;
  }

  // 设置Map类型
  static setUserData(UserData user) async {
    // 设置Map类型
    JSpUtil.setLocalStorage('user', user.toJson());
  }
}
  • 视图层引入使用
import 'package:get/get.dart';
import 'package:wlxy/app/data/user_data.dart'; // 本地数据

class MineController extends GetxController {
  //TODO: Implement MineController

  RxString name = '33'.obs;
  @override
  void onReady() {
    super.onReady();
    getUserData();
  }

  void getUserData() {
    UserData.getUserData().then((value) {
      if (value != null) {
        print(value.name);
        name.value = value.name!;
        update();
      } else {
        print('没有数据');
      }
    });
  }

  void setUserData() {
    UserData.setUserData(UserData(name: '用户名', token: '4123124124'));
  }
}