Majid Hajian Majid is a Google Developer Expert, an award-winning author, Flutter, PWA, a perf enthusiast, and a passionate software developer with years of developing and architecting complex web and mobile applications.

Introduction to using Dart in Flutter

7 min read 2079

Flutter Logo

Flutter is one of the fast-growing technology when it comes to cross-platform development, and the secret sauce behind making a Flutter application is the Dart language.

While you can start developing a Flutter app even if you are unfamiliar with Dart, this tutorial will cover the essential syntax and information you need to know in order to feel comfortable building a Flutter app.

What is Dart?

Dart is a versatile and client-optimized language for fast-developing apps on any web or mobile platform, and can be used on desktop and embedded devices. The core goal of Dart language is to provide you with a set of tools and a programming language that makes you productive and evolves as developers’ requirements and needs grow.

Dart is a comprehensive language and offers excellent language capabilities, such as Future, Stream, Sound Null Safety, etc.

Dart’s designed to be familiar to most developers with various backgrounds in programming. So, no matter if have a background in JavaScript and TypeScript, or if you have been an object-oriented programmer, you’ll find working with Dart familiar.

And, because of Dart’s architecture, killer feature, hot reload, and declarative programming are all possible in Flutter.

More importantly, Dart also comes with many built-in libraries, such as dart:async, dart:convert, dart:html, dart:io, etc., as well as a fantastic ecosystem and outstanding package manager pub.dev.

Whether you want to use Flutter or not, Dart would be a great choice to learn and use in your next application.

If you’d like to give it a try quickly, you can use dartpad.dev online.

How to use Dart

Before you start with creating a Flutter application, you should know a few Dart concepts:

We made a custom demo for .
No really. Click here to check it out.

1. Main() function:

The entry point of every app is the main() function. Even if you want to print something in the console, you must have a main() part.

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

In Flutter, you will start your application from the main() function in the PROJECT_ROOT/lib/main.dart, where you pass your main widget to runApp() that will attach it to the screen. That’s the first main entry point.

void main() {
  runApp(MyApp());
}

2. : (semicolon):

You need ; (semicolon) in Dart, as you can see in the example above:

runApp(MyApp());

3. Types and null safety

Dart is a type-safe language. It uses static type checking and runtime checks. When you learn the syntax, you understand Flutter code quicker. Here is the anatomy of a simple variable:

[MODIFIER] [TYPE] [VARIABLE_NAME] = [VALUE];

// e.g: 
final String name = "Majid Hajian";

Although types are mandatory, type annotations are optional because of type inference. So, you may encounter this:

var name = "Majid Hajian"; // from now on `name` is a String;

The most common initializing variables modifiers in Dart are var, final, const, and late, but keep in mind that you can use all modifiers except var when you use type before the variable name.

var name = "Majid Hajian";
String name = "Majid Hajian";
final String name = "Majid Hajian";
const String name = "Majid Hajian";

Using var or using no modifier creates a flexible variable, which means you can change the value anytime. If you never intend to modify the variable, you should use final, which sets the variable only once, or const, which forms a compile-time constant.

But there are more complex situations. Let’s take a look at Map and List type definition:

// Type of a List (Array): List<TYPE_OF_MEMBER>
// e.g: 
List<String> names = ['Majid', 'Hajian'];

// Type of a Map (Key-Values): Map<Key_TYPE, VALUE_TYPE>
// e.g: 
Map<String, number> ages = {'sara': 35, 'susan: 20};

In many cases, you may not provide enough information to the Dart analyzer, and you may face a type casting error. Let’s see an example:

var names = [];

The variable types infer List<dynamic> and dynamic could be any type because we do not provide the array’s possible type when we initialize the variable. Therefore, Dart cast the type to List<dynamic> where it could be anything. By adding an annotation to the value while initializing or launching the variable, we can prevent this type of error.

final names = <String>[];
// Or
final List<String> names = [];

As of Dart 2.12, Dart is a sound null safe language, which means types in your code are non-nullable by default, and that indicates a variable cannot contain null unless you say they can.

final String name = null;
// or
final String name; 

Notice that the variable above is not valid anymore because we initialize a variable with a null value. Because we haven’t specified that yet, runtime null-dereference errors turn into edit-time analysis errors.

Here is when ? comes handy. To assign the variable with the null value, we can use ? to its type declaration.

final String? name = null;

You’ll see this syntax often in Flutter 2.0.0+ together with Dart 2.12.0+.

Finally, the most common built-in types in Dart that you will find on a Flutter application are the following:

  • Numbers (int, double)
  • Strings (String)
  • Booleans (bool)
  • Lists (List, also known as arrays)
  • Sets (Set)
  • Maps (Map)
  • Symbols (Symbol)
  • The value null (Null)

4. Classes

Dart is an object-oriented language with classes and mix-in base inheritance. That means you can create abstract types, class, use implement, and extends. You may also see with where you want to use a mix-in.

In Dart classes, the constructor name is the same as className, like so:

class MyApp {
  MyApp(); // constructor
}

You don’t need to have a constructor if you do not need to initialize the instance variable or create an object. In case you need that, you should pass them via constructor parameters.

class MyApp {
MyApp(this.title);

final String? title;
}

In Dart 2, you don’t need to use new keyword to instantiate a class, either.

final myapp = MyApp('Majid Hajian');
print(myapp.title);

All widgets in Flutter are an extended class of StatelessWidget or StatefulWidget. Hence, you can create your widget (class):

class MyApp extends StatelessWidget {

}

StatelessWidget and State object corresponded to StatefulWidget have both build() method to build your screens. So, to implement your widgets to be built, you must @override the build() method.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

The convention is to start className with a capital letter.

5. Parameters in Dart

It’s imperative to learn how you can define parameters, whether in a class or a function, as it’s one of the crucial parts of Flutter development.

  • required

In Dart, if you want to define a required parameter, you can pass them to a constructor or the function.

String getUrl(String prefix, String host) {
  return '';
}
// OR 
class MyApp {
  MyApp(this.title);

  final String? title;
}

In both cases, you need to pass the parameters correctly to the expected position. That’s what we name positional parameters, too.

  • optional

In many situations, you’ll find that you’ll want to make a parameter optional. For example, to change the code above, we can code like this:

String getUrl({String? prefix, String? host}) {
  return '';
}
// OR
class MyApp {
  MyApp({this.title});

  final String? title;
}

We use {} to define our optional parameters that are named as we described. Now, to use the named parameters, use the name of the parameters and assign the value.

getUrl( host: '', prefix: '');
//Or 
MyApp(title: 'Majid');

As you can see, the main advantage of using this approach is that you do not need to pass the value to the parameter’s exact position.

More importantly, Your functions and class parameters are self-documented. In other words, you can simply define what the name of param is and pass the value. Defining name parameters is helpful when you want to specify many parameters for one method or class.

You’ll come across named parameters in Flutter often. Here is an example of the Text widget in Flutter:

Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
})

