Guide:
Flutter for Enterprise
Build your app the right way
Editorial note Author: Łukasz Kosman
CEO & Co-Founder
of LeanCode
Flutter has become enterprise ready at
However, building the app for an enterprise is a whole
an unprecedented speed from its origins in late 2018. new thing. It takes a different approach to organizing
Highly performant, cross-platform apps coupled
the project, the code, the design system, testing
with excellent developers’ experience made
practices, and the overall product roadmap.
a compelling case for both business and tech owners
all around the world. As a result, we could have Therefore, we wanted to build this comprehensive
witnessed not only the apps coming from scaleups
guide to pave the way for the enterprise teams to build
like Wolt and Delivery Hero but also corporate
players, even from well-established banking industry their large-scale products based on Flutter. This ebook
like Credit Agricole.
is based on an experience gathered by our team of 30+
Flutter Developers from LeanCode while working
Based on those cases, we can say for sure that the vast on the corporate applications for Credit Agricole Bank,
majority of guidelines and best practices for Flutter Millennium Bank, and other multinational corporations
were published with smaller apps for startups in mind. which build their core services and products based
on Flutter.
List of content:
The following chapters will help you to better prepare for building your large-scale apps using Flutter:
1 Flutter at Scale. We start with comprehensive guidance on how to organize a developers’ team.
Do you wonder what is the optimal structure of code from the long-term perspective? How to ensure
code ownership? How to maintain the app and its codebase? We will provide a detailed, case-based
argumentation for the most important choices in your future Flutter enterprise project.
2 The Architecture of the Flutter App. We will explain what it takes to build a feature in a large-scale app
and how to make the right choices from the app’s architecture perspective to make it sustainable
in the long run.
3 Automated UI Testing. Till recently, this was a missing element in the Flutter universe. With the arrival
of Patrol, the ultimate UI Testing Framework for Flutter, you are protected from regression bugs
and can run integrated e2e tests between feature squads in your development team.
4 Add to App approach. In an enterprise environment, it is not always possible to start green-field projects.
It takes time to substitute the existing app, and business owners cannot withhold the arrival of new features
from the backlog. Therefore, it is sometimes advisable to use an add-to-app approach. Read about the best
practices on how to organize the project, navigation, communication, networking, and other important
elements which will help you to add Flutter to your native apps successfully.
5 Staff Augmentation 2.0. In order to develop large-scale apps in a short manner of time, you often find
yourself in need of scaling up the team. Yet, what you truly want to achieve is to attract the best experts
who can facilitate the knowledge transfer process to your team while building the Flutter app based
on a proven experience from other enterprise apps. At LeanCode, we have experts who not only are
all computer science graduates from the best technical universities in Poland but also have vast
experience in building more than 60 Flutter apps. We will share the best practices on how to build
hybrid teams to get the most out of the staff augmentation services.
Table of Contents
Flutter at Scale 4
Feature-Based Mobile Architecture 19
The Role of UI Testing and How Patrol Can Help You Improve Them 23
Flutter Add to App - Overview and Challenges Based on Real-Life Case 27
Building Large Scale Apps with LeanCode - Staff Augmentation 2.0 34
About LeanCode 40
Flutter at Scale Author: Mateusz Wojtczak,
Senior Flutter Developer &
Head of Mobile at LeanCode
Building an enterprise-scale application requires
The following is divided into 10 chapters:
a specific approach to organizing the team and the
code they create. After the architecture, organization,
and communication are pivotal to secure yourself
a smooth path towards the milestone on the roadmap 1 Code ownership
for large-scale projects.
Why building an app for an enterprise is so much 2 Feature-based project structure
different than for a startup or small scaleup?
When we’re talking about large projects, we mean
3 Running tasks in monorepo
a project spread across different teams, companies,
and business domains because development is only
4 Cross-squad communication
a small part of the whole. In small projects, we often
know every developer who’s working with us. There’s
even a huge possibility that a backend dev that serves 5 Navigation
us some API sits next to us the whole time. It’s much
easier not to worry about some issues when we don’t
encounter them. In large-scale apps, we are rarely in
6 Localization & Translation Management System
a situation where, while building a powerful contract-
based asynchronous communication with our
7 End-to-end automated testing
backend, we can just go get some coffee with
the backend team and discuss everything.
8 Strongly-typed contracts
So, in large projects, we just want to help ourselves -
to deal with multiple obstacles that we cannot directly
resolve - obstacles that are not only related to code. 9 Taming legacy
Let’s see how we can wrap our heads around this level
of complexity not only in terms of code but, most of all,
in communication.
10 Design system
In this study, we would like to share our thoughts
and experiences from our two-year-long journey of
developing a very large banking mobile application
using Flutter working with 30 Flutter Devs spread
across 15 teams.
Flutter For Enterprise
Code ownership Let’s put some ground rules on how we define code
ownership in a Flutter project of this size.
Let’s talk about code ownership.
First and foremost, every line of code is owned by
In a small project done by a single team, there isn’t too a team. Not a single person, a team. Even if the team
much to talk about (or is it?). The team wrote it;
consists of a single developer, it is still the team's
they own it and are responsible for it. Although, they responsibility to maintain it. Developers are rarely
might internally split the responsibility further, it doesn’t self-governing, and there is always someone else
change the external perception - the code is owned by (“business people”) that prioritizes the work.
a team. The reality might be slightly different, but it will
boil down to a variation of this. If the team does not own That’s why a single person should never be
the code, it will deteriorate quite quickly. So let’s responsible for a piece of code. If that happens,
pretend that this happens every time.
then doing anything with it will require going through
so many managers with different priorities that it will
However, what if we are talking about a not-so-small never, ever change. Teams are part of the structure
project? In this case, there were, at the highest point, at a level that gives them enough power to correctly
30+ mobile developers split into 12 business teams prioritize the work around the code and pay for it
(also known as squads). We might say that this is just
by “wasting” hours of work. A single person cannot do
a simple extrapolation - each team owns the code they that.
create. That would be true. Mostly. The problem arises
when we realize that the teams work on a single app This leads us to the fact that, even if a team has
and single codebase. A. Single. Codebase.
a single dev and they switch teams (or leave),
So, by another extension, if every team creates the the responsibility stays with the team. There rarely
code in the same codebase, then is every team is a situation where a team gets dismissed or the whole
responsible for basically everything?
staff of a team leaves. That’s why there will always
be a person that owns that piece of code (even if from
In that case, no one will be responsible for anything. a business perspective).
There might be places where some people will feel
responsible for themselves, but that will be an And by responsibility, we mean that the code is
exception. If you find a bug, nobody will be willing to fix maintained by the team. If a bug is found in a code
it because that will always be someone else’s problem. owned by the team, their responsibility is to reserve
If you need another feature, you will need to write it time for a fix and do it. They also need to ensure that
yourself because no one cares.
the code adheres to the always-changing coding
standards. It doesn't, however, mean that only they
Team ownership works because the team members are allowed to add new code there. If there is a need,
feel responsible for what they create. There is a natural someone else outside of the team can edit the code -
limit to this - if too many people will “feel” responsible but that does not change the one responsible;
for a piece of work, no one will really feel responsible it’s still the team.
because always someone else will feel more adequate.
And although, I am rather pragmatic when it comes to
We need to follow the limit and put boundaries exactly all the rules, meaning “there can always be
where they are. In a project of this size, these will not exceptions”, this is the one that I think should be
come out naturally. We need to put them and abide by followed with religious devotion. Otherwise,
the rules explicitly. If we do this right from the start,
there will be problems.
it will not feel artificial, and it won’t be questioned.
After all, the rule is natural. And everyone feels like it.
Everyone feels responsible for the work they do,
but we need not allow a situation when too many
people feel that for the same piece of work.
That is the problem, and that’s why we need to
overcome it by introducing explicit code ownership.
Flutter For Enterprise
So, we know that we need to preserve ownership, but There are real things that are problematic and will bite
how should we define who owns which part of the you from day one.
code? When a whole application is developed by
a single team (be it a small mobile app or
In every project, there always is a “common” folder
a microservice on the backend), the rule is simple:
for things that are used by everyone and a place where
they own everything they create. There will be some the local packages are bootstrapped and combined to
shared code in terms of internal libraries, but these
create the final application binary.
will be created and maintained by a different team,
and we probably won’t see the code at all - we will
We really don’t want these kinds of packages because
just consume it.
they don’t have clear ownership - everyone writes
code there (directly or indirectly), but no one feels
In a big Flutter application that is developed at a rapid responsible for it. We also can’t ditch them completely
pace, that’s not that simple. Effectively everyone works because we need to have some shared ground;
on a single codebase, sometimes with not-so-clean otherwise, we would re-implement the same thing
business responsibilities. Of course, we could over and over again (which will happen, but with
introduce very clear boundaries and physically common - at a much smaller scale). We also need
separate the teams, but that would introduce so much an “app” package - the one that brings everything
friction and so much grunt work for the one responsible together. This one is even more important than
for putting it all together that the development would the first one - we can’t replicate it. We are required to
grind to a halt. We need to find a different way.
have a single entry point to the app. And everyone
needs to contribute to the app.
And, to be honest, the answer is not that far away -
let’s split the project into packages, the same way we We cannot make an exception for these two. Not
would do when splitting physically, but keep everything abiding by this rule will be far more disastrous than
in the same repository and reference (& compose) following it. This means that we need to find a way.
using just normal path references.
What we found out, the approach that works is not to
This will make the boundaries clean, but the walls won’t find some team that naturally fits to be the owner of this
be so high - you can always look into other teams' kind of code. What works is to create an “artificial”,
codes without any fuss because they are right there,
technical team. The team should be responsible for
a click away. You also compile everything at once,
all the things that no one else wants.
so provided that you enforce that on CI (and you
should!), everything will work more or less at all times. Don’t mistake it for a team of exiles, they have a much
You will also, just by a random chance, test other more important role.
people’s code.
And although it might seem like a bad idea. They don’t
As with everything, this is not a silver bullet. This bring clear business value. They seem like a team that
approach has problems that need to be overcome.
no one needs - no business product owner, they don’t
For once, because the walls are not that high,
create end-user features, and you can’t easily plan
there is the possibility that someone will modify
their work in sprints.
the code behind the owners' back. This is something
that might be a problem when your team does not want
to adhere to rules but is primarily an organizational one
- and I would say that if that happens, it means that
you have other, more critical problems.
Flutter For Enterprise
However, their work is vital. You can’t possibly allow Every small technical bug, every small optimization,
30+ people to work on a single codebase and expect and every small feature that is required to make
that they will come up with a coherent, maintainable the app work and be maintainable will, in the end,
architecture. That they will never stomp on each be their responsibility. The technical team,
other’s toes. That they will spontaneously combine although not at all times, will have plenty of work.
everything into a single application. That won’t “just”
happen. There might be natural-born leaders that will Because at the end of the day, someone has to be
do this work because they will feel like they are the responsible. And if no one wants to be responsible,
ones that should do it, but leaving that to chance will the technical team will be.
make the process longer and more painful.
Feature-based project
Every project of this size requires a team of
“architects”, someone that overlooks
structure
the development, someone that puts the basics
Starting the project often starts with us thinking about
in place and puts the ground rules of development.
how we can structure this one better than
The team is responsible for the code that no one needs,
the previous one. There are multiple propositions of
and they are responsible for making sure that
structuring code; we have a lot of architectural patterns
everything works together. They put in the ground
that enforce or promote some. There are two main
rules, and they ensure that they are followed. They
approaches - we call them horizontal and vertical
design how to (technically) communicate, and they
structures. You may have heard about layering your
try to satisfy every developer need that is not directly
code. It’s a common thing that people propose to divide
related to the business but needs to be done
code into layers that handle different abstractions.
nevertheless.
The layers often go from the end user (UI layer) through
Keeping up with every developer’s needs can be
some view & application logic until they reach pure
exhausting. And that is not the only responsibility of
data - that means some domain logic or data contracts.
a technical team. As I said - they are the ones
That’s what we call a horizontal approach.
responsible for the overall architecture, for keeping
everything in proper order. That, although it might not
seem as much, is a lot of work.
Repositories package
AccountsRepository LoansRepository BenefitsRepository
Blocs package
AccountsCubit LoansCubit BenefitsCubit
UI package
AccountsDetailsPage LoansPage BenefitsPage
Flutter For Enterprise
As we can see in the diagram, in this example,
The team is most likely divided into business squads
we have a UI layer with some Flutter widgets,
that have their responsibilities according to some
an application logic layer with some Blocs/Cubits,
business domains. In that way, there are also people
and a data layer with repositories that represent our in squads that are crucial to proceed but are not even
data sources. While it may not seem so bad, it can next to software development. Ok, so we have our
actually corrupt our project in the long run. Let’s see “Loans Squad Daily Meeting”. We can talk about
what that architecture really means.
business requirements, and as developers, we can
extract business knowledge from other people
Why do we structure the code? To organize it better. and process it so that we know how to build
Why organizing? To communicate better.
the software around that.
That’s really something that we can build our code In this domain-driven workflow, let’s come back to
structure on. As it was said in previous chapters,
code structure. Let’s try to project our communication
we really need code ownership. So, let’s try to answer model onto that:
this question: Who owns the repository package?
Who owns the widgets package?
Well, everyone and no one, of course. We could bring
an iconic software aphorism in here - Conway’s Law.
It states that:
“
Any organization that designs a system
(defined broadly) will produce a design
whose structure is a copy of
the organization's communication
structure.
— Melvin E. Conway ”
So, what does it mean for us? It means that no matter
how we structure the code, it would still end up shaped
similarly to the organization. That means we would like
to project our organization’s communication structure
onto our code so that we don’t have to divide our code
artificially; just do it as we do with the rest of our work.
In that case, let’s ask ourselves: how do we
communicate? Is there a “Repository Daily Meeting”?
Is there a “Widgets Daily Meeting”? Not at all.
Flutter For Enterprise
Repositories package Loans package Benefits package
AccountsRepository LoansRepository BenefitsRepository
AccountsCubit LoansCubit BenefitsCubit
AccountsDetailsPage LoansPage BenefitsPage
Now we can see that if the same developers work
How can we organize it inside the package? Let’s use
on anything related to loans, then they don’t have to the same assumptions as with package layering. Let’s
work outside their packages. This way, we also try to make a vertical approach inside packages. To do
minimize code conflicts between squads. Our daily that, we need to define what the core unit of our work
work is going to be revolving around those packages is: in our example, it’s a feature. In agile development,
that are directly owned by our squad. That’s what
a developer does a user story enabling users to do
we call a vertical approach, and it allowed us
something related to the business. If we want to let
to maintain a large project code structure.
users see their loan agreement documents, then we
have to do everything from UI to data sources because
Also, in this example, we show a 1:1 relation between otherwise, the feature doesn’t make sense. So, we can
squads and packages. Actually, it’s a bit of a different define our features inside a package, and inside every
rule. As it goes with previous code ownership feature, we can split our work into blocs, widgets,
assumptions, we allow multiple packages per squad. pages, data classes, etc. What happens inside
It’s perfectly normal that there are some larger a feature directory stays there. For other devs, it’s
business domains that are still strictly related to
a black box as long as they don’t need to go in there.
the squad, but devs need another package for that.
For example, “Onboarding Squad” can have their
onboarding customer journey but also a KYC (Know
Your Customer) module that seems to be a separate
thing and can be organized in a separate package.
The important thing is to remember that one squad
can own many packages, but every package should
have only one owner.
However, this doesn’t exhaust the subject of code
structure. What about the actual code inside
the package? It’s also essential to have some
guidelines here. That’s because you want your code
structure to be predictable. When one developer
swaps squads with another, you don’t want them
to spend weeks studying the code before anything
really happens.
Flutter For Enterprise
Running tasks in mono repo It helps maintain many popular packages like
FlutterFire (Firebase libraries for Flutter), Flame (game
The next thing that’s crucial in large projects is engine), or Flutter Community Plus plugins. However,
simplicity and consistency in doing daily jobs.
the most important thing is that you can run any shell
What we mean by that is we don’t want to have trouble script in each package directory, and you can filter
restoring dependencies in our package.
what packages it should consider.
A developer would want a simple way for running tests It’s really helpful because you can define such scripts
just in their packages, running formatting, etc. To run all and filters once, write some simple docs (although
these things in a multi-package environment, we need melos.yaml file is already pretty convenient to read)
a task runner like Make or “*insert your favorite and help new developers on your team be quickly
language*ake”. It would be really convenient if we had
onboarded in the environment. Having a task runner
a task runner that is somehow aware of Dart/Flutter also simplifies building a CI/CD pipeline because some
packages and is adjusted to the ecosystem we work in.
tasks are already defined there.
Fortunately, we found that there already are things
Last but not least, you can use concurrency for your
for that. One that we have selected is Melos.
scripts. But beware of that because your Flutter scripts
It’s officially described as “A tool for managing Dart may fail to run in parallel. In some cases, there will be
projects with multiple packages.”. That’s pretty much locks so that even if you start it concurrently, the tasks
exactly what we need looking at the description. would still have to wait for each other. Also, Melos
command output can end up messy when a lot of
packages are being considered since it doesn’t
Melos provides a couple of scripts itself, such as:
maintain the order - simply speaking, the output is
Automatic versioning & changelog generation mangled from all the executions.
Automated publishing of packages to pub.dev
Local package linking and installation Still, it’s a great tool, and you probably should use it,
Executing simultaneous commands across especially when working with large Flutter projects.
packages
Listing of local packages & their dependencies.
Flutter For Enterprise
Cross-squad communication Our solution to that is, as an idea, quite simple.
We divide the communication into two groups -
synchronous when we need some data or we need
The physical split into (local) packages does not solve to do action “right now”. This covers, for example,
all the problems. And, unfortunately, they amplify the aforementioned authorization or getting
others. Life is not simple. Although you might have
the accounts list. And asynchronous, where we
a “clean cut” when it comes to team responsibilities, invert the dependency and expose that some action
you must assume that there will be a point when occurred as an event. The other squad can subscribe
someone will need someone else.
to it and react, updating their data. This covers
updating the account (which is a reaction
With twelve squads and a banking app, some in the accounts team) to a new transaction
features depend on data from other squads,
(an event raised by the payments squad).
and some invalidate data in others.
We also put some more concrete constraints there.
You can’t possibly have an app that has 12 completely For example, the synchronous facade should expose
separate features. Take, for example, the accounts
the data as streams (backed by `BehaviorSubject`)
in a banking app - it is the most essential feature to see
so that integration and auto-update (after an event)
a list of accounts that you own. Every feature depends are easily doable.
on it this way or another. For example, when you see
a transactions list (another basic feature), you must see Because of that, the data should be automatically
it in the context of an account. On the other hand,
provided (when requested), and every failure should be
when a payment is made, it changes the account automatically retried. The error should not, in most
balance, another property of the account.
cases, be visible through a facade because you can’t
react to it sensibly - and if you allow reacting to it,
As you can see, there always will be dependencies you can easily react too much. Multiple teams will
between teams. What is worse, the dependencies will show the same error message.
not be unidirectional. There might be cases where two
teams need to communicate with each other in both The other kind of interaction, the one where you don’t
directions. One example is: you need some data
need data but you need to do something, is even
(must be synchronous), then you do something
simpler - you expose a properly named & self-
and expect someone else to update
contained method that does what you need,
the aforementioned data (might be asynchronous).
the way you need. Making more rules here is really
unnecessary.
But that is just one example, and arguably simpler
(to maintain, not to develop) one because you can The other kind of facade, the asynchronous one,
easily invert the control by, e.g., using events, resulting is mostly similar, except it does even less. If you do
in what is effectively uni-directional dependence.
some kind of action, you expose an event that
But there are cases where you need access in both describes what occurred. For example, when the user
directions. I personally worked on a squad that makes a payment, you expose a data package that
provided basic authorization functionality, which
tells you what the source and destination accounts are,
was used by arguably every squad. On the other hand, what the title is, and what amount it covers.
when displaying some auth-related configuration,
Nothing more is exposed. You also don’t store
I needed the information from accounts. This couldn’t the events - we’re not doing event sourcing or any
be made asynchronous - everything needs to be persistent-event architecture. After all, every event
provided now. And we have a cyclic dependency that, we publish is meant to update some UI or trigger data
although solvable without too much fuss, is another reload. The heavy lifting is done on the backend.
thing to maintain.
And because of the plethora of options, we need to put
some constraints there so that we can do everything
with ease. And to make it last.
Flutter For Enterprise
Navigation
Another form of cross-squad communication is cross-
squad navigation. In such a huge domain split between
twelve different business squads, navigation to many
pages in the applications will be from a different squad.
For example, from the page presenting details of a bank
account that is owned by the accounts squad,
Separation from Flutter
there must be a way to seamlessly navigate to a bank
transfer that is owned by the payments squad.
Navigation is a part of business logic, and because of
Such a navigation use case needs to be addressed this, it should be available from the bloc/cubit
while adhering to the code ownership principles and business logic code. However, usually, we avoid
code separation between multiple dart packages. introducing a dependency on Flutter in business code.
This constraint means that we have to trigger
the navigation on the view side somehow. This is
To achieve those objectives, we implemented custom usually achieved by creating some pseudo-state
navigation based on the Flutter standard navigation or by introducing an additional stream for navigation
with the following objectives: events.
Separation of navigation targets from the page In our solution, because the navigator state is
implementatio independent of Flutter, we have no issue with passing
Allowing for passing the context data between it to the business logic, and we can execute navigation
page right from the bloc/cubit.
Separation of a navigator from Flutter
Separation from page
implementation
To allow for navigation between different squads
without exposing the specific page implementation
between pages and squads, the page definition has
been split between the globally visible target
and package-private builder, which builds the page
based on data from the target.
The targets are similar to intents known from the
Android development world. A target is a class
representing a specific page in the app. It can also Localization & Translation
contain additional context data, which needs to be
provided in order to build the page. For example,
Management Systems
the target for the bank account details page will
contain the bank account number of the account
The next important thing when developing large
that should be displayed.
Flutter applications is being available to people from
different cultures, locales, and countries. Thus,
The target is an identifier necessary to execute
we need a way to set up localization (often put as l10n).
the navigation; it must be available to all squads that Localization “is the process of adapting a product's
might need to navigate to the page, and thus,
translation to a specific country or region”.
the targets are defined in the central navigation
package.
Flutter For Enterprise
It’s part of a larger process called internationalization The problem is that information about what to localize
(or i14n). There are hundreds or thousands of string and how to do it doesn’t come from the development
values used across the whole app, and they have to be at all. In most cases, it is business knowledge that has
translated according to the current user locale on their been processed by many people in the organization:
device or personal setting within the app. In our case, marketing people, product owners, translators, and
there were over 5500 string values that we needed
others. Developers are only one small part of that. This
in two languages for the first release, but we also gives us a reason to think that localization is no longer
needed the possibility of adding more languages later a development thing. It’s a product thing. Thus,
at a low cost. That’s why you need a localization localization files stored in code repositories should not
solution.
be a single source of truth for that. Going back to code
ownership, localization should also be owned, and that
Flutter already has a preferred solution for l10n.
ownership should be transferred to business people.
There are two main packages from the Flutter team:
one called flutter_localizations (for l10n) and another Fortunately, there already are things for that. They
called intl (for i14n). These two are connected, and you are called Translation Management Systems and
can say that they work together to make your app typically are web apps optimized for localization
available to everyone. The most popular approach is
workflows. In our project, we used Phrase as a TMS
to use those packages along with .arb format files that tool because it supports the .arb format and has a lot of
contain “key-value” information about each localized convenient features like comments, activity tracking,
string. roles, and advanced translation workflows.
TMS tools can help you with things like:
Tracking the history of translation terms
Discuss localization through comments
Manage a glossary of business domain-related
terms that could be uncertain for translators
Tagging terms
Importing keys and values from l10n files
Exporting translations to l10n files
Versioning translations (like Git in the development
world).
HELLO!
HOLA!
CZEŚĆ!
Flutter For Enterprise
Another important thing is to remember that
As we can see, it’s a specifically designed workflow
the translation workflow behaves in a different way for working with localization. TMS tools are crucial
than the development process. Thus, we need to have to have in your project (even if it’s not large) because
tools that know about it. In a large project, we have using them is simple, and a lot of l10n-specific things
business people that make initial localization terms, are taken care of for us.
then it goes to translators (probably more than one)
that also send some translations to native speakers
Last but not least, check the trial version of your TMS
to consult and confirm. After that, localizations can be tool first. It’s really important because a lot of TMS tools
accepted by translators or changed again.
have different features and handle some specific file
formats in different ways, so especially with Flutter .arb
That means we need a way to verify some already files, which are not so popular in the l10n world,
translated terms. It’s hard to visualize this process, so you better be sure that the tool you are going to pay for
let’s take a look at the Phrase workflow diagram: is compatible with your development.
Automatic UI tests
I think we all can agree that UI tests are great. They
can simplify testing tremendously. They can lessen
the number of regressions. They can give you feedback
if you break something right away. They might be hard
to maintain, and you probably need to have a dedicated
team just for writing UI tests, but it’s still worth it.
Until recently, however, there was a problem with UI
Tests and them working within the Flutter app.
There were some building blocks like Flutter Driver
to test Flutter parts, and you could use Appium for
the native parts, but that is cumbersome. It gets even
worse if you have two separate teams: one writing
the app, the other writing the tests.
Flutter For Enterprise
Therefore at LeanCode, we have created a totally The only solution to that is to make the UI tests
new approach that will bridge the gap between the a normal part of the SCRUM team. UI tests should
native part of the app and the Flutter interface. probably be a part of the acceptance criteria for each
and every user story. Only then will UI tests
not cannibalize developer time, and everyone will
It is called Patrol, and it is an open-source framework want to write them - not only devs & testers but also
for writing UI automation tests in Dart for Flutter apps.
business people. Everyone will have a stake in making
It helps to handle all kinds of permission dialogs them so that they will get created. Since they will be
between OS and the Flutter app so that you can easily a part of the SCRUM team and will develop
test the scenario where you need to take the code from simultaneously with features, they will change
an incoming SMS or open push notifications. with them (because most of the time, the changes
in the app and in tests would be symmetric), and that
will ensure that they will not deteriorate.
In the long run, this is really the only solution if you
have a big, multi-team project where different powers
pull in different directions.
Patrol is the final building block to make
Flutter enterprise-ready. Also, let’s not forget that UI tests are still tests -
If you want to explore Patrol and UI Testing as such, and should be treated as tests. You should run them
you should read the last chapter of this eBook which with your normal development workflow. I agree
will allow you to better understand how Patrol works. that they might take too long to run, and it might not be
feasible to run them on every build, but at least
a minimal viable subset should be run that way.
And everything else should be run periodically
Having a tool is one thing. Using it properly
(a couple of times a day at least).
is a completely different case. When the QA team
is separated from the regular development cycle,
they are working in a cascade mode. Oftentimes
writing a good test requires that the QA specialist
knows the code. Otherwise, doing the procedure
& pinpointing the code owner takes time, although,
without it, you can’t start writing the tests. Eventually,
the best person for doing the job will be someone
from the inside - a dev. And dev time is precious.
And that creates a problem:
1 It takes developers’ time,
Which, if not accounted for, might be
2 considered “wasted”,
3 So it is not in line with PO’s goals,
4 Hence they don’t want to do it,
So developers can’t provide necessary
5 things for people doing UI tests,
6 And they waste their time waiting on blockers.
Flutter For Enterprise
Contracts
We all know that somewhere there is a normal JSON
(or XML)-based API. Probably someone will like to call it
a REST API, REST-ful, REST-ish, or just “an'' API.
Or GraphQL one. That does not really matter.
No matter what API style you use, it still requires
a non-negligible amount of work.
First and foremost, you must manually manage it.
You need to design every endpoint deliberately.
You need to consider every aspect of the API: will it be
a problem to compose a request? Do I require some
other request to be done before this one? Or was
a request done after this one? How do we ensure that
the same data is passed to a couple of different
requests? Or how can we tell that the format of
the data will be exactly the same across a single “area”
of the API? Although it seems simple, it’s not really
So, our solution to that is “strongly typed contracts”.
that simple: it’s not easy to express that in a structured A concept that is now widely used both in the banking
way.
app and at LeanCode.
Yes, we have OpenAPI that allows us to express that, The idea is simple: since backend devs are the ones
and yes, although manually, we will probably be using that serve the requests, and they have the most
some tooling to manage everything. But that won’t be “business” knowledge when it comes to how things
fully automatic. And there will be some OpenAPI-code should move underneath, let them write everything.
impedance mismatch. Especially since you will either Both the request schema, the request handlers,
have both backend and frontend contracts generated and the clients for these requests. But instead of
out of the OpenAPI schema, and both generators will using OpenAPI to design that, let’s use a language
work slightly differently.
that is native to the backend and allows us to express
all the things that I was talking about earlier.
And this mismatch is not the only thing that will cause The backend language is probably some general-
problems. The clients will work the way the tool wants purpose one, so parsing it & generating a client that
them to work, which might not make sense to you.
is based on it will probably be a not-so-hard task
if we limit the feature set to only the vital things.
You will have to tweak them and then maintain them
manually. And if you use some exotic feature or design
something just slightly different than the tool authors
assumed, your code will break. Sometimes very subtly.
And this will push you towards manually writing
a client for this single request, and then another,
and then another, and you will have a set of manually
constructed requests that are unmaintainable,
probably broken, or altogether wrong.
Because of these, contract testing is a must - API
breakage will be too common to ignore. And finding
what broke will be very difficult without a robust set
of tests. Or when the backend allows for some
ambiguity.
Flutter For Enterprise
This approach has a number of benefits: You have a strongly-typed client, so if something
changes in the types, you will need to account for that
when you update the contracts. And it won’t matter
1 A single source of truth - the backend team
that it changed far away, in a request that you don’t use.
dictates how everything looks. Of course,
they do so after agreeing with the mobile
They don’t make the API versioning & supporting
team on how that needs to look. ;)
easier. You still need to do that, although particular
versioning requests are now slightly easier. You can
2 They know the flows, and they know
also enforce that the API is backward compatible
the data they need to manage, so they can
with proper linting.
preserve the meaning and communicate
that to clients using the same language.
And since you operate on high-level types and you
Plus, a sprinkle of in-code documentation.
have a limited number of types there (because, after all,
it is JSON), you can’t really express everything.
3 Since they write normal code that is readily
And everything is typed, so there is less room for
usable, they don’t waste time doing
ambiguity, and sometimes you need to make things
documentation (that will, in the end,
awkward.
be thrown away because the requirements
change).
Although there are problems, some of them small,
Since everything is code and code that is some of them amplified, all in all, it is a gigantic net plus
4
generated, there isn’t really a place for that simplifies the development and understanding of
technical omissions. Basic types are
the project substantially.
the same everywhere, so if contracts want
an `int`, you will not be able to put
Taming legacy
a `String` there. If everything compiles
(both on the backend and client side), there
When we develop a large-scale project, we often
is a very high chance that it is correct.
make decisions that become outdated over time.
5 /
Since the client is a normal Dart ( JS) code,
Also, those decisions will have to be made in the future,
so it’s inevitable. Our code is a legacy from the moment
the discoverability of it is exactly the same
we wrote it. If we wrote it again, there would probably
as the rest of the project - you just open
“the API” in VSCode or your other favorite be other things that we’d consider. So it’s okay;
we have to accept it. We can’t remove it, but we can
editor. You control how the client works,
tame it.
so you can make it readable without
sacrificing functionality.
What can we do as developers?
6 The contracts are also easily versionable
when it comes to schema - you just use git First, we can deprecate things. If some function,
/
for that and point to a particular commit tag widget, class or even whole module is being used all
to use a particular version. Do not confuse around the code, but a new way of doing things has to
this with API versioning. You still need to do be introduced since business assumptions change,
that. then just deprecate it. In Dart (like other languages),
there is an annotation @deprecated that comes
in handy for that. It’s really helpful because when there
’
What s best is that it s ’ Dart all the way, and every
are 20+ developers working on the project, they have
/
mobile developer is needs to be comfortable with it .
to know that this method or class shouldn’t be used
and what is the correct way to do that.
Of course, it’s not a silver bullet.
This does not solve every problem possible
and introduces a number of problems on their own.
Flutter For Enterprise
However, make sure you really deprecate. The worst Developers should always use design system
thing about deprecation is not obeying it.
components, and one squad should be responsible
This introduces even more corruption to our code
for the design system itself. In our organization,
and communication since both ways of doing one thing it is the Overall Design Squad that is a kind of
are still being used, and new developers don’t know “master squad” for all designers working on
what to choose.
the project. It also has developers that, while
continuously interacting with UX/UI people,
Also, we have to remember about broken windows
are developing design system concepts
in our code. A broken window in the code means
in the codebase.
a code smell or something that obviously should be
done another way, but there probably was no time
for that or for any other unpredictable reason.
We have written more about our experience
That code will be emerging as more and more
in building and implementing design
corrupted because people wouldn’t bother to refactor
systems. On our blog, you can find more
things. That’s why we want to make sure about
articles about this topic and learn about
deprecation. Because the objective is to encourage
the best practices.
refactoring, we want to minimize concepts that
discourage it - such as broken windows.
To ensure policies in our large organization, we have to You’ve been warned
maintain a technical squad because there must still be
an owner to ensure things. The technical squad owns
The areas you have listed are the tip of an iceberg.
everything that’s not related to any business domain -
CI/CD, tooling, cross-squad big picture things and one
Complex enterprise applications written in Flutter,
of those responsibilities is taming legacy. That squad
as in any other framework, require special care from
has to organize cycle status events where we can
a team of experienced developers. If you want to
monitor the progress of refactoring deprecated code.
enhance your own team with that experience,
That’s why it’s very important to synchronize across
you should consider hiring our Flutter Developers,
squads on a weekly basis or so. We don’t want
who can form hybrid teams with your in-house
the inertia to grow silently as we come to the release.
development team to work on building the next
If we control it, then it’s not so much of an inertia after
state-of-the-art applications.
all.
Design system
Last but not least is that we should also apply similar
constraints when it comes to design. The design
of our app basically means UI from the code side,
and to make our user experience top-notch, we have to
be consistent. What does it mean? It means that good
app design should have consistent behaviors,
a consistent color palette, consistent approaches to
similar user stories and so on, and so on.
Moreover, this should not come from the code
because the UX/UI and design don’t come from us -
developers. We merely implement the thoughts
and concepts of the designers that we work with.
That’s why we have to maintain a design system,
and we have to do it in tight collaboration with
designers.
Flutter For Enterprise
Author: Marcin Wojnarowski
Feature-Based Flutter Senior Flutter Developer
at LeanCode
Architecture
At LeanCode, the architecture of mobile applications
This enables greater scalability and flexibility
is driven by the experience gained during
the completion of many successful projects.
developers can work in parallel on different feature
code is less scattered, it is easy to find all code
This architecture scales from small to large projects. responsible for a featur
While some components of the architecture are enables feature-level innovation, the local
refreshed to adapt to new community standards,
architecture of a feature can be rewritten without
most remain the same due to being battle-tested
affecting other feature
on real-world projects. In this article, we highlight
self-contained and independent of other features
the design decisions made for the architecture when
developing a feature. This includes our approach
to dependency injection, state management,
Our comment section feature would look the
widget lifecycle, and data fetching.
following way:
social-app/
Imagining a feature lib
features
comment_section
As we said in the previous part, from a high-level
bloc
architecture perspective, we can see our application comment_section_cubit.dart
as a set of loosely-coupled features. We described comment_section.dart
how to organize them and do the wiring, so now we
widgets
can focus on how to build a single feature.
upvote_button.dart
Let’s say we are developing a feature for a comment
section under a post in a social media application.
A user would be able to see the list of comments,
Feature entrypoint
upvote comments, and add their own comments.
Since this is Flutter, most often than not, an entrypoint
The file structure will be feature-based. This means
for your feature will be a widget. In our case,
things related to the comment section will be closed
the widget is responsible for showing a comment
under the same directory. This is opposed to a type/
section. A feature entrypoint is required to set up all
function-based approach, where files are grouped by
dependencies used within a feature. This includes
their function.
external dependencies and those that will be injected
into the widget tree.
For dependency injection, we favor the community
standard package:provider. It allows for tying
the lifetime of a dependency to a widget tree.
The dependency is injected when the tree is created
and disposed once the tree is unmounted. The injected
values are also scoped to a specific widget tree,
further assuring us of the self-containment of a feature.
Since provider is merely a wrapper around Flutter-
native’s InheritedWidget, we can leverage Flutter tools
without locking ourselves to a different paradigm.
Flutter For Enterprise
While we acknowledge the shortcomings
Managing state
of package:provider (such as not having the compile-
time safety when consuming dependencies), we State management for UI usually boils down to two
believe alternative solutions claiming to solve this concepts: data representing the state and some
problem such as package:riverpod introduce other, functions that alter this data. In this setting, UI is
larger issues. a function of the state. package:bloc is no different
and is our solution of choice. It introduces a clear
distinction between said data and functions.
Let’s see the entrypoint of the comment section
This distinction promotes the immutability of state,
feature:
which then allows for a fully declarative UI.
Additionally, the simplicity results in an easy to reason
about code.
is observed
to create
State UI
calls
change methods
which cause
This widget accepts in the constructor all data
needed to initialize a comment section. In this case,
Transforms
it is only the postId (1a) which is then passed to the
state manager (1b). Cubit for this feature is provided (2)
to the widget tree and will be automatically disposed
when unmounting. The cubit itself also needs the API
A Cubit is responsible for guiding the behavior of
client to make requests, thus it is injected (3) (more
the UI through its state. It does not have access
on that in a later section). The ApiClient is considered
to BuildContext, making it completely detached from
a global dependency, as it is injected in the root of
the rendering pipeline. This ensures greater separation
the application. Finally, once everything is set up,
and testing in isolation. However, not all UI behavior
we return the child responsible for drawing all the UI
should be a function of the state. Most notably,
and listening to the state manager (4).
one-off events that are not worth persisting in the state.
In the case of a comment section, failing to upvote
Other than a handful of globally-injected services,
a comment could be an UI event. We don’t particularly
the dependencies of a feature are clearly defined by
care about remembering that it happened, but we
the constructor of the entrypoint.
surely want to make the UI reflect that it did. For this,
we use package:bloc_presentation, which simply adds
Disclaimer: In really large apps, while the Flutter widget an additional stream to a Bloc for one-off events called
tree gets more and more nested, there could be
presentation events.
an issue when StackOverflowError is being thrown
because of that depth. This issue is tracked on Flutter To learn more, visit the package's repository.
repository and can be found here, along with
a workaround. This might occur when there are
a lot of global Providers nested in each other on
top of the widget tree. To avoid this, you can switch
to another dependency injection tool. When it comes
to global dependencies, we don’t need them bound
to a specific element’s lifecycle. We have encountered
this problem only once in a project with >1M lines
of code.
Flutter For Enterprise
Let’s see how a CommentSectionCubit would look And finally our presentation events:
like:
Making requests
At LeanCode, we design backends to be tailored for
the clients (“backend-for-frontend”). This means,
instead of having a generic REST endpoint /
posts/:postId/comments, which would probably return
extra data which the client wouldn’t use, or too little
data forcing the client to make additional requests
to other endpoints, we design a dedicated endpoint
for this mobile screen.
This approach has the benefit of feature-level
endpoint optimization and, once again, isolation
between features. One less visible but still important
benefit is that it removes the need for the repository
The Cubit starts in an empty initial state indicating
level. Instead, the API is already tailored to our needs,
that no work has yet been done. The initialize method
so we can directly make requests in a cubit through
fetches comments and indicates a hard failure in case
some API client.
of errors. In the failure state, no methods should work
since they will all most likely need the ready state to
access fetched comments. Recovering from hard For instance, the initialize method in the cubit would
failures is possible by calling initialize again or,
do the following:
better yet, through a dedicated refresh method
(which would be called by a pull-to-refresh).
In the upvoteComment method, we can emit
a presentation event in case of an error, since failing
to upvote is not a particularly interesting thing to
persist.
Where client is a generic HTTP client which can handle
For state, we use package:freezed which enables
request blueprints, and GetCommentSection is such
an easy way to define union types with value-equality. a blueprint encoding all information needed to reach
This distinction between state types (initial, inProgress, the appropriate endpoint. At LeanCode, these
ready, etc) makes it clear which methods should
blueprints are automatically generated using
and which shouldn’t be allowed during some state. our contracts generator, which gives us type-safe
backend-mobile communication.
CommentSectionState would look like the following:
Sometimes when we want to do additional processing
on the fetched data, a need for a repository arises.
A notable example is caching/offline mode.
The feature-based architecture allows us to make such
decisions on a feature-level. In such a case, we can
introduce a repository that encloses all additional data
logic and inject it into the cubit instead of the ApiClient.
Flutter For Enterprise
Reflecting state with UI Conclusions
Once we have a source of truth to draw the UI, a Cubit, In this part, we detailed how we build a single feature
we render widgets depending on Cubit’s state.
with its state management and proper separation
Due to the state being expressed as a union, we protect between UI and business logic. We also showed how
ourselves from rendering wrong widgets. For example, we can set up dependency injection and how to bind
we cannot show the list of comments while not being
lifecycles of logic objects and widgets to implement
in the ready state since we simply won’t have access
business-driven requirements.
to the list of comments.
With the knowledge from the previous part, we can
For our comment section, it is the following: scale this across multiple packages, teams and
organizations, so that we can damage control the ever-
changing project specification.
One last thing to handle is presentation events.
We need to subscribe to the presentation stream
and react to them appropriately. The act of
“subscribing” already implies that we need a stateful
approach. Classically this would be done with
a StatefulWidget, but these widgets tend to be full of
boilerplate noise and be less declarative than
one could want. As an alternative, we prefer to use
package:flutter_hooks. This package removes
the boilerplate of a StatefulWidget and transfers it
to a conceptual overhead.
Using a hook, we can setup a listener for presentation
events:
Flutter For Enterprise
The Role of UI Testing
Author: Julia Borkowska
Senior QA Tester & Head of QA
and How Patrol Can Help at LeanCode
You Improve Them
When it comes to testing Flutter applications, there
It’s not easy to be done though - UI tests are written
are many approaches and tools that we can use to as code, and this code should work in a predictable
implement tests on various levels. way. This means that we need some tools to properly
distinguish elements on the screen and interact
with them only when a real user would.
Starting from the bottom of the testing pyramid, we can
make use of unit tests - closely related to the app’s For example, such a test should only interact
code. Another tool are widget tests, which we can bring with elements that are visible on the screen and also
into play as a kind of module testing, where bigger should know when to try interacting with them.
elements of UI are tested in isolation. Focusing more
On the level of code, we usually don’t have
on layout, than business logic, we can utilize golden file straightforward information about what we can interact
testing - a Flutter-specific approach to screenshot with and when we should wait for something to appear
testing. on screen. And there are ways to do that - though
we should keep in mind that these methods
are complicated and are based on many technical
In this article, we’d like to focus on a solution nuances, which vary between different technology
incorporating testing functionalities and interacting stacks.
with real layout - UI tests, which unfurl their true
potential in testing large apps that are often only
a part of much bigger systems. Regression testing - automated
The second highlight is that UI tests help in regression
UI tests - what do they really tests and they suit this task best. And there are
test? reasons for that.
First of all, regression suites are precisely defined -
When planning, designing, and implementing tests,
there is no need to alter the test cases spontaneously,
it is crucial to know what should and should not be
like in exploratory testing. It means that an automated
done on a specific test level. The main objective during
test can do the same task as a human tester would,
UI testing is to check whether the main purpose
adding even more accuracy and repeatability
of the app can be achieved by its users. Having this
in execution.
general goal in mind, we can try to separate out,
which particular goals our tests should target.
Second advantage is that automated UI tests are
These targets specific for UI tests are a high-level
significantly faster than humans, which means that
view of the system, big regression suites, and imitating
they cost much less than a big team of QAs.
real users' behaviors.
Finally - regression testing is the most tedious task
To be a robot, but act like
in testers work. Automation makes it not only better
in execution, but it lets the QA team utilize their time
a human in a better way.
UI tests are the only kind of tests, in which we try
to simulate real user actions - we build and launch
the whole system, almost no data is mocked and we
want the test to perform user-like interaction with UI.
Flutter For Enterprise
Have a vista of your app Often they don’t have many opportunities
to communicate with each other about how their
Since we discuss large apps here, a big-picture view
parts interact, which is usually causing most of
of what we created is very important. And no other the bugs detected by the QA team.
tests do it better than UI tests. Our scenarios are
on such a level of detail, that they can correspond
To fill this lack of synchronization between many
with features, and test suites can comply with
teams, we need a point in the process where we
the app's modules. Not only can developers utilize would check our app as a whole product. In this case,
reports of such tests to monitor the health of
UI tests suit the best. Not only do we have a high-level
the system they’re creating, but business owners
view of features, but also we focus on interactions
can benefit from them too. between many parts of the system. It makes UI tests
a perfect tool for verification while having many
Beware of overzealous independent teams working separately on the same
product.
Among aspects that are not covered by UI tests are
visuals, though they are called UI tests. We are Communication with business owners
interacting with the real UI of our app, but we won’t
check how it looks. Making sure the app is aligned
Looking at this process from another point of view,
with the designs is a task for the golden file tests.
business owners need regular updates on product’s
readiness and level of stability.
Our task is to make sure that this UI is functional.
Having automated high-level tests, which take into
Another thing to keep in mind while writing UI tests
account all parts of the system, gives an opportunity
is that they should cover only crucial user paths
to easily gather the results and present them
and features. Many of the edgecases can be covered in the context of development progress and system
by tests on lower levels, such as unit and widget tests. reliability. Though other test results such as integration
It lets us not duplicate what we already tested
tests can provide similar information, UI tests
and focus on high-level aspects and integrations
are already on the right significance level that
that can’t be covered anywhere else. is needed from a business point of view.
Why are UI tests so important
Everybody hates regression
in enterprise-level applications?
Last but not least, regression testing costs grow
rapidly with the app’s size. In addition to cost,
If we think about software development, we can point the time needed to perform every regression suite
out some steps that are part of the software can extend to days of work of quite a big QA team.
development life cycle in every project, both small Finally, we all know how tedious work it is to execute
startups and big-tech companies. Though UI tests
all of the scenarios, which makes it error-prone
are not a popular topic among all developer teams,
and less efficient. Automated UI tests, once written,
we see that this kind of test gains more attention
can be run in less time and without or with minimal
in bigger projects. And there are some reasons behind human supervision. The cost of maintaining
it, which we will discuss in this section. and executing those tests is far lower than the cost of
manual regression. Additionally, saved time of the QA
team can be spent e.g., on exploratory testing
More teams, different problems and other tasks which can’t be automated.
It’s clear to see that enterprise development teams
are much bigger. Those teams are divided into smaller
ones and usually every smaller team is responsible
for delivering one feature or use case of the app
as described in a Flutter at Scale part of this ebook.
Flutter For Enterprise
Solutions Now, Patrol users can write their UI tests in Dart, using
simple yet powerful API, which lets them construct
Appium and its imperfections human-readable selectors and easily simulate real
user’s actions, both Flutter-native and OS-native.
The most popular and universal tool for UI tests
We incorporate our own framework in many projects
of mobile apps is Appium. It is a black box solution
we develop for our clients, so we can perform better
that is designed for testing on many platforms.
tests and make our framework battle-proven i
Though when it comes to testing Flutter apps,
n real-life use cases.
this framework is not enough.
A well-known solution of that problem is using
Also, Patrol is open-source, so every developer can
a combination of Flutter Driver and Appium, which use it in their project, either commercial or not.
gives a possibility of testing both the Flutter side of
the app and the platform native one - either Android
Recently, we released a new major version of our
or iOS. It enables us to cover more complicated use framework - Patrol 2.0. We reworked internals of how
cases, which incorporate not only those steps that
the tests are executed and integrated with the native
are performed in our app but also those outside
side, which enables our users to run Patrol tests
of the Flutter code - e.g., email verification, logging in,
on device farms and improves their experience with CI
or registration by external services such as Google
pipelines. With new design, it is possible to utilize
or Facebook, reading SMS codes and many others. sharding on device farms, also time needed to run
many tests on the same app became shorter.
We believe that those improvements unlock lots of
At LeanCode we were challenged to write tests
potential for new features and following refinements.
with Appium and Flutter’s integration_test package in
large scale apps to test complex functionalities made
See Patrol in action
with Flutter, while also incorporating some native
features. We quickly found that this solution required
Here we’d like to present some examples,
an amount of work which was not acceptable
how the tests look like when written with Patrol.
for our needs.
When you expect to see a permission dialog
On the one hand, using the Flutter-native testing for sending notifications, you simply add this step:
package missed a possibility to fully interact
with the native side of our app, such as providing
permissions for notifications or use of localization
services. We wanted to use a framework
that is extensible, easy-to-use, and in which tests
can be written in the same language as our app
(in our case - in Dart). Knowing the limitations
of Appium-Flutter-Driver specific design, we were
aware that we won’t be able to solve all of our current
Entering a text into a text field is simple as well - though
and future problems with this solution.
the first part of this instruction, which is responsible
for searching for the desired element on screen,
Patrol - our new framework
can vary. Here we use searching by in-code type of
and the reasoning behind its creation widget, which is TextField in this case.
The experience with Appium-Flutter-Driver led us
to write our own, Flutter-focused and open-source
UI testing framework, named Patrol that was released
in September 2022.
Flutter For Enterprise
Summary
UI tests are a crucial part of test strategy
in enterprise-level projects. Proper tools and good
practices of UI tests can significantly improve
the quality of the developed product. From lowering
costs and time spent on regression testing, through
Scrolling and tapping on elements on screen is also
creating a point of synchronization for scattered teams
very easy. Here you can see a bit more complex
working on the same product to providing high-level
selector, still it is written in a descriptive way.
visibility on the completeness of the system - UI tests
This step consists of scrolling to an arrow icon,
prove their validity in many ways.
that is inside a list tile, which contains text “Part 1”,
then taps on the icon that was found.
At LeanCode, we created Patrol having in mind big
and complex, Flutter-focused apps developer teams,
who need a simple in use yet powerful and extensible
tool for creating the most challenging automated tests.
If you’d like to see Patrol in action or learn more about
how we made UI testing in Flutter a better experience,
visit our blog and Patrol’s website.
Please notice that we don’t have to write any code
that would ensure the visibility of found elements -
it is already taken into consideration by Patrol.
Also, if there are many elements matching this selector,
action is performed on the first one by default.
Of course, if you would like to search for the third one,
it is still possible - by adding the “at(index)” method
after the selector, as in the example below.
All of those types as ElevatedButton and TextField
are types of widgets that are either provided by Flutter
framework, or declared in app’s code. This access
makes our test greybox, which we consider as good
equilibrium for automated tests - the test doesn’t have
to know everything about the app, so it is easy
to maintain it, but also we can differentiate elements
on the screen in a convenient way.
Flutter For Enterprise
Flutter Add to App - Author: Marcin Chudy
Senior Flutter Developer
Overview and at LeanCode
Challenges Based on
Real-Life Case
Flutter has taken the mobile market by storm. We often
hear from our clients that they are considering building
What is Flutter Add to App?
an application in Flutter. In the case of the enterprise
application, you often see the arguments that Flutter Add to App is a Flutter feature developed by
can help you to either cut the costs or simply the Google team. You don’t always have to write
streamline the development and deliver a much bigger a Flutter application from scratch. Flutter can be
scope in a given timeframe. However, it does not integrated into your existing application piecemeal as
happen every day that you start building your mobile a library or module. If you have an existing native
application from scratch as a greenfield project. mobile application for Android and iOS, you can use
Flutter to render only some views in your application
For those cases, it is relatively simple. With very rare or reuse some business logic in Dart between
exceptions, you should use Flutter. Yet, what should the two platforms. Flutter Add to App can be added to
your approach be when you already have a native app iOS (Flutter can be incrementally added into your
working on a production, with its traffic, user base,
existing iOS applications seamlessly with Cocoapods
and a backlog of new features which are waiting
or with pre-generated embedded frameworks)
to be shipped? There is hope, and it is called Add to and Android app (Flutter can be embedded into your
App. This is a hybrid application where fundaments
existing Android app piecemeal, as a source code
are in the natively written mobile application,
Gradle subproject or as AARs).
and certain components are built using Flutter.
When to use Flutter Add to App?
In this article, we will provide:
Cross-platform development is a very promising
Description of Flutter Add to App featur concept for Product Owners. It saves their time
Pros and Cons of Flutter Add to Ap and money by maintaining one codebase between
Challenges when using Flutter Add to Ap iOS and Android apps and makes managing
Recommendations for using Flutter Add to App. the development team with a single Sprint goal easier.
Therefore cross-platform frameworks such as Flutter
It will be based on a real-life case from the banking or React Native became the first choice for
industry, where for one of our clients, we have built
the greenfield projects like startups and new
a corporate banking app. enterprise ventures.
However, things can be more complicated if there
is already an existing application (iOS or Android app)
and you consider changing the technology. In such
a case, Flutter can be integrated, and this option
is called Add to App. It enables the mobile development
team to add the Flutter features to the existing native
app. Yes, you heard it correctly, you can integrate
Flutter and take the benefits of cross-platform
development even if you have previously developed
a native code.
Flutter For Enterprise
Yet, there are strict cases for when this Previously the only chance to do so was to consider
adding the PWA component and displaying
scenario is worth considering.
the integrated webview. However, you can stick to
the components created in Dart with Flutter. This will
1 Rewriting the existing mobile application
allow you to deliver a better experience to the user
on Android and iOS apps and save time on gathering
This is the most radical approach. It means that you
the two native teams for the same task on both
have decided to build the new app with Flutter, which
platforms.
will replace the existing native solution, but you don’t
want to suspend releases of the new tasks.
5 Impro ving the UI of your current application
In that case, a Flutter Add to App will help you add
new features to the existing Android or iOS app while One of the strongest selling points of building
replacing the existing ones with the new Dart code.
applications in Flutter is the ease of implementing
custom, complex user interfaces. And with Flutter
Add the Flutter module scenario works best when
Add to App, it’s no different. This is where Flutter
one team is working on new features and adding them can spread its wings and significantly speed up
to the app, and the other mobile team is working
development. Once you add Flutter to the existing
in the background on rewriting the native part.
application and starts drawing things on the screen,
Such an approach is recommended for existing apps the UI development is the same flawless experience
with a solid user base in a highly competitive market, as with pure Flutter apps. And the app's UI in Flutter
where it is vital to introduce new features. looks really well.
Getting the arguments for using
The con of mixing two technologies in a single app
2
Flutter technology is that if you want the design consistent across all
(which is most often the case), many
screens
Suppose you are a Flutter advocate looking for a way
components of the app's UI in Flutter need to be
to attract business stakeholders to the idea
rewritten from scratch. It might be technically possible
of rebuilding your current mobile app in Flutter.
to reuse some native components, but it kind of breaks
In that case, it is a good idea to showcase some small,
the purpose of using Flutter in the first place.
additional features developed as Flutter add to app.
And for later maintenance, keeping all the UI
This will help you demonstrate the most significant
components in sync can be a significant managerial
advantage of this technology: ease and speed
challenge.
of development. As a result, it increases the chances
of getting approval for continuing to implement Flutter
throughout the app.
Using the Flutter Add to App
feature in our case
3 Building proof of concept type of apps
At LeanCode, we had an opportunity to develop
an add-to-app Flutter proof-of-concept for a big
The main point of building proof of concept is to test
client from the banking sector. Our goal was to prove
whether or not a particular concept is possible
and beneficial from a technical point of view. This is
that Flutter was the right technology for a new mobile
similar to the point mentioned above. However,
application that was to be written from scratch .
it means that you don’t need to release that feature
to your end users but only showcase it to some internal
As part of the PoC, we verified some native features
stakeholders. This is enough to prove whether your
like camera, biometry, and animations. The main part
mobile app's performance will be satisfying or not.
was to rewrite one complex business process
in an existing native app. The process involved
4 Implementing a small, isolated feature integrating with existing native screens and A PIs,
feature flags mechanisms, and doing some
Let’s assume that your current native mobile app
background processing. Considering the sector,
fulfilling strict security requirements was also highly
is working and you don’t have the native team
assembled to perform new tasks. Yet, you want to add
important.
some fairly isolated features.
Flutter For Enterprise
Challenges of introducing First, we discovered that app bars in the native iOS
app used a custom animation for screen transitions.
Flutter Add to App feature It quickly turned out that it didn’t play well with Flutter.
While it seemed technically possible to handle such
First of all, the official documentation leaves something transitions gracefully, it would involve much work
to be desired. Flutter Add to App from the start is not to make the animations go well together. Due to all
the default, recommended way of creating apps with those issues, it turned out that it might be faster
Flutter. The general process is described in the docs, to rewrite more screens in Flutter (especially as the UI
and it seems straightforward. When it comes to was not that complex) than to look for a workaround
integrating with a big, long-lived native app, where to keep those screens native. And in fact, we did just
many things often had been implemented in a custom, that - a few screens that weren’t initially meant to be
non-standard way, the integration can cause many rewritten in Flutter were migrated to Flutter. That way,
hard-to-solve problems.
we could keep the whole process working in Flutter
and avoid extra-native communication. The entire
It's nearly impossible to predict all edge cases,
business logic stayed multiplatform in Dart.
which can cause disruptions in the entire application
development flow. Having multiple engines (instances) We also discovered that the native iOS app had some
of Flutter in more advanced integration scenarios generic view controllers that didn’t play well
increases the complexity. The risks are reduced when with Flutter screens. We had to develop a custom
we use the more straightforward, isolated approach
wrapper for that need which was not a lot of extra work,
of adding Flutter or integrating with smaller native but it required some native knowledge, and it could be
apps. Still, some help and engagement of native tricky for many Flutter devs.
developers must be considered while planning
the work.
The business process we were to develop was
intrinsically asynchronous - it involved checking some
You also need to keep in mind that Add to App
data in the background and displaying dialogs when
as a long-term solution can decrease the efficiency
the flow changed. It meant that we had to develop
of your app. In most cases, you would need to maintain a way to show Flutter dialogs at any place in the app,
two UI component sets and a bridging layer with both over native and other Flutter screens. It involved
developer experience decreased due to context maintaining a couple of Flutter engines and managing
switching and longer build times.
their lifetime, a challenge that is strictly connected
with the Add to App integration.
When you have decided on Add to App,
we recommend isolating the Flutter module as much If your app necessitates transitions between Flutter
as possible in place of a complex integration process and native screens, every such path has to be taken
because it needs a lot of extra work and brings issues care of. For every such transition, a bridge has to
that don't exist in pure Flutter apps. Some complex be created (in Dart, Kotlin, and Swift). You can make
technical topics can be researched on the side
the bridges pretty generic, but that still adds some
without touching the native app to avoid time spent
development overhead that wouldn’t be the case
on integrating with native parts. Remember that
for a pure Flutter app.
the development effort can vary concerning code
quality and the technical debt of native applications.
Navigation in native and Flutter
projects
The navigation is where the native and Flutter
definitely need to cross. Flutter Add to App,
by definition, means native and Flutter screens work
seamlessly together. In our case, though, things
appeared more complex.
Flutter For Enterprise
Networking between the native With a pure Flutter app, due to the myriad of libraries
available and the Flutter approach to rendering,
and Flutter app one rarely needs to develop and compile native
Android and iOS code (which can take quite a lot of
Almost every app communicates with backend time).
services of some sort. Unless the Flutter module
is heavily isolated (e.g., depends on some third-party Hot reload and hot refresh is very fast,
APIs that are not used by the native app), another and the developer can immediately see all changes
communication point arises between the native app in the code. With native recompilation, all screen state
and Flutter. is lost, and a developer needs to click through all
screens to retest a feature. Developers lose time
recompiling native parts and often need to debug
across many IDEs, which hurts productivity.
When rewriting an existing native module in Flutter,
two approaches can be taken:
Another interesting thing is that due to the lifecycle
of Flutter engines in Add to App, the screen state gets
The first is implementing networking (headers,
cached. So if you’re, for example, going from a native
serialization, etc.) in Flutter with the thinnest
screen to a Flutter screen, do something on it, then go
possible bridge to handle authentication.
back to the native screen, and then again to the Flutter
Usually, the authentication flow will have already
screen, it will look exactly the same once you have left
been implemented natively before adding a new
it. This is often an undesired behavior. The difference
framework to the app. Some edge cases
in the screen lifecycle can be pretty confusing
with refreshing tokens and keeping requests
to developers. They would expect the screen code
in the queue to preserve order can be tricky
to run from square one, but it doesn’t. To properly
to handle.
handle that case without destroying the engine, special
The other is to reuse the existing native networking, native bridges are necessary.
keep all endpoint definitions over there and expose
a full-on client to Flutter. The latter can make sense
if the protocol is complex and heavily customized, Native libraries in Flutter Add to
so reimplementing it in Dart for a PoC is
App
unnecessarily expensive.
While the UI is pure reusable Flutter, almost every app
needs some sort of native integrations (camera, push
Issues with debugging in case notifications, storage, geolocation, etc.). Those need
Flutter libraries containing native code. Adding such
of multiple engines plugins is a very straightforward process in a pure
Flutter app (usually, adding it is limited to changes
One of the reasons Flutter became so popular
only in the Dart code).
and is considered a mobile framework of choice
for many developers worldwide is the developer With Add to App, some native libraries may require
experience it provides.
extra configuration. It is often undocumented as those
libraries are not designed and tested with Add to App
Once you’re only using one Flutter engine in your app in mind.
(i.e., the Flutter part is heavily isolated) debugging
experience is the same as with pure Flutter apps.
The native app we worked on did not use Cocoapods
But for multiple engines, we have found issues
on iOS (a package manager for which Flutter Add to
with debugging multiple Flutter contexts.
App has built-in integrations). Because of that,
The debugger wouldn’t attach to some engines, and we were forced to embed all native frameworks
the hot reload would not always work. in the native app manually. That meant building
frameworks, opening XCode, and manually adding
them for each native library, an action that is easy to
forget and tedious for developers.
Flutter For Enterprise
On Android, on the other hand, we were forced to use
Flutter fragments instead of activities because
App size when using Flutter
of the existing native app architecture (activities
Add to App
and fragments are two different types of UI
components on Android). As Flutter has its rendering engine and runtime,
it will impact the app size. The increase is not
While it is not a problem in itself (Flutter supports dramatic, but if the app size is very important for
fragments), we later realized that some native libraries your app, it needs to be kept in mind. Remember
we had installed did not work as expected.
to measure size only on release builds. Debug build
It turned out that some extra configuration
sizes are definitely not representative, as they contain
was needed, and the documentation was written
a lot of tooling that only developers need.
only with activities in mind.
The Flutter module we developed consisted of a few
Background services
Flutter screens, some assets, and a limited number
and Flutter Add to App of pub libraries. The size increase for our app was 27
MB for iOS and 48 MB for Android. However, the
Implementing some background services in the mobile Android app was still being deployed to Play Store
app was necessary as part of the process. We needed in a deprecated APK format. Uploading in that format
to check for status changes periodically, and the logic is no longer possible for new apps. It is now required to
had to continue executing even when the entire use the superior AAB (Android App Bundle) format.
application was closed. We could simply execute
the existing native background services, but our goal The problem with APKs is that they need to contain
was different - the business logic had to be a platform- binaries for multiple architectures. In the case of AABs,
independent module in Dart. We wanted to prove
the app that the end-user downloads from the Play
a hypothesis that we can achieve high code reusability Store is optimized for their device. With AABs,
even in more complex cases.
the effective increase would be 3-4 times smaller.
Changing the deployment format and, thus,
Obviously, for all of that to work, we still had to optimizing the size of the app with Flutter can be
communicate with native code. The background an extra organizational effort.
process had to be a separate Dart isolate (a concept
similar to a thread), which meant it needed to be run in If your app contains assets that are also to be used by
a separate Flutter engine. The engines can Flutter, they can be shared between native code on
communicate between themselves (in pure Dart),
iOS, but unfortunately, at the time of writing, this feature
but they consume more resources. The Flutter team is not yet available on Android. It means those assets
introduced a new concept called engine groups which will need to be included twice in the final app bundle,
brings huge optimizations, but it was still unstable
and they will increase the overall app size.
at the time we were using it. The infrastructural logic
and turning on and off different engines must remain
native.
Flutter engines and
We implemented a bridge for talking with iOS
performance overhead
and Android using Pigeon - a handy but not that well-
When you’re adding Flutter runtime over a running
known tool yet for generating typesafe contracts
native app, it is impossible not to add some extra
for Dart, Android, and iOS bridges. It needs to be said
performance overhead to the existing iOS and Android
that code for managing engine lifecycles can be tricky,
app. Flutter needs its own execution environment
especially for developers not experienced with native
and even multiple environments in more complex
development. For example, a lack of a more profound
scenarios.
understanding of the reference counting mechanism
in Swift can cause unexpected crashes.
If you want to find out how to implement background
services in the Flutter Add to App, go to this article.
Flutter For Enterprise
The most expensive operation is starting
(pre-warming) the Flutter engine. All assets
There are two basic approaches. Firstly, you can
and the Flutter library must be loaded, a Dart virtual install the Flutter pipeline on build machines - it’s the
machine has to be started, and the entry point code most straightforward way. Adding a few extra steps
has to be executed. Depending on the use case,
(Flutter build, tests, etc.) will complete the Flutter Add to
this can be done at app startup, after the app displays App integration.
some initial data to the user, or only when the user
opens the Flutter screen. It all depends on when
If that’s a problem in your organization, e.g., due to
you’re willing to sacrifice the extra time that the user security concerns (as it happened in our case), there
has to wait. While this can mostly happen
is a way around in which you can build the Flutter part
in the background, Flutter still needs to block
to native AARs (for Android) and xcframeworks
the main thread for up to a few hundred milliseconds (for iOS). Then, theoretically, it’s possible to commit
during initialization. those built artifacts to the repository and keep
the existing CI process untouched.
With the engine being prewarmed, rendering the first
Flutter UI frame also has some latency. In our testing,
it was around 100ms slower than rendering the native It will, of course, lack testing and static analysis
screen. This was completely acceptable for our case of the Dart code, but it will suffice for publishing the app
and hard to be noticed by the user.
with Flutter for some quick testing. There are still
Times of subsequent renders were comparable
problems with Android, which requires network access
to native views.
to some Flutter Maven repositories. We quickly found
out that those were blocked in the corporate network.
When you open a Flutter screen and then go back
to a native screen, by default, the Flutter engine keeps
running. The UI state is cached so that subsequent Add Flutter to an existing app
screen openings are faster. This keeps eating
the CPU, and you need to keep an eye on RAM.
vs. organizational problems
When your Flutter process is not isolated, there’s
Introducing Flutter in a large organization
a high chance you will need multiple Flutter engines. and introducing it as part of an existing native
Every Flutter engine needs resources to run,
application brings a lot of challenges
so the performance overhead increases. However,
on the management level. Large corporations often
the Flutter team has recently introduced a new concept use proxies, artifact management systems for libraries,
called FlutterEngineGroup. It makes it possible to share and custom certificates. The network traffic can be
resources between multiple engines so that
heavily restricted.
the overhead of running another engine is minimal.
This means significant improvements for more Flutter development may therefore require close
complex Add to App scenarios. cooperation with other teams to resolve those issues
so that developers can set up their development
environment on their machines. This process can take
Continuous integration
some time, and one has to remember that it can
in Flutter and native apps completely block development and waste resources.
For native apps, you most likely have some Continuous When the Add to App idea is to rewrite an existing part
Integration system set up so that new iOS or Android of the iOS and Android app in Flutter, you have to
app versions are automatically published for testers. consider whether the whole business process
The build process can also be integrated with Google has up-to-date documentation. For long-running apps,
Play Store and App Store so that deployments take the documentation is often outdated, and in the end,
minimum effort. While Flutter builds to native platform developers will need to analyze the native code to
code (APKs and IPAs), some changes to the CI process understand how the app works. This is much slower
are required. than working with well-described requirements,
especially if the code quality is poor.
Flutter For Enterprise
Introducing Flutter can also bring some tension
with the native developers currently working
Conclusions on Flutter Add to
on the application. They will need to install and set up App
Flutter locally to continue working locally. Suppose that
brings problems with the environment, workstations,
or security. In that case, Flutter devs can deliver pre- T he success of using this approach depends hugely
built native artifacts (AARs and xcframeworks)
on the choice of the feature you want to implement
so that native devs don’t need any additional tooling
with Flutter. As you saw from the list of things you need
for the Flutter project. The approach is suboptimal
to take into account, whether they will hurt is subject
due to the extra manual work required from the Flutter to the proper isolation of that feature from the native
team. environment. ake this choice wisely, and you will be
M
able to avoid most of the hurdles we have described.
How to add Flutter to an
existing app - approaches Remember as well that Flutter Add to App is not
the default approach for Flutter app development.
So be clear about your vision and what you want to
Understandably, if you would like to rewrite your
achieve with this roof-of- oncept Flutter-based
P C
entire application in a new trendy cross-platform
feature.
framework such as Flutter right away, it could be
perceived as too huge of a revolution. With Flutter,
Whatever the choice you make regarding the feature
though, you can start step by step.
set, it is important that you will be assisted by a team
of skilled and experienced Flutter developers.
Flutter can be added to existing native apps, even only
as a single screen or even as part of the screen.
Since Add to App is purely documented as speci c fi
Adding Flutter in such a way can be a small proof of
skills depend on the existing native project,
concept to manifest the new technology in your
all architectural choices, as well as the execution,
organization. You can also implement the same feature
are demanding, and high expertise is required.
natively and in Flutter to compare performance
Explore this ebook further on how we can assist you
and user engagement with A/B tests.
by providing the Staff Augmentation . services.
2 0
We can take several approaches when considering
how to include Flutter in a native app:
Self-contained Flutter module - when we have a
business module that is ideally kept in one place in
the app (e.g., one tab of the application). We can
implement it in Flutter
and restrict native integrations to a minimum
Hybrid navigation - when the business feature we
want to implement in Flutter crosses paths with
some existing native screens (e.g., we can go from a
Flutter screen to a native screen
and then to another Flutter screen). This approach
brings some additional technical challenges
Mixing native and Flutter views - it is technically
possible to render some parts
of native screens with Flutter (and vice versa). This
is the most complex and often suboptimal way of
using the Flutter Add to App.
Flutter For Enterprise
Building Large Scale Author: Łukasz Kosman
CEO & Co-Founder at
Apps with LeanCode - LeanCode
Staff Augmentation 2.0
When you are a CTO or a Tech Lead for a new venture Let’s say that you hire 4 Flutter Developers
in an enterprise, you are faced with a lot of from LeanCode. What you get in return is
challenges. On the one hand, you have an option to the guarantee that whatever problem or challenge
finally take responsibility for the system architecture you want to address, there will be 60 top IT Specialists
and its delivery. This is for sure satisfying. On the willing to help.
other hand, there are plenty of decisions to be made
which can impact future success. N eed a further example?
Are you building for the big scale from day one? Will the While developing the product, our team stumbled upon
business deliver enough traction to justify a complex a specific requirement, for example, payment card
architecture? How to balance the quick time-to-market provisioning from Mastercard and Visa. In that case,
of new features and the requirements for the complex a domain expert from our Flutter Team is called to
architecture? Those are all tough decisions,
provide guidance for the feature architecture
and sometimes you need a good sparring partner
and to share best practices. This collaboration goes
who is experienced in similar projects. beyond the specific field of expertise. We often have
cases in which the Flutter teams are using the support
At LeanCode, as a tech partner for our clients,
of our Backend Architects to facilitate the modeling
we are not another team augmentation provider,
of contracts with the in-house server-side team.
but we actively help you to make good strategic Whatever the challenge, the chances are that in one
decisions and help you reach your business objectives. of our 60 projects, we have already encountered
We also help you to scale quickly, but adding
a similar case.
our qualified, English-speaking team which makes
a real difference. Last but not least, we can support you D evelopers from the best Universities only
in your own recruitment and training efforts so that,
(no career changers)
in the end, you build your own team while delivering
a great product. Our entire team graduated from computer science
studies at the best technical universities in Poland,
Interested in learning more? Let’s go! where the vast majority comes from Warsaw
University of Technology. Moreover, we are also
leading two courses at that University about
Why Staff Augmentation 2.0? Developing Mobile Apps in Flutter and System
Architecture in .NET.
While engaging with different clients, we often saw
that typical outsourcing companies only recruit
Knowledge transfer supervised by our
and sell profiles. At LeanCode, we promote
the knowledge transfer and personal growth of all Team Leaders
developers and experts. This is why we can provide
We hire professionals who can indeed lead
this knowledge and expertise to our clients.
the development teams. They are the experts in their
respective fields. They can supervise the team
How does it work in practice?
of in-house developers to facilitate the knowledge
transfer to their development team and ensure
that everybody is on the same page.
Flutter For Enterprise
Professionals with excellent Training for the local IT workforce
communication skills
Is your mobile development team not experienced
It’s not only about the language we use, but all
in Flutter? They can form a hybrid team with
our developers can speak fluent English. Moreover,
our Engineering Managers, who will teach them
our staff augmentation team is a perfect fit for any the best practices. We practice teaching skills at
company which embraces open communication
Warsaw University of Technology, where our experts
and transparent culture. lead two courses.
Conference speakers and community Help in your future recruitment processes
members
What will happen once the project is over?
Within our team, you can easily spot popular We have no vendor locks. Even our own libraries
conference speakers who are widely respected by
are open-sourced, and you can easily maintain them.
the international tech audience. They are true experts We can offer the SLA offer for the 2nd level of support
and can inspire an in-house team to cooperate better of the projects, but we also have experience creating
together. job postings and recruiting new software developers
for the in-house teams to help you with the in-house
team expansion.
Engineering managers experienced
in enterprise projects
Building a startup in-house and creating a truly
scalable product ready for demanding security
and performance tests often conducted by third parties
are two different animals. Our developers are
experienced in large-scale software development
projects with different-sized augmented teams starting
from 6 to 200 team members and can bring those
best practices with them.
Bulletproof expertise
As all experts, we are opinionated. It makes life
so much easier to follow specific architecture choices,
especially when they have been through rigorous
penetration, security, and performance tests
and are based on eight years of experience.
Moreover, our standards are updated to catch up
with the technological edge and are regularly reviewed
to meet the highest standards. As a staff augmentation
partner, we bring this experience to keep your mind
at rest.
Sparring partners
In a leadership position on a tech side of a project, it
gets lonely quickly, and the responsibility for the
decisions is vast. CTOs from our clients like to
exchange ideas with our Team Leaders and
Engineering Managers and work together hand-at-
hand to make the right architectural decisions.
Flutter For Enterprise
Not just another staff augmentation provider
At LeanCode, we love complex cases. In our core technologies, we have been working on almost all possible
technical challenges, and we love discovering something new. As your outsourcing vendor, we will happily tackle
the most troubling problems, build proof-of-concept projects and transfer the knowledge back to your team.
The reviews we have, speak for themselves
“LeanCode has delivered the MVP within 2.5 “Solid knowledge of LeanCode and trusting
months, exceeding our expectations. Agile cooperation help us deliver new features
and detail-oriented, they've taken the time
to our customers quickly, continuously,
to understand the banking industry to deliver and based on high-security standards.
the most effective solution for our users.
The sophisticated code base and experience
They are professional, efficient,
within LeanCode provided us with a strong
and responsive.”
foundation for the IT security certification
process.”
Tomasz Czerwiński Mario Martella
Deputy CIO at Credit Agricole Bank Polska
Poland is the best outsourcing destination you can imagine
During his recent visit to Poland in May 2023,
For us living and breathing in Poland, those statements
Sam Altman, the CEO of OpenAI, mentioned that 10 out are hardly a surprise. Rooting from the communist
of 50 initial OpenAI Developers in the Core Team were times, we have an excellent foundation for STEM
Polish. He wondered, "I don't know what Poland
courses in our universities which quickly transformed
does to create such an amazing engineering talent".
from the coal and heavy industry into computer
This same observation was shared recently by Visa, science laboratories. With a very good command
which decided to build a tech hub in Poland for
of English and the mindset of hardworking specialists,
a 1.5K IT workforce. Polish IT Developers significantly impact the tech world
in general.
Flutter For Enterprise
Additional opportunities from .NET
our staff augmentation service This is the core backend technology at LeanCode.
We have our own framework, which is built on top of
Short mobilization time .NET and streamlines the work. It is battle-tested
in 50+ projects.
With a team of 60+ IT experts, we can quickly scale up
the dedicated team according to the project's needs. React.js
Typically our projects can start within up to four weeks.
For one of our clients, we have assembled a team of 20 We remain attached to the proven web development
experienced Flutter developers within three months. technologies for regular, customer-facing web portals.
This would not be possible using the traditional Check our case studies to learn more about the React
recruitment process. portals we have built.
Flexibility and part-time team members Node.js
You can choose what availability is the best match
Great fit for teams who want to embrace the full-stack
for your project. Imagine, at a particular stage,
development experience.
you need only 1/2 FTE of Quality assurance specialists
for automated tests. Our flexible team augmentation
services can help you to get a custom offer.
DevOps
Our backend developers are typically taking care
Special consulting sessions on demand of the infrastructure in their projects. Our primary field
of expertise is Microsoft Azure, but we have developers
One of the benefits of working with an augmented seasoned in other cloud solutions like Google Cloud
team from LeanCode is that you can tap into
or AWS.
the knowledge and experience of all our teams. While
we were working on an enterprise Flutter mobile app, UX Design
one of our clients needed to consult the complex
backend architecture. Although we were not Our dedicated team of UX and UI Designers can build
responsible for that part, one of our Team Leaders from an excellent Design System and bring your vision
the backend guild took an active role in shaping
to the tangible, high-fidelity prototype in Figma.
this architecture and worked as a sparing partner
for the Technical Team Leader on the Client's side.
In terms of technology, we are
very focused
We are a boutique software development studio
that is highly specialized. We do not provide experts
in all possible technologies, but when we focus on a
specific technology, you can be sure we have
mastered that.
Flutter
We co-founded Flutter Europe and Flutter Warsaw,
one of the biggest communities of Flutter Developers
in Europe; we are contributors to Flutter and build
our own open-source libraries. Our team of 30 Flutter
Developers is one of the most experienced teams
in mobile development using Flutter worldwide.
Flutter For Enterprise
What is a typical staff The second model is called horizontal.
augmentation model? In this model, the augmented team cares about
a particular app layer. For example, at LeanCode,
There are several scenarios in which you you can hire a team of .NET Developers who can be
responsible for the backed side of the project and feed
can adopt a staff augmentation model.
the contracts to the frontend developers working
in an existing in-house team.
The first one is called vertical.
It means that the additional team is supplementing
Horizontal Dedicated Team
the efforts of the current in-house team by adding
another development squad. This staff augmentation
model has roots in a feature-based team organization
where the in-house team is leading squads responsible Product Owner
for the different features, and the dedicated team takes
care of just a particular set of business requirements.
For example, let's say you want to add in-app credit Frontend/Mobile Devs Frontend/Mobile Devs Frontend/Mobile Devs
card provisioning to the existing mobile app.
In that case, a separate vertical team can take care of Backend Developers Backend Developers Backend Developers
the backend integration and mobile development of
this feature, isolated from the other squads. In this
model, the team has a separate project manager and The last model we call a hybrid software development
product owner supervising this augmented team.
model.
In this model, the local team capabilities are multiplied
by adding new team members with comparable In this hybrid mode, developers of all skills are mixed
skillset. to form a coherent team. In this case, we provide
experienced Flutter or .NET Developers who work
hand-at-hand with the local team. This model is based
Vertical Dedicated Team on a tight collaboration where external and internal
developers run each other code review sessions
Dedicated team Existing teams
to enhance the knowledge transfer. The main
advantage of this cooperation model is that the local
Feature Squad #1 Feature Squad #2 Feature Squad #3
team acquires the know-how.
Hybrid Dedicated Team
Product Owner Product Owner Product Owner
Frontend/Mobile Devs Frontend/Mobile Devs Frontend/Mobile Devs
Product Owner
Backend Developers Backend Developers Backend Developers
Frontend/Mobile Devs Frontend/Mobile Devs Frontend/Mobile Devs
Backend Developers Backend Developers Backend Developers
Choosing a suitable staff augmentation model hugely
depends on your internal resources and what you can
provide internally.
Flutter For Enterprise
How much can staff How to start with our staff
augmentation services cost? augmentation?
Well, as always, it depends. This process is not linear,
In short, this is very affordable in the short run.
but the stages below can give you an idea of what you
It helps you to scale your product development efforts
can expect from us.
in no time. It gives the confidence that architectural
decisions are made right and that you can rely on your
freshly hired and already integrated IT team from day 1 Schedule a call. We need to understand
one. Neither the in-house recruitment process nor your your situation better. At this stage, anything
own training will bring you comparable flexibility
can happen. If you have already built some
and high performance.
MVP, we are happy to run a technical audit
on that. If you have stumbled on some hard
In the mid-to-long term, the profitability
things in the system architecture, we are
and affordability depend on several items.
pleased to refactor them. If you have
If you choose your outsourcing partner wisely, their identified specific risks, we can build
costs could be very advantageous. Eastern Europe has a PoC to mitigate certain risks if you have
an excellent IT talent pool that you can tap into for identified them. As said before, it depends.
valuable resources. At LeanCode, we bring world-class
2 Review the team. Based on your
Experts for highly competitive prices.
expectations, we will preliminary select
candidates who fit your needs well. You can
While securing the budget for staff augmentation,
review their profiles, and we will arrange
you need to consider the size of the requested team,
a call to check if it clicks between you
the skillset required, and the length of the contract.
and the team.
You can negotiate better prices if your vision is long-
term, as it brings stability to the project and the team.
3 Sign the Agreement and the first Order.
Our Master Service Agreement handles
the transition of IP and several other
essential elements. However, it doesn't
oblige you to pay anything. The actual works
start after the Order is signed for a particular
team for a specific period.
4 Build a Partnership with us. We like to meet
regularly, at least once a week, to monitor
the project's progress on regular statutes.
Once a week, we also ask you in a survey
about your satisfaction and collect your
feedback. This helps us hugely to be a better
partner and to provide the best staff
augmentation services.
Schedule a call
Łukasz Kosman
CEO & Co-founder LeanCode
Flutter For Enterprise
About LeanCode
LeanCode is a Software House from Warsaw, Poland, and a leading provider of
the native mobile applications built with the Flutter Framework.
We have a team of 60+ developers, The majority of our clients represent
designers, product owners, scrum the Banking and Fintech industry, but
masters, and QA engineers who we also develop products for
support the development of mobile and Marketplaces, Logistics companies,
web applications using Flutter, .NET, SportTech, MedTech startups, and
React, and other technologies. others.
We work with clients from all over the
world, including the USA, UK, Germany,
and Australia.
Our services include:
Mobile App Mobile Apps
Web
Staff
Product
IT Consulting
Development Audit development Augmentation 2.0 Design
You can find out more about our core If you have any project in mind, we are
technologies, services, and delivered always open to discuss your needs and
applications on our website:
possibilities. The best is to reach us via:
leancode.co
Get an estimate form
See what’s new at LeanCode on: