Write your first Flutter app, part 1

If you prefer an instructor-led version of this codelab, check out the following workshop:

The app that you'll be building

This is a guide to creating your first Flutter app. If you are familiar with object-oriented code and basic programming concepts such as variables, loops, and conditionals, you can complete this tutorial. You don’t need previous experience with Dart, mobile, desktop, or web programming.

This codelab is part 1 of a two-part codelab. You can find part 2 on Google Developers Codelabs (as well as a copy of this codelab, part 1).

What you’ll build in part 1

You’ll implement a simple app that generates proposed names for a startup company. The user can select and unselect names, saving the best ones. The code lazily generates 10 names at a time. As the user scrolls, more names are generated. There is no limit to how far a user can scroll.

The animated GIF shows how the app works at the completion of part 1.

Every Flutter app you create also compiles for the web. In your IDE under the
devices
pulldown, or at the command line using
flutter devices, you should now see
Chrome
and
Web peladen
listed. The
Chrome
device automatically starts Chrome. The
Web server
starts a peladen that hosts the app so that you can load it from any browser. Use the Chrome device during development so that you can use DevTools, and the web server when you want to test on other browsers. For more information, see Building a web application with Flutter and Write your first Flutter app on the web.

Also, Flutter apps can compile for desktop. You should see your operating system listed in your IDE under
devices, for example:
Windows (desktop), or at the command line using
flutter devices. For more information on building apps for desktop, see Write a Flutter desktop application.

Step 1: Create the starter Flutter app

Create a simple, templated Flutter app, using the instructions in Getting Started with your first Flutter app. Name the project
startup_namer
(instead of
flutter_app).

You’ll mostly edit
lib/main.dart, where the Dart code lives.

  1. Replace the contents of
    lib/main.dart.
    Delete all of the code from
    lib/main.dart. Replace with the following code, which displays “Hello World” in the center of the screen.

    // Copyright 2022 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.  import 'package:flutter/material.dart';  void main() {   runApp(const MyApp()); }  class MyApp extends StatelessWidget {   const MyApp({super.key});    @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'Welcome to Flutter',       home: Scaffold(         appBar: AppBar(           title: const Text('Welcome to Flutter'),         ),         body: const Center(           child: Text('Hello World'),         ),       ),     );   } }

  2. Run the app in the way your IDE describes. You should see either Android, iOS, Windows, Linux, macOS, or web output, depending on your device.

    Hello world app on Windows

    Windows

    Hello world app on iOS

    iOS

Observations

  • This example creates a Material app. Material is a visual design language that is standard on mobile and the web. Flutter offers a rich set of Material widgets. It’s a good idea to have a
    uses-material-design: true
    entry in the
    flutter
    section of your
    pubspec.yaml
    file. This will allow you to use more features of Material, such as their set of predefined Icons.
  • The app extends
    StatelessWidget, which makes the app itself a widget. In Flutter, almost everything is a widget, including alignment, padding, and layout.
  • The
    Scaffold
    widget, from the Material library, provides a default app warung kopi, and a body property that holds the widget tree for the home screen. The widget subtree can be quite complex.
  • A widget’s main job is to provide a
    build()
    method that describes how to display the widget in terms of other, lower level widgets.
  • The body for this example consists of a
    Center
    widget containing a
    Text
    child widget. The Center widget aligns its widget subtree to the center of the screen.

Step 2: Use an external package

In this step, you’ll tiba using an open-source package named english_words, which contains a few thousand of the most used English words plus some utility functions.

