Scalable 

Flutter Mobile Development 

Majid Hajian

mhadaily

 Beautiful

Fast

Open

mhadaily

Productive

 Beautiful

Fast

Open

mhadaily

mhadaily

Agenda

  • Code Quality Automation
  • CD / CI
  • From Code to User
  • Architecture

ME.dart

import 'package:flutter/material.dart';
MaterialApp(
   ThemeData(
        name: "Majid Hajian",
        location: "Oslo, Norway",
        description: '''
                Google Developer Expert
        	Passionate Software engineer, 
	        Community Leader, Author and international Speaker
         ''',
        main: "Flutter/Dart, PWA, Performance",
        homepage: "https://www.majidhajian.com",
        socials: {
          twitter: "https://www.twitter.com/mhadaily",
          github: "https://www.github.com/mhadaily"
        },
        author: {
          Pluralsight: "www.pluralsight.com/authors/majid-hajian",
          Apress: "Progressive Web App with Angular, Book",
          PacktPub: "PWA development",
          Udemy: "PWA development",
        }
        founder: "Softiware As (www.Softiware.com)"
        devDependencies: {
          tea: "Ginger", 
          mac: "10.14+",
        },
        community: {
          MobileEraConference: "Orginizer",
          FlutterVikings: "Orginizer", 
          FlutterDartOslo: "Orginizer",
          GDGOslo: "Co-Orginizer",
          DevFestNorway: "Orginizer",
          ...more
        }));

mhadaily

Find me on the internet by

From Code to User

The Flow

mhadaily

Company Testers 

Internal Testers 

External Testers / Beta 

Production

mhadaily

Git Flow

Scrum

mhadaily

mhadaily

Company Testers 

Internal Testers 

External Testers / Beta 

Production

mhadaily

if [[ "$FH_BRANCH" == "master" ]]; then
  export DEPLOYMENT_LANE="production" 
  
elif [[ "$FH_BRANCH" == "develop" ]]; then
  export DEPLOYMENT_LANE="internal"
  
elif [[ "$FH_BRANCH" =~ ^release\/.*$ ]]; then
  export DEPLOYMENT_LANE="beta"
  
else
  export DEPLOYMENT_LANE="feature"
fi

Production

Beta

Production

Alpha

Internal

App Store

TestFlight

End Users

Internal Testers

Beta Testers

mhadaily

mhadaily

Code Quality

mhadaily

void main(List<String> arguments) {
  analyze.main();
  format.main();
  test.main();
}

mhadaily

void main(List<String> arguments) {
  analyze.main();
  format.main();
  test.main();
}
void main() {
  stdout.write('Start Analyzing...\n');
  final ProcessResult result = Process.runSync(
    'flutter',
    <String>['analyze', '--no-pub'],
    runInShell: true,
  );
  checkResult(result);
}

mhadaily

mhadaily

#!/bin/bash
# Created For Majid's App 
if [ ! -f /Users/majid/Projects/app/scripts/git_hooks/pre_commit.dart ]; then
    # Dart script not found, skipping hook"
    exit 0
fi
echo "> Running pre_commit hook..."
DART_EXIT_CODE=0
dart /Users/majid/Projects/app/scripts/git_hooks/pre_commit.dart "$@"
DART_EXIT_CODE=$?
if [[ ${DART_EXIT_CODE} -ne 0 ]]; then
  echo ""
  echo "> Error detected in pre_commit hook."
        exit 1
fi
> cat .git/hooks/pre-commit

mhadaily

mhadaily

Consider from day 1

  • Well-written documentation
  • Avoid creating wheels
  • Well-tested code
  • Consistent code / architecture pattern
  • 100% code coverage

CI / CD

mhadaily

mhadaily

mhadaily

  1. Capture screenshots automatically
  2. Distribute beta builds
  3. publish your app with the push of a button
  4. automate code signing for iOS
  5. Handle your build by code (code as infrastructure)  
  6. Open source / Written in Ruby / Gem
  7. Handle push notificaiton certificates out of the box

mhadaily

fastlane is an open source platform aimed at simplifying Android and iOS deployment.
fastlane lets you automate every aspect of your development and release workflow.

mhadaily

mhadaily