The this.data is positional, meaning the first argument is mandatory to pass in, but the rest of the parameters are optional as they are defined within {}.

You may not ask how a named parameter can be required instead of optional. In Dart 2.12+, you now have the required keyword that makes an argument to become mandatory to pass in. Let’s look at the example above.

class MyApp {
  MyApp({this.title}); // optional named parameter
  final String? title;
}

But if you use the required keyword before the argument, we will make it mandatory to pass it in, even though it’s a named parameter.

class MyApp {
  MyApp({required this.title});

  final String? title;
}

If you now instantiate MyApp() class, you have to pass title too; otherwise, the compiler will throw an error.

print(MyApp(title: 'Majid'));

6. Dart formatting

Dart comes with a formatting tool that helps make your code more readable. Here’s a tip that will help you format your code much better, especially in Flutter, where you will have many nested widgets. Use , where you can!

 Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Text('You have pushed the button this many times:'),
    Text('$_counter', style: Theme.of(context).textTheme.headline4),
  ],
),

Here is a Column widget that has two Text children. None of the children use , where they pass arguments. The text is cramped and not easily readable, but if you use , at the end of the last parameter in each Text widget, it will be formatted and more friendly.

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Text(
      'You have pushed the button this many times:',
    ),
    Text(
      '$_counter',
      style: Theme.of(context).textTheme.headline4,
    ),
  ],
),

You’ll get the formatting out-of-the-box for free with the format tool in the command line or your editor of choice together with the Dart plugin.

7. Functions

You may define a function inside a class — i.e., methods — or in top-level. Creating a function as simple as the syntax below.

// top-level
getMyname() {
// logic
}

// OR 

class MyClass() {

  getMyName() { 
  }
}

7. Async/Await

Dart provides asynchronous programming via Future or Stream. To define a Future, you can use the async keyword.

Future<String> getUrl({String? prefix, String? host}) async {
  return 'd';
}

And to wait until the value is resolved, you can use the await keyword.

main() async {  
  final url = await getUrl(prefix: '', host: '');
}

You should use the await keyword wrapped in a function/method with the async keyword.

To create a Stream, you’ll use async* keyword. Now, you can subscribe to the stream and get the value every time that it is emitted until you cancel this subscription.

 getUrl(prefix: '', host: '').listen(
    (String value) {
      print(value);
    },
  );

Notice that the listen() function accepts a function, i.e., a callback, and because everything is an object in Dart, you can pass them around even in functions. This is commonly used in Flutter when events are occuring, such as onPressed.

TextButton(
      onPressed: () {
        // pressed
        // logic
      },
      child: Text('Submit'),
)

Understanding a Flutter widget tree

Now, you should be able to read, understand, and write Flutter code. To prove it, let’s take an example:

class MyCustomWidget extends StatelessWidget {
  MyCustomWidget({this.counter});

  final String? counter;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'You have pushed the button this many times:',
          ),
          Text(
            '$counter',
            style: Theme.of(context).textTheme.headline4,
          ),
        ],
      ),
    );
  }
}

First, you’ll create your custom widget where it uses extends. Then, you’ll @override the build() method. You’ll return Center, a Flutter pre-defined widget with several name parameters, including the child where you assign Column.

Column has several name parameters where you use only mainAxisAlignment and children. You’ll have two Text widgets where they have both positional and named parameters, and so on.

You’ll see now how easily you can understand this code, and you can now even write yours!

Conclusion

Flutter is a fantastic piece of technology that helps you create a cross-platform application, and Dart is its foundation. Dart is easy to learn when you know where to start and what to learn first.

In this article, we reviewed the most widely-used fundamentals in Flutter so that you can open a Flutter application and not only understand the initial syntax, but can write your Dart code too.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Majid Hajian Majid is a Google Developer Expert, an award-winning author, Flutter, PWA, a perf enthusiast, and a passionate software developer with years of developing and architecting complex web and mobile applications.

Leave a Reply