新闻阅读器

News Reader 是一款简单的 Flutter 应用,可以调用纽约时报热门文章 API,并显示文章列表,当点击列表项时显示详情(一个典型的母/子应用)。

我们将使用该 API 的“最受欢迎”部分。

http://api.nytimes.com/svc/mostpopular/v2/mostviewed/{section}/{period}.json?api-key=sample-key
要测试此 API,您可以使用 URL 中 section 部分的 all-sections,period 部分的 7(可用的 period 值是 1、7 和 30,代表 API 返回结果的时间范围,单位为天)。

http://api.nytimes.com/svc/mostpopular/v2/mostviewed/all-sections/7.json?api-key=sample-key

alt text

要求

  • Android Studio
  • Flutter SDK 2.8.1

API 密钥

要成功连接到应用使用的API,需要一个 API 密钥。获得 API 密钥后,请在 /nyt_flutter/lib/common/contant.dart 中更改 API_KEY 属性,然后运行应用。

应用架构

本示例遵循 BLoC 模式 + Clean Architecture。

模型/实体

模型是领域对象。它代表我们正在处理的实际数据或信息。模型的一个例子可能是联系人(包含姓名、电话号码、地址等)或实时流媒体发布点的特征。

关于模型需要记住的关键是,它包含信息,但不包含操作该信息的行为或服务。它不负责格式化文本以在屏幕上漂亮地显示,或从远程服务器获取项目列表(实际上,在该列表中,每个项目很可能都是它自己的模型)。业务逻辑通常与模型分开,并封装在其他操作模型的类中。

数据层

以模型/实体的形式提供存储库所需的所有数据。

远程数据源

管理所有服务器/外部 API 调用。

本地数据源

管理所有本地数据存储:例如 SQLite 实现、Room、Realm…

Repository

当涉及管理数据 CRUD 操作时,这是决策者类。在此层中可以执行的操作包括缓存机制、管理连续 API 调用等…

用例/交互器

代表业务概念、当前情况信息和业务规则。

BLoC

BLoC 库中有三个主要的小部件

  • Bloc
  • BlocBuilder
  • BlocProvider
    您需要它们来设置 BLoCs,根据应用状态的变化构建这些 BLoCs,并设置条件。让我们看看如何实现每个小部件并在您的应用业务逻辑中使用它。
Bloc

Bloc 小部件是实现所有业务逻辑所需的关键组件。要使用它,请继承 Bloc 类并覆盖 mapEventToState 和 initialState 方法。

BlocBuilder

BlocBuilder 是一个响应新状态并通过构建 BLoCs 来做出响应的小部件。此小部件可以调用多次,并且像一个函数一样响应状态的变化,通过创建看起来像新 UI 组件的小部件。

BlocProvider

此小部件充当依赖注入,这意味着它可以一次将 BLoCs 提供给属于同一子树的多个小部件。BlocProvider 用于构建可供子树中所有小部件使用的 bloc。

入门

此存储库实现了以下质量门

Build Pipeline

  • 静态代码检查:运行 lint 来检查代码是否存在任何问题。
  • 单元测试:运行 单元测试
  • 代码覆盖率:使用 LCOV 生成代码覆盖率报告
  • 集成测试:使用 Flutter 集成测试运行功能测试

这些步骤可以手动运行,也可以使用持续集成工具(如 Bitrise)运行。

检出代码

检出并运行代码

git clone https://github.com/oudaykhaled/nyt-flutter-clean-architecture-unit-test.git

主要库/工具

类别 库/工具 链接
开发 Flutter – Dart https://flutterdart.cn/
IDE Android Studio https://developer.android.com.cn/studio
单元测试 Flutter 单元测试 https://docs.flutterdart.cn/cookbook/testing/unit/introduction
代码覆盖率 LCOV https://github.com/linux-test-project/lcov
静态代码检查 Lint for Dart/Flutter https://pub.dev/packages/lint
集成测试 Flutter 集成测试 https://docs.flutterdart.cn/cookbook/testing/integration/introduction
CI/CD Bitrise https://app.bitrise.io/
依赖注入 injectable https://pub.dev/packages/injectable
服务定位器 get_it https://pub.dev/packages/get_it
演示层管理 flutter_bloc https://pub.dev/packages/flutter_bloc
网络层管理 retrofit https://pub.dev/packages/retrofit
代码生成器 Freezed https://pub.dev/packages/freezed
HTTP 客户端 Dio https://pub.dev/packages/dio
图片缓存 cached_network_image https://pub.dev/packages/cached_network_image
Mock 库 Mockito https://pub.dev/packages/mockito

设置先决条件

安装 LCOV

在终端运行以下命令 sudo apt-get install lcov

安装 scrcpy

在终端运行以下命令 sudo apt install scrcpy

生成文件

在终端运行以下命令 flutter pub run build_runner watch --delete-conflicting-outputs

运行质量门和部署命令

Linting

在终端运行以下命令 flutter analyze
alt text

测试

Flutter 中的测试分为 2 种类型

测试

位于 /test – 这些是在您的机器上运行的测试。当您的测试没有 Flutter 框架依赖项或您可以模拟 Flutter 框架依赖项时,请使用这些测试来最小化执行时间。
alt text

集成测试

位于 /integration_test – 这些是在硬件设备或模拟器上运行的测试。这些测试可以访问所有 Flutter API,为您提供对您正在测试的应用程序 Context 等信息的访问权限,并允许您从测试代码中控制被测应用程序。在编写集成和功能 UI 测试以自动化用户交互时,或者当您的测试具有无法满足的 mock 对象依赖项时,请使用这些测试。
alt text
alt text

运行单元测试

Flutter 应用程序的单元测试在 Flutter 文档中有详细解释。在本存储库中,
从 Android Studio

  • 右键单击类并选择“运行”
  • 要查看覆盖率,请选择“使用覆盖率运行”

测试覆盖率

测试覆盖率使用 LCOV
为了运行 testintegration_test 并生成代码覆盖率报告,请创建一个脚本文件来完成此任务。

  
red=$(tput setaf 1)  
none=$(tput sgr0)  
filename=  
open_browser=  
  
show_help() {  
    printf "  
Script for running all unit and widget tests with code coverage.  
(run it from your root Flutter's project)  
*Important: requires lcov  
     Usage:   
 $0 [--help] [--open] [--filename <path>]where:  
 -o, --open Open the coverage in your browser, Default is google-chrome you can change this in the function open_cov(). -h, --help print this message -f <path>, --filename <path> Run a particular test file. For example:             -f test/a_particular_test.dart  
         Or you can run all tests in a directory  
 -f test/some_directory/"  
}  
  
run_tests() {  
    if [[ -f "pubspec.yaml" ]]; then  
  rm -f coverage/lcov.info  
        rm -f coverage/lcov-final.info  
        flutter test --coverage "$filename"  
  ch_dir  
  else  
  printf "\n${red}Error: this is not a Flutter project${none}\n"  
  exit 1  
  fi  
}  
  
run_report() {  
    if [[ -f "coverage/lcov.info" ]]; then  
  lcov -r coverage/lcov.info lib/resources/l10n/\* lib/\*/fake_\*.dart \  
             -o coverage/lcov-final.info  
        genhtml -o coverage coverage/lcov-final.info  
    else  
  printf "\n${red}Error: no coverage info was generated${none}\n"  
  exit 1  
  fi  
}  
  
ch_dir(){  
    dir=$(pwd)  
  input="$dir/coverage/lcov.info"  
  output="$dir/coverage/lcov_new.info"  
  echo "$input"  
  while read line  
    do  
  secondString="SF:$dir/"  
  echo "${line/SF:/$secondString}" >> $output  
    done < "$input"  
  
  mv $output $input  
}  
  
open_cov(){  
    # This depends on your system   
    # Google Chrome:  
 # google-chrome coverage/index-sort-l.html # Mozilla:  firefox coverage/index-sort-l.html  
}  
  
while [ "$1" != "" ]; do  
 case $1 in  
  -h|--help)  
            show_help  
 exit  ;;  
  -o|--open)  
            open_browser=1  
  ;;  
  -f|--filename)  
            shift  
  filename=$1  
            ;;  
  *)  
            show_help  
 exit  ;;  
 esac  shift  
