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.

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 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 {
    @override
    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) {
    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 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 {
    @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. 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>[
        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 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(
    'assets/lock.png',
);

Inside the ListView children, add icon before 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 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/testfairy-blog/Flutter-Sample-App

Do you have any questions or comments?

Happy to get your feedback at the comments below.

Leave a Reply

Your email address will not be published. Required fields are marked *