You can find the
english_words
package, as well as many other open source packages, on pub.dev.

  1. Add
    english_words
    package to your project as follows:

                  
                    $
                    
                    flutter pub add english_words
                    Resolving dependencies... + english_words 4.0.0   path 1.8.0 (1.8.1 available)   source_span 1.8.1 (1.8.2 available)   test_api 0.4.3 (0.4.9 available) Downloading english_words 4.0.0... Changed 1 dependency!
                    
                  
                

    The
    pubspec.yaml
    file manages the assets and dependencies for a Flutter app. In
    pubspec.yaml, you will see that the
    english_words
    dependency has been added:

    @@ -23,4 +23,5 @@

    23

    23


    dependencies:

    24

    24



    flutter:

    25

    25



    sdk: flutter

    26

    26



    cupertino_icons: ^1.0.2

    27

    +

    english_words: ^4.0.0

  2. While viewing the
    pubspec.yaml
    file in Android Studio’s editor view, click
    Pub get. This pulls the package into your project. You should see the following in the console:

                  
                    $
                    
                    flutter pub get
                    Running "flutter pub get" in startup_namer... Process finished with exit code 0
                    
                  
                

    Performing
    Pub get
    also auto-generates the
    pubspec.lock
    file with a list of all packages pulled into the project and their version numbers.

  3. In
    lib/main.dart, import the new package:

                  import 'package:english_words/english_words.dart';
                  import 'package:flutter/material.dart';

    As you type, Android Studio gives you suggestions for libraries to import. It then renders the import string in gray, letting you know that the imported library is unused (so far).

  4. Use the English words package to generate the text instead of using the string “Hello World”:

    @@ -2,6 +2,7 @@

    2

    2


    // Use of this source code is governed by a BSD-style license that can be

    3

    3


    // found in the LICENSE file.

    4

    +
    import ‘package:english_words/english_words.dart’;

    4

    5


    import ‘package:flutter/material.dart’;

    5

    6


    void main() {

    @@ -13,14 +14,15 @@

    13

    14



    @override

    14

    15



    Widget build(BuildContext context) {

    16

    +

    final wordPair = WordPair.random();

    15

    17



    return MaterialApp(

    16

    18



    title: ‘Welcome to Flutter’,

    17

    19



    home: Scaffold(

    18

    20



    appBar: AppBar(

    19

    21



    title: const Text(‘Welcome to Flutter’),

    20

    22



    ),

    21



    body:
    const
    Center(

    22



    child: Text(‘Hello World’),

    23

    +

    body: Center(

    24

    +

    child: Text(wordPair.asPascalCase),

    23

    25



    ),

    24

    26



    ),

    25

    27



    );

  5. If the app is running, hot reload to update the running app. Each time you click hot reload, or save the project, you should see a different word pair, chosen at random, in the running app. This is because the word pairing is generated inside the build method, which is run each time the
    MaterialApp
    requires rendering, or when toggling the Podium in Flutter Inspector.

    App at completion of second step on Windows

    Windows

    App at completion of second step on iOS

    iOS

Problems?

If your app is not running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following links to get back on track.

  • pubspec.yaml
  • lib/main.dart

Stateless
widgets are immutable, meaning that their properties can’t change—all values are final.

Stateful
widgets maintain state that might change during the lifetime of the widget. Implementing a stateful widget requires at least two classes: 1) a
StatefulWidget
class that creates an instance of 2) a
State
class. The
StatefulWidget
class is, itself, immutable and can be thrown away and regenerated, but the
State
class persists over the lifetime of the widget.

In this step, you’ll add a stateful widget,
RandomWords, which creates its
State
class,
_RandomWordsState. You’ll then use
RandomWords
as a child inside the existing
MyApp
stateless widget.

  1. Create the boilerplate code for a stateful widget.
    In
    lib/main.dart, position your cursor after all of the code, enter
    Return
    a couple times to start on a fresh line. In your IDE, start typing
    stful. The pengedit asks if you want to create a
    Stateful
    widget. Press
    Return
    to accept. The boilerplate code for two classes appears, and the cursor is positioned for you to enter the name of your stateful widget.

  2. Enter
    RandomWords
    as the name of your widget.
    The
    RandomWords
    widget does little else beside creating its
    State
    class.

    Once you’ve entered
    RandomWords
    as the name of the stateful widget, the IDE automatically updates the accompanying
    State
    class, naming it
    _RandomWordsState. By default, the name of the
    State
    class is prefixed with an underbar. Prefixing an identifier with an underscore enforces privacy in the Dart language and is a recommended best practice for
    State
    objects.

    The IDE also automatically updates the state class to extend
    State<RandomWords>, indicating that you’re using a generic
    State
    class specialized for use with
    RandomWords. Most of the app’s logic resides here—it maintains the state for the
    RandomWords
    widget. This class saves the list of generated word pairs, which grows infinitely as the user scrolls and, in part 2 of this lab, favorites word pairs as the user adds or removes them from the list by toggling the heart icon.

    Both classes now look as follows:

    class RandomWords extends StatefulWidget {     const RandomWords({super.key});      @override     State<RandomWords> createState() => _RandomWordsState();   }    class _RandomWordsState extends State<RandomWords> {     @override     Widget build(BuildContext context) {       return Container();     }   }

  3. Update the
    build()
    method in
    _RandomWordsState:

    class _RandomWordsState extends State<RandomWords> {
                  @override
                  Widget build(BuildContext context) {
                  
                    final wordPair = WordPair.random();
                  
                    return Text(wordPair.asPascalCase);
                  }
                  }

  4. Remove the word generation code from
    MyApp
    by making the changes shown in the following diff:

    @@ -14,16 +14,15 @@

    14

    14



    @override

    15

    15



    Widget build(BuildContext context) {

    16



    final wordPair = WordPair.random();

    17

    16



    return MaterialApp(

    18

    17



    title: ‘Welcome to Flutter’,

    19

    18



    home: Scaffold(

    20

    19



    appBar: AppBar(

    21

    20



    title: const Text(‘Welcome to Flutter’),

    22

    21



    ),

    23



    body: Center(

    24



    child:
    Text(wordPair.asPascalCase),

    22

    +

    body:
    const
    Center(

    23

    +

    child:
    RandomWords(),

    25

    24



    ),

    26

    25



    ),

    27

    26



    );

    28

    27



    }

  5. Restart the app. The app should behave as before, displaying a word pairing each time you hot reload or save the app.

  6. Problems?

    If your app is not running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following link to get back on track.

  • lib/main.dart

In this step, you’ll expand
_RandomWordsState
to generate and display a list of word pairings. As the user scrolls the list (displayed in a
ListView
widget) grows infinitely.
ListView’s
builder
factory constructor allows you to build a list view lazily, on demand.

  1. Add a
    _suggestions
    list to the
    _RandomWordsState
    class for saving suggested word pairings. Also, add a
    _biggerFont
    variable for making the font size larger.

    class _RandomWordsState extends State<RandomWords> {
                  final _suggestions = <WordPair>[];
                  final _biggerFont = const TextStyle(fontSize: 18);
                  // ··· }

  2. Next, you’ll add a
    ListView
    widget to the
    _RandomWordsState
    class with the
    ListView.builder
    constructor. This method creates the
    ListView
    that displays the suggested word pairing.

    The
    ListView
    class provides a builder property,
    itemBuilder, that’s a factory builder and callback function specified as an anonymous function. Two parameters are passed to the function—the
    BuildContext, and the row iterator,
    i. The iterator begins at 0 and increments each time the function is called. It increments twice for every suggested word pairing: once for the ListTile, and once for the Divider. This contoh allows the suggested list to continue growing as the user scrolls.

    Return a
    ListView
    widget from the
    build
    method of the
    _RandomWordsState
    class using the
    ListView.builder
    constructor:

    return ListView.builder(   padding: const EdgeInsets.all(16.0),   itemBuilder: /*1*/ (context, i) {     if (i.isOdd) return const Divider(); /*2*/      final index = i ~/ 2; /*3*/     if (index >= _suggestions.length) {       _suggestions.addAll(generateWordPairs().take(10)); /*4*/     }     return Text(_suggestions[index].asPascalCase);   }, );

    1. The
      itemBuilder
      callback is called once sendirisendiri suggested word pairing, and places each suggestion into a
      ListTile
      row. For even rows, the function adds a
      ListTile
      row for the word pairing. For odd rows, the function adds a
      Divider
      widget to visually separate the entries. Note that the divider might be difficult to see on smaller devices.
    2. Add a one-pixel-high divider widget before each row in the
      ListView.
    3. The expression
      i ~/ 2
      divides
      i
      by 2 and returns an integer result. For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2. This calculates the actual number of word pairings in the
      ListView, minus the divider widgets.
    4. If you’ve reached the end of the available word pairings, then generate 10 more and add them to the suggestions list.

    The
    ListView.builder
    constructor creates and displays a
    Text
    widget once tiap-tiap word pairing. In the next step, you’ll instead return each new pair as a
    ListTile, which allows you to make the rows more attractive in the next step.

  3. Replace the returned
    Text
    in the
    itemBuilder
    body of the
    ListView.builder
    in
    _RandomWordsState
    with a
    ListTile
    displaying the suggestion:

    return ListTile(   title: Text(     _suggestions[index].asPascalCase,     style: _biggerFont,   ), );

    A
    ListTile
    is a fixed height row that contains text as well as leading or trailing icons or other widgets.

  4. Once complete, the
    build()
    method in the
    _RandomWordsState
    class should match the following highlighted code:

    @override Widget build(BuildContext context) {
                  return ListView.builder(
                  
                    padding: const EdgeInsets.all(16.0),
                  
                    itemBuilder: /*1*/ (context, i) {
                  
                    if (i.isOdd) return const Divider(); /*2*/
                  
                    final index = i ~/ 2; /*3*/
                  
                    if (index >= _suggestions.length) {
                  
                    _suggestions.addAll(generateWordPairs().take(10)); /*4*/
                  
                    }
                  
                    return ListTile(
                  
                    title: Text(
                  
                    _suggestions[index].asPascalCase,
                  
                    style: _biggerFont,
                  
                    ),
                  
                    );
                  
                    },
                  );
                  }

  5. To put it all together, update the displayed title of the app by updating the
    build()
    method in the
    MyApp
    class and changing the title of the
    AppBar:

    @@ -14,12 +14,12 @@

    14

    14



    @override

    15

    15



    Widget build(BuildContext context) {

    16

    16



    return MaterialApp(

    17



    title: ‘Welcome
    to
    Flutter‘,

    17

    +

    title: ‘Startup
    Name
    Penyemangat‘,

    18

    18



    home: Scaffold(

    19

    19



    appBar: AppBar(

    20



    title: const Text(‘Welcome
    to
    Flutter‘),

    20

    +

    title: const Text(‘Startup
    Name
    Pengungkit‘),

    21

    21



    ),

    22

    22



    body: const Center(

    23

    23



    child: RandomWords(),

    24

    24



    ),

    @@ -27,18 +27,36 @@

    27

    27



    );

    28

    28



    }

    29

    29


    }

    30


    class
    RandomWords
    extends
    StatefulWidget
    {

    31



    const
    RandomWords({super.key});

    30

    +
    class
    _RandomWordsState
    extends
    State<RandomWords>
    {

    31

    +

    final
    _suggestions = <WordPair>[];

    32

    +

    final _biggerFont = const TextStyle(fontSize: 18);

    32

    33



    @override

    33



    State<RandomWords>
    createState()
    => _RandomWordsState();

    34

    +

    Widget
    build(BuildContext context)
    {

    35

    +

    return ListView.builder(

    36

    +

    padding: const EdgeInsets.all(16.0),

    37

    +

    itemBuilder: /*1*/ (context, i) {

    38

    +

    if (i.isOdd) return const Divider(); /*2*/

    39

    +


    40

    +

    final index = i ~/ 2; /*3*/

    41

    +

    if (index >= _suggestions.length) {

    42

    +

    _suggestions.addAll(generateWordPairs().take(10)); /*4*/

    43

    +

    }

    44

    +

    return ListTile(

    45

    +

    title: Text(

    46

    +

    _suggestions[index].asPascalCase,

    47

    +

    style: _biggerFont,

    48

    +

    ),

    49

    +

    );

    50

    +

    },

    51

    +

    );

    52

    +

    }

    34

    53


    }

    35


    class
    _RandomWordsState
    extends
    State<RandomWords>
    {

    54

    +
    class
    RandomWords
    extends
    StatefulWidget
    {

    55

    +

    const RandomWords({super.key});

    56

    +


    36

    57



    @override

    37



    Widget
    build(BuildContext
    context)
    {

    38



    final wordPair = WordPair.random();

    39



    return Text(wordPair.asPascalCase);

    40



    }

    58

    +

    State<RandomWords>
    createState()
    => _RandomWordsState();

    41

    59


    }

  6. Restart the app. You should see a list of word pairings no matter how far you scroll.

    App at completion of fourth step on Windows

    Windows

    App at completion of fourth step on iOS

    iOS

Problems?

If your app is titinada running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following link to get back on track.

  • lib/main.dart

Profile or release runs

So far you’ve been running your app in
debug
mode. Debug tendensi trades performance for useful developer features such as hot reload and step debugging. It’s not unexpected to see slow performance and janky animations in debug mode. Once you are ready to analyze performance or release your app, you’ll want to use Flutter’s “profile” or “release” build modes. For more details, see Flutter’s build modes.

Next steps

The app from part 2

The app from part 2

Congratulations!

You’ve written an interactive Flutter app that runs on iOS, Android, Windows and web. In this codelab, you’ve:

  • Created a Flutter app from the ground up.
  • Written Dart code.
  • Leveraged an external, third-party library.
  • Used hot reload for a faster development cycle.
  • Implemented a stateful widget.
  • Created a lazily loaded, infinite scrolling list.

If you would like to extend this app, proceed to part 2 on the Google Developers Codelabs site, where you add the following functionality:

  • Implement interactivity by adding a clickable heart icon to save favorite pairings.
  • Implement navigation to a new route by adding a new screen containing the saved favorites.
  • Modify the theme color, making an all-white app.