done  
  
run_tests  
remove_from_coverage -f coverage/lcov.info -r '.g.dart$'  
remove_from_coverage -f coverage/lcov.info -r '.freezed.dart$'  
remove_from_coverage -f coverage/lcov.info -r '.config.dart$'  
run_report  
if [ "$open_browser" = "1" ]; then  
  open_cov  
fi

以下行已添加,以便在生成代码覆盖率报告时忽略生成的文件

remove_from_coverage -f coverage/lcov.info -r '.g.dart$'  
remove_from_coverage -f coverage/lcov.info -r '.freezed.dart$'  
remove_from_coverage -f coverage/lcov.info -r '.config.dart$' 

从命令行

sh test_with_coverage.sh

测试覆盖率结果可在

/coverage/index.html
alt text

CI-CD – 通过 Bitrise 构建(yml 文件)

此存储库包含一个 bitrise 文件,该文件用于定义 Bitrise 的**声明式管道**,用于 CI/CD 来构建代码、运行质量门、代码覆盖率、静态分析并部署到 Bitrise。

以下是 Bitrise 声明式管道的结构

---  
format_version: '11'  
default_step_lib_source: 'https://github.com/bitrise-io/bitrise-steplib.git'  
project_type: flutter  
trigger_map:  
- push_branch: '*'  
  workflow: primary  
- pull_request_source_branch: '*'  
  workflow: primary  
workflows:  
  deploy:  
    steps:  
    - activate-ssh-key@4:  
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'  
  - git-clone@6: {}  
    - script@1:  
        title: Do anything with Script step  
    - certificate-and-profile-installer@1: {}  
    - flutter-installer@0:  
        inputs:  
        - is_update: 'false'  
  - cache-pull@2: {}  
    - flutter-analyze@0:  
        inputs:  
        - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION  
    - flutter-test@1:  
        inputs:  
        - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION  
    - flutter-build@0:  
        inputs:  
        - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION  
        - platform: both  
    - xcode-archive@4:  
        inputs:  
        - project_path: $BITRISE_PROJECT_PATH  
        - scheme: $BITRISE_SCHEME  
        - distribution_method: $BITRISE_DISTRIBUTION_METHOD  
        - configuration: Release  
    - deploy-to-bitrise-io@2: {}  
    - cache-push@2: {}  
  primary:  
    steps:  
    - activate-ssh-key@4:  
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'  
  - git-clone@6: {}  
    - script@1:  
        title: Do anything with Script step  
    - flutter-installer@0:  
        inputs:  
        - is_update: 'false'  
  - cache-pull@2: {}  
    - [email protected]:  
        inputs:  
        - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION  
    - flutter-test@1:  
        inputs:  
        - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION  
    - deploy-to-bitrise-io@2: {}  
    - cache-push@2: {}  
meta:  
  bitrise.io:  
    stack: linux-docker-android-20.04  
    machine_type_id: elite  
app:  
  envs:  
  - opts:  
      is_expand: false  
    BITRISE_FLUTTER_PROJECT_LOCATION: .  
  - opts:  
      is_expand: false  
    BITRISE_PROJECT_PATH: .  
  - opts:  
      is_expand: false  
    BITRISE_SCHEME: .  
  - opts:  
      is_expand: false  
    BITRISE_DISTRIBUTION_METHOD: development

以下是 Bitrise 将执行的管道的说明

alt text

使用 Bitrise 构建应用程序

应遵循以下步骤来自动化 Bitrise 的应用程序构建

  • 在 Bitrise 上创建一个帐户。
  • 按照向导在 Bitrise 上创建 Flutter 项目。
  • workflows 选项卡中,选择 <>bitrise.yaml 选项卡。
  • 选择 Store in app repository 以读取存储库 yaml 文件。

此存储库已附加到公共 Bitrise 项目

许可证

Apache 许可证,第 2.0 版

https://apache.ac.cn/licenses/LICENSE-2.0

GitHub

查看 Github