Flutter Basics - Understanding Dart

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Writing Dart🎯 code is always fun because I think that it organizes itself more elegantly when auto-formatted. It looks damn gorgeous😍 right! I hope you have read my previous post where I discussed in detail creating your first app with Flutter. It was a lot of fun but let's quickly cover the basics of Dart in this post that will enable us to code more complex logic. You can run and test your Dart code online here. Let's jump right in!

Variables and Datatypes

Dart is a statically typed Object-Oriented Language. A variable without a value has a null value by default. We can also assign a null value explicitly to a variable at the time of variable declaration or at any point in time.

Primitive Data Types

There are int, double, boolean, string data-types as Primitive data-types in dart. During the lifetime of a variable, it must only contain the data-type that it was originally defined with. Any attempt of assigning data that is of a different data-type will result in compilation error.

The infinity value is a static property of the double class and it can be accessed using double.infinity. Some more important properties that you must look into here.

The var keyword

We can assign any value to a variable that is declared of type var and Dart will automatically infer its type from its initial value. Type-inference is a built-in feature of Dart.

The num data-type

num keyword is also available in dart which allows us to create variables that can hold both int and double values. Its use case can be where a variable can contain int or double type values at any point in time.

Strings

A string can be declared using single ' ' quotes or double " " quotes similar to Javascript. You can also use triple single ''' quotes or """ double quotes to break a single line into multiple lines of code.

The dynamic data-type

You can declare a variable explicitly dynamic by using the dynamic keyword or it is automatically assigned by dart where the data-type is not explicitly declared.

Note,

Always use dynamic data-type with caution and declare your data-types explicitly as frequently as possible. Failing to do so can introduce certain bugs in your code. Side Note- Use dynamic only when you know what you are doing😉.

Functions

Dart is a typed language which means that everything in Dart has a type! Consider an example of the main function below.

The main(){} function

void main() {
  for (int i = 0; i < 5; i++) {
    print('hello ${i + 1}');
  }
}

This function has a return type of void. Dart automatically calls the main(){...} function for you when your app starts. This is a simple for loop that executes when this main function is called.

Generally, you can name any function with any name you want but main is a special name because it is the entry point of any Dart application.

Note,

You should follow camel-case convention to name your functions, i.e, yourFunctionName(){}.

General Functions

Defining your Functions

As discussed above you can define your function and name it using camel-case. If you are returning some value from the function then the return type of the function has to be declared. It is not compulsory but if you don't do it dart will not throw an error but execute it with a warning. Let us consider this example of functions with return types and no return types,

addTwoNumbers(num1, num2){
  print(num1 + num2);
}

void addTwoIntegerNumbers(int num1, int num2){
  print(num1 + num2);
}

int sumOfTwoNumbers(int num1, int num2){
  return num1 + num2;
}


void main() {
  addTwoNumbers(1, 2);
  addTwoIntegerNumbers(3,4);
  print (sumOfTwoNumbers(10,20));
}

Now, let us consider the first function addTwoNumbers(...){...}. This function has no explicitly declared return type and argument type. So, Dart will still parse and execute this function like,

dynamic addTwoNumbers(dynamic num1, dynamic num2)

Dart automatically assigns the type dynamic to the function and the arguments. You can pass any type of data as arguments to this function and dart will try to add them. But this type of function declaration is more error-prone as you can add an int and bool type which will not result in compilation error but runtime error!

The second function void addTwoIntegerNumbers(int num1, int num2){...} is less error-prone as we have explicitly declared the return type and argument types, i.e, void and int. As soon as you pass any other data that is not int to this function the compiler will start yelling at you.

The third function int sumOfTwoNumbers(int num1, int num2){...} has a return type of int because instead of printing the sum we are returning it from the function.

In the main function we call these methods by their name and to execute them we add () parenthesis.

I say it Again! Avoid the dynamic type!

Dart is a mix of strongly-typed as well as a loosely-typed language but it is recommended to avoid the dynamic type as much as possible as I illustrated to you with the example above. This will result in a more error-free and stable code!

Objects

Everything in Dart is an Object! Yupp, every object including null is an instance of some class and all these classes inherits from Object class. You can confirm this by using is and is! operator to check if an object is an instance or a type of a class. Consider this example below,

void main() {
  print('"Shashank" is an instance of "String" class: ${"Shashank" is String}');
  print('7799 is an instance of "int" class: ${7799 is int}');
  print('79.97 is an instance of "double" class: ${77.99 is double}');
  print('true is an instance of "bool" class: ${true is bool}');
  print('null is an instance of "null" class: ${null is Null}');
  print('"Anything even strings" are an instance of "Object" class: ${"Anything even strings" is Object}');
  print('null is an instance of "Object" class: ${null is Object}');
}

This results in the output below,

"Shashank" is an instance of "String" class: true
7799 is an instance of "int" class: true
79.97 is an instance of "double" class: true
true is an instance of "bool" class: true
null is an instance of "null" class: true
"Anything even strings" are an instance of "Object" class: true
null is an instance of "Object" class: true

So, you can clearly see that everything in Dart is an Object and the name of the class is the Data Type Identifier! The $ operator used in the above example is used to inject dynamic data in strings. For multi-level values use $ with {} like this, ${person.name} or ${77.99 is double} where you can also perform operations. If using a single value or variable you can use $ simply without {} like, 'My name is $name' where name is a variable.

Classes

Just like other object-oriented programming languages, classes are the core-features of Dart. It allows us to define blueprints for our Objects. A class must have a name using the UpperCamelCase naming convention. Inside the class, we define how the Object should look like.

You often build your own classes if you want to express more complex relations between data or if you want to encapsulate certain functionality in one building block.

class Product {
  var name = 'MacBook Pro';
  var price = 1299.99;
}

void main() {
  var product1 = Product();
  var product2 = Product();
  product2.name = 'iPad Pro';
  product2.price = 999.99;
  print(product1);
  print(product1.name);
  print(product1.price);
  print(product2);
  print(product2.name);
  print(product2.price);
}

The above code will result in the following output,

Instance of 'Product'
MacBook Pro
1299.99
Instance of 'Product'
iPad Pro
999.99

We have a class named Product which has two instance(class-level) variables, i.e, variables inside a class which we initialize with some values. Here we are using var to define our variables because of dart type-inference. Dart is pretty smart in inferencing the type of value that is assigned to the variable of type var automatically.

After that, we create two new Objects with this class in our main(){...} function. We can access the class/instance variable using dot . notation like product1.name, i.e, objectName.instanceVariableName. We can also assign new values to these variables.

To declare a variable private in dart we use _ in front of instance variable name like, _description. This makes the variable private and inaccessible outside the class.

I will not bore you with an hour-long discussion on classes and objects, there is a whole lot to cover so I will discuss classes in much detail in the next blogs in this series! With that let's have a quick look at Lists, Maps, const and final.

final versusconst

const and final keywords are available in Dart that is used to create variables with fixed values. But wait! What's the difference then?

const offers a compile-time constant, which means that its value must be declared while compiling the program whereas final is a runtime constant which means that its value can be assigned during the program execution.

Let's understand this better with examples,

int calculateSquare(int value){
  return value * value;
}

void main() {
  const double PI = 3.1415;
  final int square_7 = calculateSquare(7);

  print(PI);
  print(square_7);
}

As you can see above that, the value of PI is known at the compile-time but the value of square_7 is not known at the time of compilation. It is calculated at the runtime. This is the main difference between final(runtime constant) and const(compile-time constant).

Lists in Dart

Like Arrays in other programming languages, Dart offers a List class which is used to construct array-like data structures that can hold a fixed or a variable number of similar or dynamic data-types. Dart also has [] literal syntax that is used to create lists.

Fixed Lists

Lists of fixed length can be declared by defining their length. For example,

List<dynamic> aFixedList = new List(9);
List<String> anotherFixedList = new List(7);

The List<dynamic> creates a list of length 9 and the data-type of elements in the list is defined by the keyword inside the <> because List is a generic type. In the first case, the length of the List is 9 and the data-type of elements is dynamic. In the second case, they are 7 and String data-type respectively.

Growable Lists

Lists that are defined without any argument/value in the List() constructor are known as Growable lists. Consider this example below,

List<String> aGrowableList = new List();
aGrowableList.add("Shashank");
aGrowableList.add("Biplav");

//changing a value at an existing index in a list
aGrowableList[0]= 'Love';
aGrowableList[1]= 'Dart';

Maps

If you are familiar with a dictionary in Python or object literal in Javascript then maps in Dart are similar. Basically, maps are a collection of key-value pairs. Dart has a Map class that is used to create a map. A map can contain any type of keys and values.

Consider this example,

var trainCompartments = new Map<int, String>();

//assign values to the keys
trainCompartments[1] = 'ENGINE';
trainCompartments[2] = 'WOMEN CHAIR CAR';
trainCompartments[3] = 'WOMEN SLEEPER CAR';
trainCompartments[4] = 'MEN CHAIR CAR';
trainCompartments[5] = 'MEN SLEEPER CAR';
print("trainCompartments=> $trainCompartments");

Here, 1, 2, 3, 4, 5 are not indexes but they are keys! The print statement will give the following result,

trainCompartments=> {1: ENGINE, 2: WOMEN CHAIR CAR, 3: WOMEN SLEEPER CAR, 4: MEN CHAIR CAR, 5: MEN SLEEPER CAR}

We can iterate over entries of a map which can be accessed from the .entries property on a map. We will see Maps and Lists in greater detail in the upcoming posts in the series.

Conclusion

That was a lot to cover but believe me, we have scratched only the tip of the iceberg. This was just a quick walkthrough so that we can proceed to build some dope stuff. We will revisit all these topics in greater detail in the series further.

In the meantime, if you liked this post a like👍🍺🎉👏 on this would make me happy! Stay tuned for the next post in this series. In the next post, we will discuss Widgets and Widget tree. Thanks for reading it till the end fellas. Happy Coding👨🏽‍💻!

No Comments Yet