.
├── Appfile
├── Deliverfile
├── Fastfile
├── Matchfile
├── Pluginfile
├── android
│   ├── icons
│   ├── metadata
│   └── screenshots
├── devices.txt
├── ios
│   ├── metadata
│   ├── push_certificates
│   └── screenshots
└── report.xml
.
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── README.md
├── analysis_options.yaml
├── android
├── assets
├── build
├── build.yaml
├── dartdoc_options.yaml
├── docs
├── fastlane
├── gen
├── github_action.sh
├── ios
├── lib
├── local_packages
├── pubspec.lock
├── pubspec.yaml
├── res
├── scripts
├── setup.dart
├── test
├── travis_before_script.sh
├── travis_script.sh
└── web
platform :android do

# your lanes for Android here 

end


platform :ios do

# your lanes for iOS here 

end

mhadaily

Fastfile

platform :android do

  desc "Build and release to beta"
  lane :beta_internal_release do
    prepare(release: false)
    setup_new_changelog
    release_to_store(track: 'internal')
  end
  
end

mhadaily

platform :ios do

  desc "Push a new release to TestFlight"
  lane :build_and_deploy_testflight do |options|
    puts "Build and Deploy TestFlight started..."
    prepare(environment: options[:environment])
    setup_new_changelog
    upload_to_testflight(
        skip_waiting_for_build_processing: true,
        ipa: "Runner.ipa",
        username: username,
    #changelog: changelog, # it's possible to 
    #add AppInfo, BuildInfo and more, also localize it.
    )
    slack(
        message: "#{version_number}+#{build_number}: iOS beta released to 
        TestFlight #{options[:channel]} \n Changelog: \n #{emojified_changelog}",
        slack_url: slack_url,
    )
    # git_actions(release: false)
  end
end

mhadaily

platform :ios do

  desc "Prepare and archive app"
  lane :prepare do |options|
    puts "Building and deploying version #{version_number}, current build #{latest_testflight_build_number}"
    if (ENV["AWS_ACCESS_KEY_ID"])
      push_notification_certificates
    end
    handle_versioning
    flutter_build_no_sign
    generate_icons
    beta_badge(hide: options[:release])
    code_signing
    build_ios_app(
        workspace: workspace_path,
        configuration: "Release",
        scheme: "Runner",
        export_team_id: export_team_id,
        export_method: "app-store",
        # Verify that the right signing identity is used for publishing.
        codesigning_identity: codesigning_identity,
        xcargs: "DEVELOPMENT_TEAM='#{export_team_id}'"
    )
  end
end

mhadaily

Share one code signing identity across your development team to simplify your codesigning setup and prevent code signing issues.

 

codesigning.guide

mhadaily

platform :ios do
  desc "Sing code using Match and fastlane"
  lane :code_signing do
    # Stop fastlane from echoing back ENV var.
    # Doesn't matter too much since Travis doesn't echo back encrypted variables
    # anyway :D
    suppress_output {
      # Retrieves all the necessary certs and provisioning profiles.
      # match   # alias for "sync_code_signing"
      sync_code_signing(readonly: true)
    }
    puts 'Certificates and profiles installed'
    # Modify the Xcode project to use the new team and profile.
    # It will put the git state to dirty but Travis will be wiped after
    # then run session.
    disable_automatic_code_signing
    update_project_provisioning(
        build_configuration: 'Release',
        profile: ENV["sigh_#{app_identifier}_appstore_profile-path"]
    )
  end
end

mhadaily

#Matchfile

git_url(ENV['REPO'])
storage_mode("git")
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["com.your.app"])
username(ENV['USERNAME']) # Your Apple Developer Portal username

Cloud Storage

or

Github 

mhadaily

platform :ios do
  desc "Handle version and build number"
  lane :handle_versioning do
    build_number = latest_testflight_build_number + 1
    puts "new build number now is 
    #{build_number} and build 
    version is #{version_number}"
  end
end

platform :android do
  desc "Handle version and build number"
  lane :handle_versioning do
    internal_number = google_play_track_version_codes(track: 'internal')[0].to_i
    beta_number = google_play_track_version_codes(track: 'beta')[0].to_i
    alpha_number = google_play_track_version_codes(track: 'alpha')[0].to_i
    production_number = google_play_track_version_codes(track: 'production')[0].to_i
    # make sure we know all release number and bump latest version
    build_number = [internal_number, beta_number, production_number, alpha_number].max + 1
    puts "new build number now is #{build_number} and build version is #{raw_version}"
  end
