At Jimdo, we developed a new product called Dolphin. The most important goal at Jimdo is to help people be successful in their personal or business brands. To help accomplish this, we decided to add another app to our application family to help people optimize their Dolphin websites. Years of experience in the website industry has taught us that measuring achievements and feeling successful is the most important part of having a brand. Therefore, we wanted to start displaying statistics on the website to help our users on their way to success. We published Jimdo Boost mobile apps for both Android and iOS. Inside the apps, we take a look at our users' website statistics and calculate an interest rate as well as displaying how many visitors visited the website and where they come from. There will be more advanced features in the future. But for now, we would like to talk more about the technical side.
After building apps natively for Android and iOS for a while, we wanted to create the same app for both platforms at the same time. So, we thought about trying out cross-platform development and joining forces with all four developers. We decided to use Flutter, a framework developed by Google.
To one of the most existential questions in our life—why? It’s always about finding the right solution that fits your project and team. Flutter seemed promising for us for several reasons:
- We have a culture of experimenting at Jimdo, iterate on something fast, release it, evaluate it, analyze the mistakes and plan next
- things considering the experience of the past and always keep changing. For our new product — Boost, Flutter was a good choice, because Boost is a new app that we built from scratch and planned to reach the MVP stage with quite fast (within about seven weeks).
- We’re a small, cross-functional mobile team with two developers per platform and having four developers working on the same code-base sped up the process a lot and strengthened up our teamwork.
- We were all doing native development for some time, so Flutter was a perfect opportunity to learn a new language and framework and do things differently.
- Our Android developers see great potential in Flutter and think it might be the first cross-platform solution that will work, who knows :) So motivation was high as well.
- We kept an eye on other cross-platform projects (ReactNative and Xamarin), but for reasons like strongly typed and C-style programming language Dart, compiling to Native code ahead of time, support from Google, own Widget layout system etc, we decided for Flutter.
It has downsides for sure: It’s still in development and the beta version was just released recently. When we started using Flutter, it was still in the alpha stage. There aren’t that many libraries compared to native Android and iOS. We also had a lot of issues with Firebase plugins, especially on iOS. Therefore, we had to keep an eye on the issues in Flutter repository, as well as the new commits. Because of the constant change in framework, sometimes we changed Flutter channels (master, beta, etc.) and even stuck to the one specific commit to having a stable development environment.
In our team, iOS and Android developers had different challenges. Android developers were more familiar with the tools we use. They were using Android Studio from JetBrains/Google and iOS developers were using Xcode from Apple. While developing Flutter, we used IntelliJ IDEA also from JetBrains. iOS developers needed more time to get used to the IDE and learn shortcuts. In the meantime, Android developers felt at home. This was one more thing to learn for iOS developers.
Flutter has a CLI (Command Line Interface) tool. This tool provides smooth installation, build and running processes even if you’re not using an IDE like IntelliJ with Flutter plugin. Its help page is useful and provides an overview of what you can do.
We all have this one old device sitting on our desk, which we’re careful to protect from getting updated to the last supported OS version. We use it to test a new app at least once, to make sure this cool, new app runs fine on even that old OS version that everyone would like to forget.
One big, selling advantage of Flutter compared to other cross-platform technologies and even to the native development frameworks for Android and iOS is its independence from platform APIs.
The term, from back then when the Dart language launched, is valid for Flutter as well: "Batteries included". A released Flutter app includes all the needed framework parts and UI implementations that the app is using. This has the advantage, that the app has no API dependency to the platform and developers don't need to find workarounds for different API levels (which is often the case in Android development). When Flutter gains new APIs in the future, developers won't have to care if their user base gets device updates, the only thing needing an update then is the app, not the user's OS.
So update your old phone and store it away in a drawer, where you can forget about it. A Flutter app works and looks the same on any supported OS, be it iOS 8 or 11, Android 4.1 or P.
This alone is a big step forward for mobile development productivity, especially compared to native development tools.
Inside Flutter Framework
In Flutter, everything you see on the screen (and even more, like GestureDetector) is a widget. There are different types of widgets, like widgets for text, graphic, buttons or widgets to layout other widgets. The widgets are documented on flutter.io. The material widgets apply the material design guidelines provided by Google. So if you want to build an app in a material design style you’re good to go and the framework provides you with a lot of widgets. There are also some platform specific widgets for iOS (Dialog, activity indicator, etc.), but to build an iOS app which feels native this might not be enough.
This is a small example to show different kinds of widgets:
return new Column( children: [ new Center( child: new Text( 'Hi!!', style: const TextStyle( color: Colors.teal, ), ), ), new Container( child: new FlatButton( child: new Text('Press me'), onPressed: _doSomething, ), alignment: AlignmentDirectional.centerStart, padding: const EdgeInsetsDirectional.only(start: 16.0), ) ], );
The column has multiple child widgets and shows these in a column. The column has two child widgets: One Text widget wrapped in a Center widget. The Text widget is given the color teal. The second widget is a FlatButton wrapped in a Container, given some alignment and padding.
This simple widget tree shows how to build UIs in Flutter with Widgets containing another widget (Container) or multiple widgets (Column). Additionally, there is a Text widget with styling
options and an interaction widget (Button) which is able to emit actions. In this example,
_doSomething is a function which has to be implemented.
Testing in Flutter is done pretty well, there are 3 different kinds of tests:
- Unit tests
- Widget tests
- Integration tests
We only use unit tests for now, since they run fast and test the behavior of our presenters. In our presenter tests, the needed dependencies and the views are mocked via mockito. Unit tests run pretty fast since there is no rendering or I/O involved.
A simple unit test looks like this. We check the result of a method for equality with the string ‘Foo’:
Additionally, we do tests for interactions. In this example, we verify that the method
showProgress() of the view is called exactly once.
// Send button in view was pressed, feedback is sent to server await presenter.onSendPressed(Rating.AWESOME); // Verify behavior of presenter, it should call view.showProgress() verify(view.showProgress()).called(1);
In our Boost app, we’re following the Model-View-Presenter approach. We have tests for our presenters and also tests for a lot of util-like methods. At the moment we don’t have widget or integration tests. Mostly because they are more tedious to write and run way slower. Plus, our app is still simple and we discover UI bugs quickly by manual QA testing.
Configurations and Distribution (via Travis and Fastlane)
Preparing an app for production requires more effort into configurations and smooth release processes. We have three different environments like most of the production level applications, “Development”, “QA/Beta-Testing” and “Production”. (You can find more information about different environments for web apps in here. But the idea of environments is the same.) Separating environments is handled by "flavors" in Flutter, the same as in Android. Using flavors was really easy and worked pretty well while building and distributing the Android app.
But it's not as smooth for the iOS side. We had a lot of different problems while configuring our iOS app to make it work. We used Debug configuration for development and Release configuration
combined with Beta flavor for QA and Production distributions. After integrating this strategy, we’re not able to run and debug the iOS app. We explained our issue in detail in here. But thanks to Fastlane, the distribution process was easier (and works!). We have different Fastfiles for each platform. Android configuration has two lanes, one for QA-test
and one for release. Our Fastfile for Android can be seen here. On the iOS side, we have a bit more work because of the certificates. Again, we have two lanes in iOS for QA
release and store release. Fastfile for iOS can be seen here. We have some scripts which run the necessary
flutter build command (with
--no-codesign on iOS) and trigger these lanes in Fastfiles. We use Travis CI for distributing the app. Travis basically installs necessary tools for Flutter for both platforms and
runs our bash scripts in two jobs (Android runs on Linux, iOS runs on macOS) to start distribution process.
In summary, our experience with preparing our app into production and QA tests was really smooth with Android. On the iOS side, we tried with the automatically manage signing feature of Xcode
first. And after spending hours solving the problems that arose, it still didn't work. So, we had to use manually signing and use
--no-codesign flag while building the app. Thanks to
Fastlane, it was smoother than we expected.
What about non-technical things?
Moving back and forth between native and cross-platform development
We’ve been developing apps natively for years now. At the moment we started cross-platform development, our first issue was kicking out our native development expertise. When you want to expertise the knowledge on something, it requires time for both individuals and the company. It’s the same as making a change which has more risks (along with more rewards). But we like taking risks and consider it a challenge, although it was very demanding for us.
Cross-platform development never means writing code only once
Using Flutter also didn’t stop our native development completely. We started development when Flutter was in the alpha stage with problematic plugins. Therefore, we created our own platform channels when needed. We sometimes had to dive deeply into the codebase of used plugins to find the problems. This might be painful when you want to work smoothly. But we started with an awareness of those possible problems. So far, we reached the point where we’re working four developers in one codebase instead of two developers per platform. Working with double-sized developers team doesn’t mean 2x speed on development.
The concept and design process itself was not influenced as much by the decision for Flutter as the development has been. Since we developed cross-platform for the first time we decided to apply a product-focused design principle and designed the app according to Dolphin styling guidelines instead of focusing on platform-specific design rules. This way, using Flutter reduced the amount of the general visual and experience implementation state. As it requires that both platform apps use the same implementation source, although some details needed a closer look still.
Community & Hiring
Even if most companies are conservative about a framework which is in early stages, it still attracts a lot of attention. Therefore, the community is getting bigger and bigger every day. Unlike other frameworks, thanks to the technological and structural choices made by the Flutter team, it passes the expectations from a cross-platform framework. Since we expect Flutter to be on the rise, hiring good developers won’t be a problem in the near future. P.S. We’re hiring!
We thought for a while about what our conclusion of the Flutter experience is and decided that it’s best to ask two questions to every single developer in the team;
What do you think about Flutter? How did you like using it?
Artem (iOS Developer): “For me it was a great experience to develop something in Flutter. The SDK should be easy to use for those, who have experience with native development and reactive, flux-like architectures. I’d totally recommend using it for hobby projects or prototyping something fast, because due to hot-reload UI development was always fast. Stability of the project on a big scale is the biggest concern for me, rising number of issues on GitHub, missing features, amount of tooling around and problems with custom build configurations on iOS would make it no go for production for me. But I will definitely keep an eye on it in the future.”
Candost (iOS Developer): “Even it was a good experience to build reactive UI with hot-reload, it was a painful process for making the project build, run and distribute. Flutter is still too young to start using and deliver products with. Development speed and smoothness is not same for Android and iOS. The iOS side has more missing points than Android. When it’s mature and stable enough (with having a fast adoption cycle to the new platform technologies), it can be a way to go for me.”
Jörn (Android Developer): “I really enjoyed using Flutter, mostly because of the rich set of material widgets, good async support and especially: Hot-reload 🔥”
Matthias (Android Developer): “In my eyes, Flutter has three major features that together make it favorable over any other mobile app framework: platform-api independence, a shared codebase across platforms and a modern, reactive and easy to customize UI framework.
There are other great treats (like hot-reload, the Dart language or easy interop with the platforms), but I think the mentioned three are what make it disruptive not just for other cross-platform frameworks, but even for the two big native UI development frameworks.
These aren’t going to disappear anytime soon, of course, and besides others, there’s still a big hole that can be a deal breaker on which Flutter has to catch up on. But going forward it will be my first candidate for any new project from now on. Once all the quirks of still being a very early framework are outgrown, the productivity gain of going Flutter will be staggering!
Heck, coming back to native Android dev from Flutter already feels like working with your hands bound behind your back!”