- News Reader
- App Architecture
- Getting Started
- Setting up Prerequisites
- Running Quality Gates and Deployment Commands
- CI-CD - Build via Bitrise (yml file)
- License
News Reader is simple flutter app to hit the NY Times Most Popular Articles API and show a list of articles, that shows details when items on the list are tapped (a typical master/detail app).
We'll be using the most viewed section of this API.
http://api.nytimes.com/svc/mostpopular/v2/mostviewed/{section}/{period}.json?api-key=sample-key To test this API, you can use all-sections for the section path component in the URL above and 7 for period (available period values are 1, 7 and 30, which represents how far back, in days, the API returns results for).
http://api.nytimes.com/svc/mostpopular/v2/mostviewed/all-sections/7.json?api-key=sample-key
- Android Studio
- Flutter SDK 2.8.1
An API key is necessary to successfully connect to the API that the app uses. Once an API key has been aquired, change the API_KEY
property in /nyt_flutter/lib/common/contant.dart
and run the app.
This sample follows BLoC pattern + Clean Architecture.
The model is the domain object. It represents the actual data and/or information we are dealing with. An example of a model might be a contact (containing name, phone number, address, etc) or the characteristics of a live streaming publishing point.
The key to remember with the model is that it holds the information, but not behaviors or services that manipulate the information. It is not responsible for formatting text to look pretty on the screen, or fetching a list of items from a remote server (in fact, in that list, each item would most likely be a model of its own). Business logic is typically kept separate from the model, and encapsulated in other classes that act on the model.
Provides all required data to the repository in form of models/entities.
Manage all server/external API calls.
Manage all local data storage: example SQLite implementation, Room, Realm...
The decision maker class when it comes to manage data CRUD operations. Operations can be done in this layer is caching mechanism, manage consecutive api calls etc...
Represents concepts of the business, information about the current situation and business rules.
There are three primary gadgets in the BLoC library:
- Bloc
- BlocBuilder
- BlocProvider You’ll require them to set up BLoCs, construct those BLoCs as indicated by the progressions in the app’s state, and set up conditions. How about we perceive how to execute every gadget and use it in your app’s business rationale.
The Bloc gadget is the fundamental segment you’ll have to execute all business rationale. To utilize it, expand the Bloc class and supersede the mapEventToState and initialState techniques.
BlocBuilder is a gadget that reacts to new states by building BLoCs. This gadget can be called on numerous occasions and acts like a capacity that reacts to changes in the state by making gadgets that appear new UI components.
This gadget fills in as a reliance infusion, which means it can give BLoCs to a few gadgets all at once that have a place with the equivalent subtree. BlocProvider is utilized to construct blocs that will be accessible for all gadgets in the subtree.
This repository implements the following quality gates:
- Static code checks: running lint to check the code for any issues.
- Unit testing: running the unit tests
- Code coverage: generating code coverage reports using the LCOV
- Integration testing: running the functional tests using Flutter Integration Testing
These steps can be run manually or using a Continous Integration tool such as Bitrise.
Checkout and run the code
git clone https://github.com/oudaykhaled/nyt-flutter-clean-architecture-unit-test.git
Run the following command in terminal sudo apt-get install lcov
Run the following command in terminal sudo apt install scrcpy
Run the following command in terminal flutter pub run build_runner watch --delete-conflicting-outputs
Run the following command in terminal flutter analyze
Tests in Flutter are separated into 2 types:
Located at /test
- These are tests that run on your machine. Use these tests to minimize execution time when your tests have no flutter framework dependencies or when you can mock the flutter framework dependencies.
Located at /integration_test
- These are tests that run on a hardware device or emulator. These tests have access to all flutter APIs, give you access to information such as the Context of the app you are testing, and let you control the app under test from your test code. Use these tests when writing integration and functional UI tests to automate user interaction, or when your tests have flutter dependencies that mock objects cannot satisfy.
Unit testing for Flutter applications is fully explained in the Flutter documentation. In this repository, From Android Studio
- Right Clicking on the Class and select "Run
- To see the coverage we have t the select "Run with Coverage"
The test coverage uses the LCOV library
In order to run both test
and integration_test
and generate a code coverage report, create a script file to do the job.
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
Below lines are added to ignore the generated files when generating the code coverage report:
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$'
From the commandline
sh test_with_coverage.sh
Test coverage results are available at
This repo contains a bitrise, which is used to define a Bitrise declarative pipeline for CI-CD to build the code, run the quality gates, code coverage, static analysis and deploy to Bitrise.
Here is the structure of the bitrise declarative pipeline:
---
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
Below is an illustration of the pipeline that Bitrise will execute
These steps should be followed to automated the app build using Bitrise:
- Create an account on Bitrise.
- Follow the wizard for creating a Flutter project on Bitrise.
- In
workflows
tab, and select<>bitrise.yaml
tab. - Choose
Store in app repository
to read the repository yaml file.
This repository already attached to a public bitrise project.
Apache License, Version 2.0