Skip to content

Latest commit

 

History

History
126 lines (107 loc) · 7.19 KB

旦夕项目结构介绍.md

File metadata and controls

126 lines (107 loc) · 7.19 KB

旦夕项目结构介绍

本文简单介绍一下旦夕的整体项目结构。

[TOC]

文件夹结构

文件夹旁边的中文注释解释了这个文件夹下代码的作用。

一些目录结构可能随项目结构调整而变化,不过大体上区别不大,

lib 整个旦夕源码部分的根目录。
├─common 一些整个项目中会用到的东西,例如图标、应用名、下载链接、主题色、API KEY 等等
├─feature 一些 Feature 的定义。Feature 是旦夕应用内的概念,每个 Feature 可在首页被显示成一项功能。
├─generated 一些由 intl 等插件生成的国际化支持文件,你不应当手动修改这些文件。如果需要,请修改 l10n 目录下的翻译文件。
│  └─intl
├─l10n 翻译文件,包含多种语言。
├─model 一些用到的模型类,如帖子、回复、公告、消息等信息模型。.g.dart 结尾的文件由 json_serializable 生成,你也不应该手动修改。
│  ├─curriculum
│  └─opentreehole
├─page 所有页面的定义。
│  ├─curriculum
│  ├─dashboard
│  ├─opentreehole
│  └─settings
├─provider 一些类似于维护全局变量的类。其中一部分继承了 ChangeNotifier 并在 main.dart 被作为 Provider 挂到了布局树中。
├─repository 负责完成一切网络请求的类。
│  ├─app
│  ├─curriculum
│  ├─fdu
│  └─opentreehole
├─test 用于本地测试的类。你可以直接在其中编写测试代码,调试模式下会在旦夕启动后自动执行,但不要把这些新代码上传到仓库中!
├─util 一些工具类,功能很多很杂。
│  ├─bmob
│  │  └─bmob
│  │      ├─realtime
│  │      ├─response
│  │      ├─table
│  │      └─type
│  ├─builder
│  ├─io
│  ├─js
│  ├─opentreehole
│  ├─scroller_fix
│  └─win32
└─widget 应用中所用到的各种自定义控件,如逻辑复杂的对话框、帖子和回复、课程表、Tag 控件等等。
    ├─dialogs
    ├─feature_item
    ├─libraries
    ├─opentreehole
    │  ├─render
    │  └─tag_selector
    │      └─flutter_tagging
    └─time_table

一些注意事项

  1. 所有 .g.dart 结尾的文件都是自动生成的,你不应该手动修改。如果希望重新生成这些文件,你只需要在根目录下执行 flutter pub run build_runner build

……怎么做?

如果希望尽快开始,选择一个简单的类、学习一下其中代码逻辑总是好的。

创建一个新页面

我们在 main.dart 中添加了非常详细的注释。你可以通读这份源代码以迅速了解这一 Flutter 的启动方式和工作流程的大致情况。

相关的说明在 main.dartNote: A Checklist After Creating a New Page 中。

提示

关于页面里的网络请求,我们摸索出了一套完整的逻辑。通过这些模式,你可以更轻松地编写网络请求和它们的异常处理。

一个易懂的例子可以看:page/dashboard/announcement_notices.dart,你只需要注意它是如何初始化网络请求(在 initState 中)和通过网络请求构建界面(在 build 中)的!

注意

如果你是要编写一个显示在首页 Tab 的子页面(也就是和首页、树洞、课表、设置同级别的),你应当有更多的了解。

page/subpage_dashboard.dart 是我们能给出的最简单的例子了。注意看这个文件里两个类:HomeSubpageHomeSubpageState 分别继承了什么!

创建一个新首页功能

我们以宿舍电量功能为案例,在 lib/feature/dorm_electricity_feature.dartlib/repository/fdu/dorm_repository.dart 中添加了非常详细的注释。它们只有 100 行左右。你可以通过通读这两份源代码来迅速了解如何创建新的首页功能。

你通常需要先在 repository 下编写对应的网络请求代码。详见 创建一个新网络请求

然后,你需要创建一个新的 Feature。首先可以查看一个简单的示例,比如 feature/dorm_electricity_feature.dart,它是一个功能完整的、带网络请求的 Feature。

另外,Feature 还需要一些额外的声明,这方面的说明在 feature/base_feature.dartNote: A Checklist After Creating a New [Feature] 中。

创建一个新网络请求

你可能需要实现一些新的网络请求逻辑。这时,你需要在 repository 下编写对应的网络请求代码。

首先,找到你想实现的网络请求对应的类。例如,如果与 ehall 有关,你应当在 repository/fdu/ehall_repository.dart 中编写。

一个通常的请求方法像这样:

  /// Load exam scores of all semesters
  ///
  /// Compared to [EduServiceRepository]'s method of the same name,
  /// this method doesn't require a Fudan LAN environment.
  ///
  /// NOTE: Result's [type] is year + semester(e.g. "2020-2021 2"),
  /// and [id] doesn't contain the last 2 digits.
  Future<List<ExamScore>?> loadAllExamScore(PersonInfo? info) =>
      UISLoginTool.tryAsyncWithAuth(
          dio!, LOGIN_URL, cookieJar!, info, () => _loadAllExamScore());

  Future<List<ExamScore>?> _loadAllExamScore() async {
    Response r = await dio!.get(SCORE_DETAIL_URL);
    BeautifulSoup soup = BeautifulSoup(r.data.toString());
    dom.Element tableBody = soup.find("tbody")!.element!;
    return tableBody
        .getElementsByTagName("tr")
        .map((e) => ExamScore.fromDataCenterHtml(e))
        .toList();
  }

几个需要注意的地方:

  1. 由于网络请求都是异步方法,返回值一定是 Future<XXX>
  2. 每个方法,你总是需要编写两次:带下划线 _ 的私有方法和不带下划线的公用方法。前者实现网络请求,后者则简单地用 Retrier UISLoginTool.tryAsyncWithAuth 等方法包装前者,以完成必要的登录和网络重试操作。
  3. 返回值总是可空类型(也即带问号 ?),如果你发现有人没带问号,请务必顺手加上。这是为了在请求失败、抛出异常后,异常处理方法(通常是一个 catchError)可以不必返回任何值(或者说,返回 null)。如果不是可空类型,这些异常处理方法就会被要求返回一个相同类型的、不是 null 的值(在上例中,所有关于它的异常处理方法就不得不返回一个不是 nullList<ExamScore>,否则就会引发类型转换错误:Null is not a subtype of List<ExamScore>。),这使得编写一个通用的异常处理操作变得不大可能。

提示

注意到上例的公有方法没有 async 关键字了吗?这不是编写错误。

相比于返回了 List<ExamScore>? 对象的_loadAllExamScore 方法,loadAllExamScore 本身就返回了一个类型为 Future<List<ExamScore>?> 的对象,因此它自己并不是异步方法,当然也就不用加异步关键字。