Flutter 101: How to create beautiful Android and iOS apps with Android Studio.

Wouldn’t it be awesome if you could create an amazingly beautiful app that looks exactly like it should both on iOS and Android? I mean, not exactly the same but really close. What if I tell you that you can do that with the same code base?

Yes, it is possible 🙂

Say hello to Flutter. One code base. Beautiful apps.

Flutter provides out of the box support of beautiful widgets, powerful programming language, smooth performance, official IDE support (you can even use Android Studio) and many others.


So let’s start playing with the framework. Install Flutter by following steps here based on your operating system. Tip: I recommend that you first install Android Studio if you don’t have it already, it will save you a lot of work of setting up Flutter, simulator, Android SDK and IDE support. * From this line forward, I assume that we will use Android Studio, but this also works with other IDE. Next, open your Android Studio, open Preferences and select Plugins. Click on Browse repositories… and search for Dart, install it but don’t restart Android Studio yet, then search again for Flutter, install it and restart Android Studio.

Now, with Flutter installed , when you open Android Studio, it should show Create New Flutter Project in File menu.

If you’re on a Mac, plug your iPhone or iPad and surprisingly, Android Studio can detect and show your iDevice and also iOS simulators, this is very nice.

Let’s create new Flutter application, and Android Studio will generate an example of a Flutter app with Material theme for you. Now select your device / simulator and click Run button. After Flutter build finished, this beautiful app will appear on your screen.

Main Dart file for Flutter app is usually located at lib/main.dart. When you explore it, you will realise how easy it is to create a Material page with Toolbar, Main Content and Floating Action Button. No XML needed, no CSS styling needed and no confusing configuration.

One of the main benefit of using Flutter is Hot Reload, it’s similar with Android Instant Run but more powerful so you can change your source code and after pressing save, the page will be reloaded. Let’s try by changing the Theme primary colour from Blue to Lime. Go to line 20 in main.dart and change this line Colors.Blue into Colors.Lime save by pressing CMD+S / CTRL+S then open your simulator / device. Voila, instant change. Tips: One of the benefit of using Android Studio is you can instantly see the color of Colors value on the left side.

Color indicator on the left side.
Instant changes with saved state (The button was pushed 5 times).

If you’re an Android developer you must be familiar with gradle as a package manager for Android project or Podfile / Cartfile if you’re an iOS developer. Flutter is the same, it has `pubspec.yaml` as a dependency manager. I wrote dependency manager not a package manager, because in pubspec can also contain assets for your file, whether it’s fonts, sounds and images. Flutter configuration also written inside pubspec.

Login Page

Next, why don’t we start with creating a common pattern of app. Login screen and Homepage with navigation drawer & content. Let’s start by creating login screen. We need username, password field and submit button.

Open main.dart and remove everything. Add import material lib then add the entry point for Flutter application.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

Next, we create our MyApp class. Write this lines :