end

Build  Number / Version

mhadaily

platform :ios do
  desc "Build app with no signing for ios and increase build number or version number"
  lane :flutter_build_no_sign do
    Dir.chdir "../#{project_ios_path}" do
      sh("flutter", "doctor")
      sh("flutter", "packages", "get")
      sh("flutter", "clean")
      sh("flutter", "test")
    end
  end
end

Run Test / other commands

mhadaily

platform :android do
  desc "Create change log and make it ready for both slack and google play"
  lane :setup_new_changelog do
    changelog = read_changelog(
        changelog_path: "../CHANGELOG.md",
        section_identifier: "[Build: #{version_number}]", # Specify what section to read
        excluded_markdown_elements: ['-', '###'] # Specify which markdown elements should be excluded
    )
    emojified_changelog = emojify_changelog

    # write for Google store at the moment we have only no-NO
    path = "./metadata/android/no-NO/changelogs"
    dir = File.dirname(path)
    unless File.directory?(dir)
      FileUtils.mkdir_p(dir)
    end
    path << "/#{build_number}.txt"
    File.new(path, 'w')
    File.write(path, changelog)

    puts "Changelog for version #{version_number}+#{build_number}: #{changelog}"
  end
end

Change Log / Metadata

mhadaily

platform :android do
  
  desc "update git"
  lane :git_actions do
    # Ensure that your git status is not dirty
    ensure_git_status_clean
    #add git tag
    add_git_tag(build_number: version_number)
    # Commit the version bump
    commit_version_bump
    # Push the new commit and tag back to your git remote
    push_to_git_remote
  end
  
  # and many more 
  # capture screenshots for android
  # upload to Google Play
  # Run tests
  # generate icons
  # add beta badge 
  # lots of other plugins 
end

more Lanes

mhadaily

  desc "Build and release to internal tester"
  lane :internal do
    prepare(release: false, environment: 'development')
    setup_new_changelog
    release_to_store(track: 'internal')
  end

  desc "Deploy a new version to the Google Play production / First to beta and then we can promote to Production Manually"
  lane :prod do
    prepare(release: true, environment: 'production')
    setup_new_changelog
    release_to_store(track: "beta")
  end

  desc "Deploy a new version to the Google Play Beta"
  lane :beta do
    prepare(release: true, environment: 'production')
    setup_new_changelog
    release_to_store(track: "alpha")
  end

Deployment Lanes

mhadaily

if [[ "$1" == "ios" ]] || [[ "$1" == "android" ]]; then
  bundle exec fastlane "$1" "$DEPLOYMENT_LANE"
fi

Architecture

mhadaily

Resource Capacity

Project Complexity

Resource Capacity

Project Complexity

Strategic

DDD

Decomposing 

mhadaily

customer 

id

name

interest

0
 Advanced issue found
 

customer 

id

name

email

phone

Support

customer 

id

address

availability

customer 

id

name

payment method

 

Accounting

Bounded Context

 

 

By

Namespace

Folder

Project

mhadaily

customer 

id

name

interest

Sales

customer 

id

name

email

phone

Employees

Ubiquitous Language

mhadaily

customer 

id

name

interest

Sales

customer 

id

name

email

phone

Employees

customer 

id

address

availability

Marketing

customer 

id

name

payment method

 

Accounting

Context Map

mhadaily

customer 

id

name

interest

Sales

customer 

id

name

email

phone

Employees

customer 

id

address

availability

Marketing

customer 

id

name

payment method

 

Accounting

Shared

Kernel

 

e.g.

USER

Authentication

mhadaily

customer 

id

name

interest

Sales

customer 

id

name

email

phone

Support

customer 

id

address

availability

Order

customer 

id

name

payment method

 

Accounting

Open Host Service

mhadaily

Sales

Catalog

E-commerce App

Domain

Domain

Infrastructure

Infrastructure

Presentation

Presentation

Application

Application

mhadaily

https://slides.com/mhadaily/flutter-architecture-inspired-strategic-domain-driven-design

mhadaily

