Convert Angular app to Flutter
100% Custom Design
Support MQTT (real-time)
Support GraphQL
~200 Screens
~4 Months
"Flutter is an open-source UI software development kit created by Google. It is used to develop applications for Android, iOS, Windows, Mac, Linux, Google Fuchsia. and the web. The first version of Flutter was known as codename "Sky" and ran on the Android operating system."
Nor is it native development
import 'package:flutter/material.dart';
class FhFullScreenLoading extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
child: const Center(
child: Text('Loading...'),
),
),
),
);
}
}
import 'package:fh_ui/fh_ui.dart';
import 'package:flutter/material.dart';
class FhTwoToneText extends StatelessWidget {
const FhTwoToneText(this.first, this.second);
final String first;
final String second;
@override
Widget build(BuildContext context) {
return RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: first,
style: TextStyles.openSans14regular.copyWith(FhColors.darkBlue),
),
TextSpan(
text: second,
style: TextStyles.openSans14light.copyWith(FhColors.darkBlue),
),
],
),
);
}
}
"Dart is an object-oriented, class defined, garbage-collected language using a C-style syntax that transcompiles optionally into JavaScript. It supports interfaces, mixins, abstract classes, reified generics, static typing, and a sound type system."
(flutter_bloc)
MQTT
HTTP
(DB)
xDataProvider
yDataProvider
xRepository
BLoC
Widgets
yRepository
zRepository
BehaviorSubject<Profile> profile$ = BehaviorSubject<Profile>.seeded(null);
Future<Profile> fetchProfile() async {
final Uri uri = fhHttpClient.getUri(heimdallEndpoint, profilePath);
final http.Response response = await fhHttpClient.get(uri);
if (response.statusCode == 200) {
final dynamic body = jsonDecode(response.body);
final Profile profile = Profile.fromJson(body);
profile$.add(profile);
return profile;
} else {
_logger.warning('Could not fetch profile.');
return null;
}
}
Stream<Profile> getProfile() async* {
await for (final Profile profile in profile$) {
if (profile != null) {
yield profile;
}
}
}
Stream<Profile> getProfile() async* {
yield* _profileDataProvider.getProfile();
}
ProfileHomeBloc() {
_subscription = _profileDataProvider.getProfile().listen(
(Profile profile) {
add(SyncProfileEvent(profile));
},
);
}
@override
Stream<ProfileHomeState> mapEventToState(
ProfileHomeEvent event,
) async* {
if (event is InitProfileHomeEvent) {
yield* _mapInitProfileHomeEventToState();
} else if (event is SyncProfileEvent) {
yield* _mapSyncProfileEventToState(event.profile);
}
}
Stream<ProfileHomeState> _mapSyncProfileEventToState(
Profile profile,
) async* {
yield ProfileHomeLoadedState(profile);
}
body: BlocProvider<ProfileHomeBloc>(
builder: (BuildContext context) => ProfileHomeBloc(),
child: BlocBuilder<ProfileHomeBloc, ProfileHomeState>(
builder: (BuildContext context, ProfileHomeState state) {
if (state is ProfileHomeLoadingState) {
return FhFullScreenLoading();
} else if (state is ProfileHomeLoadedState) {
return ProfileHomeBody(state.profile);
} else if (state is ProfileHomeErrorState) {
return FhFullScreenError();
} else {
return const Center(
child: Text('ProfileHome'),
);
}
},
),
),
What is difficult of CI in Mobile development
1- Android Emulator required for instrumentation tests
2- iOS simulator required for iOS UI tests
3- iOS apps cannot be build on linux / windows
4- Shared code signing for all teams/members
Tools?
Travis CI
Circle CI
Jenkins
Github Actions
GitLab CI
Code Magic
Never Code
Fastlane
Commandline
platform :android do
# your lanes for Android here
end
platform :ios do
# your lanes for iOS here
end
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
platform :ios do
desc "Push a new release to TestFlight"
lane :build_and_deploy_testflight do |options|
setup_travis
prepare(release: false)
setup_new_changelog
upload_to_testflight(
skip_waiting_for_build_processing: true,
ipa: "Runner.ipa",
username: username,
changelog: changelog,
)
slack(
message: "#{version_number}+#{build_number}:
iOS beta released to TestFlight \n
Changelog: \n #{emojified_changelog}",
slack_url: slack_url,
)
# git_actions(release: false)
end
end
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}"
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",
silent: true,
clean: true,
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
Share one code signing identity across your development team to simplify your codesigning setup and prevent code signing issues.
match
🔄 | Automatically sync your iOS keys and profiles across all your team members using git |
📦 | Handle all the heavy lifting of creating and storing your certificates and profiles |
💻 | Setup codesigning on a new machine in under a minute |
🎯 | Designed to work with apps with multiple targets and bundle identifiers |
🔒 | You have full control over your files and Git repo, no third party service involved |
✨ | Provisioning profile will always match the correct certificate |
💥 | Easily reset your existing profiles and certificates if your current account has expired or invalid profiles |
♻️ | Automatically renew your provisioning profiles to include all your devices using the --force option |
👥 | Support for multiple Apple accounts and multiple teams |
✨ | Tightly integrated with fastlane to work seamlessly with gym and other build tools |
#Matchfile
git_url("git@github.com:YOURACCOUNT/YOUR_CERT_REPO.git")
storage_mode("git")
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["no.futurehome.futurehomeApp"])
username("majid@futurehome.no") # Your Apple Developer Portal username
platform :ios do
desc "Sign 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
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
build_number = current_build + 1
puts "new build number now is #{build_number} and build version is #{raw_version}"
end
end
Build Version
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", "build", "ios", "--build-number=#{build_number}", "--build-name=#{version_number} ", "--release", "--no-codesign")
end
end
end
Run Test / other commands
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
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
Q/A?