class MyApp extends StatelessWidget {
  // This widget is the root of your application.

Go inside class expression and press CTRL + I or via menu Code -> Implement Methods and choose build method. Android Studio will add the boilerplate code for you. So why extending StatelessWidget? It’s because there are no state changes inside MyApp widget. There will be more explanations and differences between StatelessWidget and StatefulWidget below.


Implement build method

Inside build method, add this line of codes.

return new MaterialApp(
   title: 'Flutter Demo',
   theme: new ThemeData(
       primarySwatch: Colors.lime,
   routes: {
    // "/": (context) => LoginRoute(),
    // "/home": (context) => HomeRoute(),
   initialRoute: "/",

If you notice from first boilerplate code when we create new Flutter project. There are differences in our code above. In the boilerplate, we add initial page (or route in Flutter) by using home . Here we add routes and the initial page is declared using initialRoute. Using routes will have an advantage on navigation.

Now we create the LoginRoute. Right click on lib, click on New -> Dart File and name it login_route.dart. Note: Dart convention is to name every file with lower_snake_case.dart. First we import Flutter material lib. Next, add these lines:

class LoginRoute extends StatefulWidget {
    State createState() => _LoginRouteState();

Note: There are two kind of Widget in Flutter. Stateless and Stateful. If your widget will be capturing user input like text field, dropdown, button you want your widget to be Stateful. When your widget will be doing network call, you also want your widget to be Stateful. The key point is when there are State that will be changed based on some event (user input, async network call, system event) extend your class from StatefulWidget. Parent class can be Stateless (if there are no state changes) even when the child widget is Stateful.

Note: In Dart, for a multi line expression body, you can use

(parameter) {

For one line expression body, you can shorten it into

(paremeter) => callMethod01(parameter);

Next, add these lines :

class _LoginRouteState extends State<LoginRoute> {
    Widget build(BuildContext context) {
        final username = Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
                decoration: InputDecoration(
                    labelText: "Username",
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(4.0),

        final password = Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
                obscureText: true,
                decoration: InputDecoration(
                    labelText: "Password",
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(4.0),

        final submitButton = Padding(
            padding: const EdgeInsets.all(16.0),
            child: Material(
                elevation: 5.0,
                shadowColor: Colors.lime.shade100,
                child: MaterialButton(
                    minWidth: 200.0,
                    height: 48.0,
                    child: Text(
                        "LOG IN",
                        style: TextStyle(color: Colors.white, fontSize: 16.0),
                    color: Colors.lime,
                    onPressed: () {
                        // Navigator.push(context, MaterialPageRoute(builder: (context) => HomeRoute()));

        return Scaffold(
            backgroundColor: Colors.white,
            body: Center(
                child: ListView(
                    children: [username, password, submitButton],

If we’re using StatefulWidget, we must define State class, which has build method inside. Inside this build method define how we will render the components and this will be re-rendered when a state change by calling .setState(). You don’t need to worry about how often the rendering happens and will it has any impact on performance, the Flutter rendered is smart enough to render only the changes.

First, we create the username TextField and add a decoration to style the look of the TextField. Next we create the password TextField with obscureText property set to true. In Flutter , to add space (padding) and to position an element we wrap that element to be a child of Padding or Center (to Center an element), Column (like LinearLayout with Vertical orientation / UIStackView with vertical orientation) or Row (like LinearLayout with Horizontal orientation / UIStackView with horizontal orientation).

Tip: when you’re using Android Studio. After you add the element and want to wrap it inside Padding / Center / Column / Row, write your element first, then press Alt + Enter to show action. Then you can choose what dimension / container for wrapper.

Quick actions


Column quick action result.

Next, we add Submit button. We use MaterialButton to add a button with material look and then we set the width, height, text (by setting child property and fill it with Text), color, and so on. To add more Material feels, we wrap that button inside Material widget to set it’s elevation and shadow color.

Lastly, we stitch all the widgets above by using Scaffold. Scaffold is like a skeleton for app with Material theme. It can consist of AppBar, Body, Floating Action Button, Drawer, Right Drawer and Bottom navigation bar. For our login, we only need the body part, add List View (or Scroll View, but I prefer List View because we can position all items directly without a need to wrap it inside a Column) then fill the children of ListView with our components above.

Back to main.dart. Uncomment these line :

"/": (context) => LoginRoute(),

Then, below of our material, we import our login_route file.

import 'package:flutter_app_as/login_route.dart';

Because Flutter plugin for Android Studio haven’t yet implemented auto import, we need to write the import manually. But don’t worry, the autocomplete is still available. Now Save the project or Run it if you haven’t already run it in simulator / device.  You should see a login screen with beautiful UI and nice animation when you clicked on username / password field.


Login page

Home Page

Next, we will create a home page and then connect the login page to home page when we click on LOG IN button. First, create new home_route.dart and import material.dart library. Then add this line :

class HomeRoute extends StatelessWidget {

We extend this class from StatelessWidget because this class doesn’t have any state change and without user input. Note: you may need to extend it from StatefulWidget later when you want the drawer to be fully functional (e.g clicking on menu will change the content of the body). Next, implement the method from StatelessWidget by pressing CTRL+I.

class HomeRoute extends StatelessWidget {
    Widget build(BuildContext context) {

Add these lines inside build method :

return Scaffold(
    appBar: AppBar(
        title: Text("Welcome"),
    drawer: Drawer(
        child: Column(
            children: [
                    accountName: Text("John Doe"),
                    accountEmail: Text("johndoe@testfairy.com"),
    body: Center(
        child: Text("Hello"),

We return a Scaffold with Appbar, Drawer and Body. For Appbar we set the title to be a Text with label “Welcome” Inside the Drawer we add a column with UserAccountsDrawerHeader on top and menus  on the bottom of it. This menus will be navigation menus that user can tap on it. Now let’s create this menus. Before return Scaffold line, add these lines :

final menus = Column(
    children: <Widget>[
            title: Text("Logout"),
            onTap: () {
                Navigator.popUntil(context, ModalRoute.withName("/"));

First, we add Column because we want the elements to be placed in vertical order (top-down). Column or Row need an array of widgets for its children. Next we add ListTile as the children. You may be wondering, Why ListTile, why not just Text ?. This is because by using ListTile, we can easily add onTap event on its child, which in this case is Text.

Inside onTap() we add Navigator.popUntil that will pop the navigator stack until it finds a route with name “/”. If you remember, this route name have already defined inside main.dart. Back to main.dart and uncomment this line :

"/home": (context) => HomeRoute(),

Then we import home_route.dart

import 'package:flutter_app_as/home_route.dart';

Next, open login_route.dart and uncomment this line :

Navigator.push(context, MaterialPageRoute(builder: (context) => HomeRoute()));

Because we also refer to HomeRoute() then we need to also import home_route.dart inside login_route. Now save the the file to hot reload the app. Now when you log in app will navigate to Home route and when you tap on Logout menu app will go back to login screen.

Floating Action Button

Now let’s see if hot reload works inside a nested route. Navigate your app to Home page (by clicking login button). Open home_route.dart and after body (inside Scaffold), add a Floating action button.

floatingActionButton: FloatingActionButton(
   child: new Icon(Icons.ac_unit),
   onPressed: (){},

Press save, and our home route will be hot reloaded and we should see the fab.

Home page with fab.

Image Asset

Another nice thing about Flutter that you saw on code above is it’s shipped with material Icons and Colors, this will really help on creating a fast prototype. Next, we will add an image on top of Username that will be our company logo. First, download this image : https://www.iconfinder.com/icons/2191544/download/png/128 then move this image to /assets folder.

Then we add this image inside pubspec, so this image will be shipped inside our Flutter app.

Define our image asset.

Next, open login_route.dart and add this image asset with this line :

final icon = Image.asset(

Inside the ListView children, add icon before username, so it will be like this :

    children: [icon, username, password, submitButton],

Save and you should now see our image asset on top of username field.


From this tutorial, we can see how easy it is to create a beautiful app like this, with only 3 files. Even though Flutter hasn’t yet release 1.0 yet (when this blog was written the latest version is Release Preview 1) but so far it’s stable and some apps from big developer team has already used it in production like Hamilton : The Musical App (iOS & Android) and Hookle (iOS & Android).

You can clone full source code here : https://github.com/hidrodixtion/Flutter-App

See you in the next tutorial.

IBM case study

“For us, the combination of “shake to report”, the integration with JIRA and the video recordings really was a WOW factor”– Marcelo Juliano Ramos, IBM Watson automation specialist.



Marcelo is an a Watson automation specialist based in the IBM office in Brazil.

IBM Watson is an artificial intelligence technology, based on cognitive computing that understands natural language and is capable of answering questions posed in multiple languages.

As part of his role in IBM, Marcelo works with Volkswagen on an in-house innovative new project involving Watson technology for the new Volkswagen Virtus.
This new car model will offer a cognitive manual, which utilizes IBM Watson to respond to drivers’ questions about the vehicle, including information from the car manual. The app understands natural, informal speaking and “learns” more based on the interaction with the driver. The driver can either speak, write or take photos to interact with the app. Any “how to” questions about the car can be answered by this smart assistant, simply and quickly, in multiple languages. This solution allows a new way of interacting with the vehicle and offers a new technological experience to driving.


TestFairy is a mobile testing platform that enables teams to find, report and solve complex problems with their mobile apps, all from one dashboard. The platform includes an enterprise  app-distribution solution for iOS and Android apps, and provides an SDK that collects valuable information such as videos showing user behavior, app logs and crash reports. This information is collected and presented in an organized way, in a single, easy-to-use dashboard helping teams to quickly reproduce and understand problems, allowing for accurate and timely bug fixes.


“Users simply did not report on all problems because the manual process they had for reporting feedback was tedious and unfriendly”

As the Volkswagen Virtus project has many features and complex capabilities, and testing is done both in the lab and in the field for cars, the IBM and Volkswagen teams needed an organized way to manage testing and ensure the app is as quick and easy to use as the vehicle it accompanies. The app was to undergo testing by personnel from both companies, and so the teams needed to make sure they can both access and share the information collected throughout the process.

Reflecting on past experience working on an HR-focused app within IBM, Marcelo said their biggest problem was receiving enough useful feedback and managing this amount of data in a manner that allows the team to track problems, reproduce and fix them on time.
Users simply did not report on all problems because the manual process they had for reporting feedback was tedious and unfriendly, requiring a user testing the app to decide to take screenshots, attach them to an email and explain the situation.
As colleagues admitted to the team, it often put them off of reporting some issues, as the process required to much effort on their part. In addition, the received reports were not always useful, as sometimes the recordings may be partial or the information provided inaccurate or insufficient. This lead the team, on many occasions, to rework and waste time on recreating issues that didn’t have enough information provided on them.

Finally, a lot of data collected from the testing was eventually lost because there was no organized method of storing and using it in retrospect.As two teams are testing the app in its development process, they needed a way to efficiently manage testing throughout the development cycle and in transition from the IT development at IBM to a more user experience oriented approach at Volkswagen.


“I no longer have to wait for users to tell me what they’ve experienced. Now I can see it for myself”

After the trying experience of unorganized testing, Marcelo’s team started using TestFairy, and that made all the difference. The fact that all sessions are automatically recorded, and contain all the relevant data the development team may need to solved their biggest problem.

What actually made the testers hop on board was how incredibly easy the reporting process has become. All they had to do is shake their phone, and a bug report with a full recording and attached screenshots, logs and metrics was automatically created and sent.
For Marcelo’s team, the fact that those reports automatically open up issues on their JIRA ensured that no more bugs are missed, and all the relevant data for fixing them is available right there on that ticket. For issues users do choose to report, having all the session recordings at hand enables the team to check if other users experienced them as well.

Now, with more reports flowing in with comprehensive and accurate information, the team is able to quickly reproduce and fix problems, as well as fully understand the actual user experience, all before releasing the app.


Testing and getting feedback during the development process of an app is no easy task in any company or setting. The task is even harder when it involves a complex app and two developing companies. By using TestFairy, the IBM team is able to streamline this process and effectively run testing on their app, get more reports thanks to the super easy feedback it enables, and have urgent issues promptly sent to their JIRA. No more bugs missed, no more waiting for users – the teams can now watch the entire experience’s recordings from one dashboard.

Mobile Beta Testing – TestFairy is Integrating With Atlassian Products

TestFairy announces compatibility with JIRA Software, HipChat, Bitbucket, Bamboo and Atlassian Bitbucket Pipelines
It is not every day that you get invited by a company like Atlassian to spend a whole week in Amsterdam. And to make it a perfect geek party, you will be hanging out with developers, marketers and product managers from all product teams who will help you integrate with their platform. So are you coming or not? D’aah? And we landed in Amsterdam 🙂
To make a long story short, after less than 24 hours with the teams of JIRA Software, HipChat, Bitbucket and Bamboo, and one night without sleep, everything worked!
First came JIRA Connect.
One of TestFairy’s most useful features allows mobile testers to easily send bug reports to their developers. Testers can do that by logging in to their mobile dashboard, by using the TestFairy testers app, or by simply shaking their phone and filling in a feedback form that pops up.
Many of our customers asked us if we can post this feedback directly to JIRA Software, together with the video that shows what exactly happened on the app before the bug was reported. And so we did.
As a result, if someone says “It is not working”, and you’ll be surprised how many testers say that, you won’t have to ask them “What did you do?” “Did you click here or there?” “Did you have wifi or 3G?” “Can you send me some logs?”… Instead, just open the JIRA issue, rewind the video 10 seconds back and see exactly what happened before the user complained, and what exactly made them unhappy.
This is how a JIRA Software issue looks like with an embedded TestFairy video:

We did the same with crash reporting, where we pushed our video and logs together with every stacktrace, helping developers understand what exactly happened before their apps crashed.
It gets more interesting when the same crash occurs multiple times and the last thing you want is to have ten new issues on a crash that happened ten times (not mentioning thousands). We solved this problem by hashing every stack trace, later using it for grouping, so that one JIRA Software issue will be opened, regardless how many times your app crashed.

Issues created on JIRA Software create these magic properties. These properties include the name of the tester, the device he or she was using, operating system version, etc. You use these to later query JIRA by using JQL or simply by clicking on a property within an issue. JIRA Software will then automatically show you only the relevant issues, as in the screenshot above, only issues generated on “Apple iPhone 5”.
TestFairy for JIRA add-on is now available on Atlassian Marketplace.
Then came HipChat.

HipChat brings testers and developers together. Collaboration is instantaneous, and with TestFairy you can get testers’ feedback directly and immediately to your HipChat rooms. As a developer, you may also want to be notified when a new version has been uploaded, or a crash occurred.
Integration could not have been simpler — thanks to HipChat Connect.
All you need to do is click on
and choose the right HipChat room for your notifications. You may also configure which types of notifications you are interested in receiving.
We have found that it is a good practice to post tester’s feedbacks to one room, and crashes to another. Mainly for the reason that in most teams, different people handle different cases.
Now, when a user sends a feedback, we can easily see their notification in real time.
TestFairy for HipChat is available through the Atlassian Marketplace or directly from your account settings page on TestFairy.
Then came Bamboo.
Bamboo is where everything starts. Our plugin for Bamboo uploads your app to TestFairy whenever it is ready and automatically sends it a pre-defined group of testers. You can invite by email, or via a notification in a HipChat room.

This brings us to a platform that has TestFairy connected to JIRA, HipChat and Bamboo.
Then came Bitbucket.
Bitbucket Cloud holds the source code for your app. We closed the loop between source code and crash, by creating a link between the two. When showing a crash, TestFairy will automagically link the stack trace to your Bitbucket repository, at the right branch and revision.
And finally, Bitbucket Pipelines.
Bitbucket Pipelines is the continuous integration tool that glues the entire system together. Bitbucket Pipelines automatically compiles your mobile app with every commit. TestFairy integrates seamlessly to Bitbucket Pipelines to upload app artifacts, the .APK / .IPA files to TestFairy for distribution.
In the following example you can see an app that crashes.
The notification regarding this crash goes to HipChat in real time, together with a link to the actal TestFairy session.
Clicking on the crash will take you back to Bitbucket to the exact line that caused this crash.
We’ll be happy to get your feedback in the comments below.

Hello, Bitrise!

bitrise deployment
Guest post by: Barnabas Birmacher
Distributing your apps through TestFairy is great as always! Reaching your testers instantly and gathering valuable feedback and usage information is the best way to make awesome applications.
To make sure you keep deploying quality products you should use Continuous Integration and Delivery.
Continuous Integration is a practice where you push all your app’s code changes into a shared repository multiple times a day. Each integration should be verified by an automatic build, so you are going to be notified early about the problems and you can locate them quickly. Continuous Delivery works well with Continuous Integration – it means that you deploy every build to production every time a build passes the automated tests.


Working with unit tests and multiple daily deployments can give you a huge benefit but dealing with it manually also takes up a lot of time. Soon you will realise you are doing a lot more administrative tasks besides the actual development.
We, at Bitrise, are on a mission to bring back the joy of app development by automating all of these processes for you. We’ve also integrated TestFairy into Bitrise so every time you update your code we’ll deploy your app automatically to your favorite deployment service.

How to get started?

After you have connected your application to Bitrise you can change your own workflow on your application page and start customising it for your own needs.
To be able to deploy your application you are going to need an Xcode Archive step (that should be already added to your workflow).
To deploy your app to TestFairy, simply add a new TestFairy step after Xcode Archive and after every successful build we are going to deploy your application automatically.

(click image for full size)

The only required parameter you have to add is your TestFairy API Key. To get it you should navigate to your account preferences on TestFairy and find the key under the API Key menu.
You can also enable or disable the email notifications and set the tester groups you would like to notify. There’s an option to make your users upgrade to the latest build by enabling Auto update in the step. You can also start recording video and set the length of it.

What’s next?

We have more than 20 different integrations you can choose from. Update your build workflow by adding connections with different services like Slack or HipChat to notify your team about broken builds or successful deployments.
And that is all! Just work on your awesome app and we’ll make sure it deploys to TestFairy every time you update your code!
Happy building!
The Bitrise Team