├── lib
│   ├── catalog
│   │   ├── api
│   │   │   └── index.dart
│   │   ├── application
│   │   │   └── catalog_facade_service.dart
│   │   ├── domain
│   │   │   ├── entities
│   │   │   │   └── product.dart
│   │   │   ├── interfaces
│   │   │   │   └── product_interface.dart
│   │   │   └── logic
│   │   │       ├── catalog_logic.dart
│   │   │       └── get_official_product_manufacturer.dart
│   │   ├── infrastructure
│   │   │   ├── data_sources
│   │   │   │   ├── product_local_data_provider.dart
│   │   │   │   └── product_remote_data_provider.dart
│   │   │   ├── models
│   │   │   │   └── product_model.dart
│   │   │   └── repositories
│   │   │       └── product_repository.dart
│   │   └── presentation
│   │       ├── catalog-detail
│   │       │   ├── bloc
│   │       │   │   ├── bloc.dart
│   │       │   │   ├── catalog_detail_bloc.dart
│   │       │   │   ├── catalog_detail_event.dart
│   │       │   │   └── catalog_detail_state.dart
│   │       │   └── catalog_detail_screen.dart
│   │       ├── catalog-list
│   │       │   ├── bloc
│   │       │   │   ├── bloc.dart
│   │       │   │   ├── catalog_list_bloc.dart
│   │       │   │   ├── catalog_list_event.dart
│   │       │   │   └── catalog_list_state.dart
│   │       │   └── catalog_list_screen.dart
│   │       └── widgets
│   │           └── catalog_name.dart
│   ├── common
│   │   ├── config
│   │   │   └── config.dart
│   │   ├── exceptions
│   │   │   ├── exceptions.dart
│   │   │   ├── local_exceptions.dart
│   │   │   └── server_exceptions.dart
│   │   ├── helpers
│   │   │   └── helpers.dart
│   │   ├── platform
│   │   │   └── connectivity.dart
│   │   ├── utils
│   │   │   └── utils.dart
│   │   └── widgets
│   │       └── widgets.dart
│   ├── injections.dart
│   └── main.dart

mhadaily

Advanced

── catalog
│   ├── logic
│   │   ├── catalog_logic.dart
│   │   ├── get_official_product_manufacturer.dart
│   │   └── product_repository.dart
│   └── presentation
│       ├── catalog-detail
│       │   ├── bloc
│       │   │   ├── bloc.dart
│       │   │   ├── catalog_detail_bloc.dart
│       │   │   ├── catalog_detail_event.dart
│       │   │   └── catalog_detail_state.dart
│       │   └── catalog_detail_screen.dart
│       ├── catalog-list
│       │   ├── bloc
│       │   │   ├── bloc.dart
│       │   │   ├── catalog_list_bloc.dart
│       │   │   ├── catalog_list_event.dart
│       │   │   └── catalog_list_state.dart
│       │   └── catalog_list_screen.dart
│       └── widgets
│           └── catalog_name.dart
├── common
│   ├── config
│   │   └── config.dart
│   ├── exceptions
│   │   ├── exceptions.dart
│   │   ├── local_exceptions.dart
│   │   └── server_exceptions.dart
│   ├── helpers
│   │   └── helpers.dart
│   ├── platform
│   │   └── connectivity.dart
│   ├── utils
│   │   └── utils.dart
│   └── widgets
│       └── widgets.dart
├── data_sources
│   ├── product_local_data_provider.dart
│   ├── product_model.dart
│   └── product_remote_data_provider.dart
├── injections.dart
└── main.dart

mhadaily

Simple

Sales

Common

Catalog

E-commerce App

Presentation

Presentation

Screen

 

Logic Holder

BLoC

Widgets

Screen

 

Logic Holder

BLoC

mhadaily

Utils

Sales

Common

Catalog

E-commerce App

Domain

Presentation

Presentation

Presentation

mhadaily

Domain

Optional

Business logic

 

GetLatestNewsWithAuthorsUseCase

Sales

Common

Catalog

E-commerce App

Logic

Data

Presentation

Presentation

Presentation

Text

Remote Data Source

Local Data Source

Geo Location, ... 

Raw Data

Raw Data

Raw Data

Models

Logic

Repositories

mhadaily

Summary

mhadaily

mhadaily

We learned

  • Code Quality Automation
  • CD / CI
  • From Code to User
  • Architecture

Majid Hajian

mhadaily

Slides/Codes

slides.com/mhadaily

SVG icons credited to undraw.co