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

Shouldn’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.

Related Topics

Take a look at these posts to learn about common communication patterns between Dart and native code if you already know what you are looking for.

Setup

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 a new Flutter application, and Android Studio will generate an example of a Flutter app with a Material theme for you. Now select your device/simulator and click Run button. After Flutter builds 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 realize how easy it is to create a Material page with Toolbar, Main Content and Floating Action Button. No XML needed, no CSS styling required and no complex configuration.

One of the main benefits 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 color from Blue to Lime. Go to line 20 in main.dart and change this line Colors.Blue into Colors.Lime save the project by pressing CMD+S / CTRL+S then open your simulator/device. Voila, instant change. Tips: One of the benefits 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 a 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 a 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 is also written inside pubspec.

Login Page

Next, why don’t we start with creating a familiar pattern of an app? Login screen and Homepage with navigation drawer & content. Let’s start by creating a login screen. We need a username, a password field, and a 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 the 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 the 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 in navigation.

Now we create the LoginRoute. Right click on lib folder, 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 {
    @override
    State createState() => _LoginRouteState();
}

Note: There are two kinds 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 is doing network call, you also want your widget to be Stateful. The key point is when there is State that will be changed based on some event (user input, async network call, system event) extend your class from StatefulWidget. The 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) {
    callMethod01(parameter);
    callMethod02();
}

For one line expression body, you can shorten it into

(paremeter) => callMethod01(parameter);

Next, add these lines :

class _LoginRouteState extends State<LoginRoute> {
    @override
    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 decoration to style the look of the TextField. Next, we create the password TextField with the 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 the wrapper.

Quick actions

Column quick action result.

Next, we add the Submit button. We use MaterialButton to add a button with the 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 its elevation and shadow color.

Lastly, we stitch all the widgets above by using Scaffold. A scaffold is like a skeleton for an app with a Material theme. It can consist of an 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 ListView 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 the Flutter plugin for Android Studio hasn’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 the homepage 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 the menu will change the content of the body). Next, implement the method from StatelessWidget by pressing CTRL+I.

class HomeRoute extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    }
}

Add these lines inside build method :

return Scaffold(
    appBar: AppBar(
        title: Text("Welcome"),
    ),
    drawer: Drawer(
        child: Column(
            children: [
                UserAccountsDrawerHeader(
                    accountName: Text("John Doe"),
                    accountEmail: Text("johndoe@testfairy.com"),
                ),
                menus
            ],
        ),
    ),
    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. These menus will be navigation menus that user can tap on it. Now let’s create these menus. Before return Scaffold line, add these lines :

final menus = Column(
    children: <Widget>[
        ListTile(
            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 quickly 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 a name “/”. If you remember, this route name has 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 file to hot reload the app. Now when you login 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 Homepage (by clicking the login button). Open home_route.dart and after the 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.

Homepage with fab.

Image Asset

Another nice thing about Flutter that you saw on the code above is it’s shipped with material Icons and Colors, this will really help on creating a quick 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(
    'assets/lock.png',
);

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

ListView(
    children: [icon, username, password, submitButton],
),

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

Conclusion

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 released 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/testfairy-blog/Flutter-Sample-App

Do you have any questions or comments?

Happy to get your feedback at the comments below.