Welcome to the second article in the three-part series of dart programming language. If you haven’t yet checked out my previous part, click here.

In this article, I will try my best to introduce you to Dart by starting with the foundation of Object Oriented Design, Generic System and conclude with taking about File System in Dart.

Object-Oriented Fundamentals

Before Object Oriented languages came out, programs used to be written as a long series of operations that execute from top to bottom and were very hard to maintain. C, Fortran and COBOL were some example and were called Procedural programming languages. Just like the recipe for making that Ramyun Noodles, a list of the sequence of steps you need to follow. Nevertheless, for beginners, that’s easy to learn and also easy to maintain for small programs.

However, let’s try to deconstruct that Ramyun Noodle example, and try to build it using Object-Oriented concepts. Instead of describing the steps to cook noodles, let’s consider each of the objects in the kitchen and what they can do. The pan, your noodle, the stove and many others. Now, these objects are like some mini-program that can contain their own data and functions and they interact with another object to perform some combined task like cooking that noodle. As you can see the end goal of both procedural and object-oriented concept is the same, but it differs in the way we tackle that problem and the way we think about it. Moreover, later way of thinking makes our code more reusable. For example, what if we want to eat Wai Wai Noodles the next day, can we again use the same line of code as in Ramyun Noodles? Let’s say the statement is “Pour Ramyun noodles into the pan”. No, we cannot, right? (Though we can, by using variables and functions, but for now let’s think about the larger program 😬). But we can directly use the Stove and the Pan object again because it does not care about the noodle object.

Hope that will clear some doubts about object-oriented. As you realize, it is not a programming language and is something else which we refer to as a programming paradigm. There are other paradigms like functional paradigm which we will cover in our Advanced Dart article. Although these other paradigms have some specific use case, you will always encounter object-oriented in your journey as they are heavily used in web programming, mobile programming and even game development. Now, let’s dive in the world of object-oriented with the introduction of Objects and Classes.

Objects and Classes

The major intent of the object-oriented design was to make thinking about programming more approachable, by making it closer to thinking about the real world. Now let’s forget about computer programming for a while and think about what exactly is an object in a real life? A package of Ramyun noodles? or that Pan we used to cook? or that Stove we turned on? or the Ramyun factory in China? Sure, all these things are objects. They have their own identity and their own use case, they are separate from one another and have their own characteristics. They have their own separate property which helps us to identify them like colour and size. And they also have some behaviour like the Stove can be turned on or off. And these two things: attributes and behaviour are actually the same thing that defines objects in object-oriented programming.

In computer programming, an object can resemble real-world objects like ones above but it could be more abstract as well as a Date, a Calender or Time. Let’s take a practical example, suppose you are writing a simple web application that requires a user to log in and register. Then you would create a user object from a user class (We will talk about class next, but for now they are what helps us create an object). That user has certain attributes like last name, first name, email and password and he/she should be able to log in and register.

We can’t discuss object without talking about classes, because we use classes to create them. A class is simply a blueprint or a template or a description of how we can create the object. It is exactly like a blueprint of a house. Blueprint is not a house but we use that blueprint to create the house and we can create one house or maybe million houses using the same blueprint. Same is the case with classes and objects, we define the class once and then create many objects of that class. A class simply describe attribute and behaviours. Let’s define a class and create an object from that class in Dart.

main() {

void user1 = new User();

var user2 = User();



user1.id = 121;

user1.email = "sharadghimire5551@gmail.com"

print("${user1.id} and ${user1.email}");



user1.register();

user1.login(email);

} class User {

int id;

String lastname;

String firstname;

String email;

String password; void login(email, password){

print('Welcome! Your email is ${email}');

}

void register() => print('Thanks for registering');

}

Here, class User {...} defines states (properties) and the behaviour of a User.

defines states (properties) and the behaviour of a User. new keyword is used to create an object from a class. In Dart, we can also directly create an object without new .

keyword is used to create an object from a class. In Dart, we can also directly create an object without . id, lastname, firstname, email and password are called instance (field) variable whose default value is null . login() and register() are called methods.

are called instance (field) variable whose default value is . are called methods. user1.register() This is how you call a method of an object.

Four Fundamental Concepts in Object Orientation

Abstraction: Let’s get back to that noodle. If I say noodle to you what do you understand? What comes to your mind? A long, curly thing that we can eat, right? I didn’t say if it is Ramyun noodle or Wai Wai noodle, neither did I said cooked noodle or uncooked, you just understood what noodle is and I did not have to get that specific to make you understand. Because you already understand the actual idea of noodle. You have eaten many noodles to abstract the idea of what a noodle looks like. That’s what abstraction means, that we focus on the essential qualities of something rather than one specific example. So, we have an idea that is completely separate from any specific instance of that object, then we call it an abstraction. And that’s what we do with OOP while making a class. We don’t create a separate class for ramyun and a separate class for wai-wai, instead we create a class called noodle and create ramyun object from that class. (I promise this is the last time you are hearing about the noodle. I don’t know. I think I am obsessed with this noodle 🙄) Encapsulation: Take your attribute and the behaviours and encapsulate them together in the same class and restrict access to the inner working of that class. An object should not reveal about itself except what is absolutely necessary and hide other data which can only be accessed from inside that class. For example, we use buttons on the remote and see the output in the tv but we don’t know what’s inside the remote or how it is working. This is also called black boxing. It allows us to safely change the way our objects work. We only have to worry about the specific class rather than the whole program. Its all about reducing dependency inside our application so that a simple change in one class does not affect another. Inheritance: We can simply create a new class but instead of writing all of the content of that class, we base it on an existing class. For example, we have a User class in our application, later we need a separate class for an Admin User. Here’s the thing, the User class and the Admin User class are exactly the same except some more attributes and behaviours in the Admin User class. As we are using abstraction, we are trying to focus on the important aspect of the class and not all the users are admins. So, here are two solutions, either we have to create a new class or base that class to an existing one. In OOP, it is better to base or say inherit from another class. And that’s what Inheritance means. Now, our new admin class automatically have all the properties and behaviour of the user class and we can add its own attributes and methods to it. In terms of OOP, the user class is now called superclass (or parent class) and admin class is called subclass (or child class). Moreover, we can create many other class that can inherit from our base user class. Now, we can create a user object, or admin object or simply any other objects. Polymorphism: Polymorphism simply means “many forms”. Meaning, we can use the same construct in different scenarios and that will perform the correct behaviour according to that specific context it is being used at. For example, we can have a user class and that inherited admin user class with their respective attributes and behaviours. Say, user class has a login method defined inside it which logs in the user and print the name of the user in the screen. As admin user is a child class of user class, it automatically has that method inherited. So, we added inside admin class that method and instead of print the name of the user we added “Welcome admin”, this is referred to as overriding the method. Now, if we called login method for user it will print the name and if we called the same method for the admin user, it will print welcome admin message. Login function is working differently in a different context.

Okay, that ’s a little too much. Don’t panic. They are just some underlying concepts of OOP which you will understand as you go through them. You may already be using them.

Constructors

These objects we have been creating has something called lifecycle methods ie. these methods run in some instant or life of an object. One of them is called the constructor, which is a special method of a class that is responsible for initialization of an object of that type. It has the same name as the class name and is mostly used to set the values of the members to some user-defined or some default. In some other language like C++, we have a concept called deconstructor, which is executed automatically when an object is destroyed that has been created by the constructor. Dart doesn’t have deconstructor because it has something called garbage collector which go out and removes all the unwanted object from the memory. There are different types of constructor method in the Dart language.

void main(){

var user1 = User();

var user2 = User(id: 1, name: "Sharad");

var user3 = User.createNewOne();

var user4 = User.createNewOne(12, "Sharad");

user1._marks = 200.0; // Error because its private field

} class User {

int id;

String name;

double _marks = 100.0; User(){} // Default constructor User(int id, String name){ // Parameterized constructor

this.id = id;

this.name = name;

}



User({this.id, this.name}); //Shortcut for param. constructor User.createNewOne() {} //Named constructor

User.createNewOne(this.id, this.name){} //Named param con. // Other methods below.

// Private functions

void _printMarks() => print("You marks: ${this._marks}");

}

_id Here, _ meaning the field is private to this class and other classes cannot access them i.e we cannot modify it from outside. Like user._marks = 200.0 from main() the method gives an error. Some concepts go with methods as well.

Here, _ meaning the field is private to this class and other classes cannot access them i.e we cannot modify it from outside. Like from the method gives an error. Some concepts go with methods as well. Constructors are very useful to pass information to and from the classes. In the parameterized constructor, we are getting values and assigning that values to the field variable of the class.

Before creating an object from the class, the code inside the constructor will run.

Default and Named constructor in the same class is not allowed. We can have as many named constructor as we want to.

this defines that this field variable is part of the object and not part of the function.

Important concepts of OOP in Dart

Scope: Scope simply refers to the visibility of variables. Every variable between { .. } has the scope inside that block of code which is called block scope. Dart is also Lexically scoped. With lexical scoping, child scopes will access the most recently declared variable of the same name. The innermost scope is searched first, followed by a search outward through other enclosing scopes. In doubt, do not use the same variable name because you may encounter a scope problem.

{

// Search outermost block last

String myName = "Sharad Ghimire"";

{

// Search innermost block first

String myName = "Ghimire Sharad";

print(myName); // prints => Ghimire Sharad

}

}

Getter and Setters: Now that we know about scoping and private field variables using _ , let’s understand the concept of getter and setter. Below, we have private variables, but how do we can access them outside the class even though they are private? That’s where getter (get that value) and setter (set that value) come in.

void main(){

var user = User();

user.name = "Sharad"; // Calling setter

print(user.name); // Calling getter

print(user.age); // Prints 0

user.age = 10;

print(user.age); // Prints 100;

} class User {

String _name;

String _emailId;

int _age = 0; //Default value String get name => _name;

String set name(String name) => _name = name; int get age => _age;

void set age(int age) => _age = age * 10;

}

Static members: Static members has nothing to do with the class object or instance and is shared among all of the instances of the class. We can directly call them using the class name instead of initializing its object. Static variables are also known as class variables and static methods are also known as class methods. Static variables are lazily initialized i.e they are not initialized until they are used in the program. So they consume memory only when they are used.

void main(){

var circle = Circle();

//circle.pi; // Error

//circle.calculateArea(); // Error print(Circle.pi); //Directly call using class name

Circle.calculateArea();

} class Circle {

static const double pi = 3.14;

static int maxRadius = 5; String color; static void calculateArea(){

print("Some code");

anotherFunction(); //Not allowed to call instance functions

this.color = "Some"; // Error

} void anotherFunction(){

print("Some code");

Circle.calculateArea();

this.color = "Red";

print(pi);

print(Circle.maxRadius);

}

}

From a Static method, we can only access static method and static variables. But we cannot access Normal Instance Variables and methods of the class.

Inheritance: As you already know, inheritance is a mechanism in which one object acquires properties of its parent class. Superclass of any class is Object , which provides the default implementation of some predefined functions like toString() , and hasCode getter, which returns the hash code of an object. There are commonly three types of inheritance in Dart. Single Inheritance (One class inheriting from another class), Multi-level Inheritance (One class inherit from another which in turn inherit from another class) and Hierarchical Inheritance (Two or more classes inherit from one parent class). Moreover, as you already know, method overriding is a mechanism by which the child class redefines a method which is in its parent class and compiler gives more priority to that of the child class.

void main(){

var livingThing = LivingThing();

var human = Human();

var animal = Animal();

print(livingThing.canReproduce()); //"Yes they can"

print(human.canReproduce()); //"Yes, Human can reproduce"

print(animal.canReproduce());

//"Yes they can" and " Animal can also reproduce"

}

class LivingThing {

bool isAlive = true;

bool canBreadth = true;

void canReproduce() => print("Yes they can");

} class Human extends LivingThing {

String name;

bool work = true;



// method overriding

void canReproduce() => print("Yes, Human can reproduce!");

} class Animal extends LivingThing {

String breed;

String name;

void canReproduce() {

super.canReproduce(); // Also execute parent's method

print("Animal can also reproduce");

}

}

extends keyword is used to inherit from the parent class.

keyword is used to inherit from the parent class. super keyword is used if we want to call the parent’s method or variable from the child class.

keyword is used if we want to call the parent’s method or variable from the child class. Here, canReproduce() method is overridden multiple times, and if we call that method from the different object we get the different result because of polymorphism.

Mixins: Mixins comes into play when considering multiple inheritances. This is one specific feature of Dart. Mixins are a way of reusing a class’s code in multiple class hierarchies. This is an advanced topic which we will cover in one separate article later. Here is one of the best articles for learning mixins. For now, let’s just understand the basics.

main(){

Lizard liz = Lizard();

liz.test(); // Testing in Lizard, Animal, and Reptile

liz.crawl(); // Now we can call crawl() from Reptile class

} class Animal {

void test() => print("Testing in Animal");

} class Reptile {

bool canCrawl = true;

void crawl() => print("Crawl");

void test() => print("Testing in Reptile");

}

class Cat extends Animal {

bool hasTwoPointyEars = true;

void meow() => print("Meow!!");



@override

void test() {

print("Testing in Cat");

super.test();

}

} class Lizard extends Animal with Reptile{

bool hasTail = true; @override

void test(){

print("Testing in Lizard");

super.test();

}

}

One of the requirements of the mixin is that the class should be independent on its own like above Reptile is not extending any other class. So, by using mixin we can call the method of another class without inheriting it.

Interfaces: An Interface is simply a contract between two classes. An interface is used when we need a concrete implementation of all of its functions within its subclass. It is mandatory to override all methods in the implementing class. Also, we can implement multiple classes but we can not extend multiple classes during inheritance. Simply, when we inherit we inherit all the properties of the base call and when we implement we have to implement them our self.

void main(){

Manager srd = new Manager();

srd.printSomething();

} class Worker {

String name = "";

void printSomething() => print("Worker Printing");

} class Manager implements Worker {

String name = "Sharad";

void printSomething() => print("Manager Printing");

}

Abstraction: In abstraction, what we basically building is not a class but an idea or concept. It is similar to the interface but we can actually inherit from them. To make a method abstract, use a semicolon instead of the method body. An abstract method can only exist with the abstract class. We need to override abstract methods in sub-class. For an abstract class, use abstract keyword to declare an abstract class. They can have abstract methods, normal methods and instance variables as well. The abstract class cannot be instantiated, ie. we cannot create objects.

void main() {

var rect = Rectangle();

rect.draw();

}



abstract class Shape {

String x; // Can also define instance variable

void draw(); // abstract method

// We can also define normal functions as well

}



// Whenever we extend a abstract class, then its mendatory to override that class's abstract methods class Rectangle extends Shape {

String x = "yo!";

void draw() => print("Overrided!!");

}

Generics

If you have gone through part one of this article, congratulations, you have already used generics.

List values = [1, 2, 5]; // Dart is smart to figure out datatype List<String> name = new List<String>();

name.addAll(["Sharad", "Ghimire"]); List<int> numbers = new List<int>();

numbers.addAll([1, 2]);

We have a List which is a class and that class is a generic class so we have to give it a type which in the first line is a String type. Again we used the same syntax but instead, we passed an int as the type. And as you can see the same class can be used for int as well as String. That’s called a generic. Basically, the code can handle a different type according to what you want it to do.

void main(){

add<int>(1, 2); // prints 3

add<double>(1.0, 2.0); // prints 3.0

add<String>("Sharad", "Ghimire"); // prints SharadGhimire

addNumbers<String>("a", "b"); // Gives error but still work??

} void add<T>(T a, T b) { // T is shorthand for type

print(a + b); // But, for String, the operator + isn't defined for the class 'Object'

} void addNumbers<T extends num>(T a, T b){ // num includes int and doubles so it does not work for String

print(a +b);

}

Here T is shorthand for Type. We can write any letter in void add<K>(K a, K b){ }.

is shorthand for Type. We can write any letter in Below is another example of generic programming. There we can simply change int value to double and it works perfectly.

main() {

List values = [1, 2, 4, 5];

print(subtract(15, values));

} T subtract<T extends num>(T value, List<T> items){

T x = values;

items.forEach((values) {

x = x - value;

});

return x;

}

Generic Class example: Below we are creating a generic class called Counter<T extends num> { .. } which performs some operations like addAll and total.

void main() {

Counter<double> doules = new Counter<double>();

doubles.addAll([1.0, 2.0, 3.0]);

double.total();

} class Counter<T extends num> {

List<T> _items = new List<T>();

void addAll(Iterable<T> iterable) => _items.add(iterable);

void add(T value) => _items.add(value);



T elementAt(int index) => _items.elementAt(index);



void total() {

num value = 0;

_items.forEach((item){

values = values + item;

});

print(value);

}

}

Note: Examples of generics are taken directly from video series of oreilly. If you wanna follow those. Click here.

File System

For performing actions like reading and writing from and to a file we need to import a separate package called dart:io .

import 'dart:io';

main(){

String myPath = '/';

Directory myDirectory = Directory(path);

Directory dir = Directory.systemTemp.createTempSync(); if(myDirectory.existsSync()){

print("File Exists");

} else {

print("File not found");

} if(dir.existsSync()){

print("Deleting ${dir.path}");

dir.deleteSync();

} else {

print("Could not delete!!!");

}

}

/ is the root directory for Linux. For Windows, we have to write C:\ .

is the root directory for Linux. For Windows, we have to write . Sync vs Async: We will cover asynchronous programming in the next article, for now just understand, I/O can be both synchronous and asynchronous. Synchronous means things will happen one at a time while asynchronous meaning many things will happen at one.

dir.existSync() meaning we have to (or want to) wait and after that thing executes then only other code below it will run.

meaning we have to (or want to) wait and after that thing executes then only other code below it will run. Directory.systemTemp.createTempSync() We are calling a static member of Directory class so we are going to create a temporary directory synchronously. And, we are deleting the directory if exists.

import 'dart:io';

main(){

Directory dir = Directory.current;

List<FileSystemEntity> list = dir.listSync(recursive: true);

print(${list.length});

list.forEach((FileSystemEntity value){

FileStat stat = value.statSync();

print('Path: ${value.path}');

print('Type: ${stat.type}');

print('Modified: ${stat.modified}');

print('Size: ${stat.size}');

});

}

Above code is self-explanatory, we are simply getting current directory and storing all the files in it recursively and printing each file stats like size and type.

import 'dart:io';

void main() {

Directory d = Directory.current;

File file = File(dir.path + '/file.txt');

writeToThatFile(file);

readThatFile(file);

} void writeToThatFile(File file){

RandomAccessFile f = file.openSync(mode: FileMode.APPEND);

f.writeStringSync("Yeah I wrote that programmatically");

f.closeSync();

}

void readThatFile(File file){

if(file.exitsSync){

print("I am reading that file now");

print(file.readAsStringSync());

} else {

print("File not found :( ");

return; // returns nothing

} }

When we write to a file, we can either append (add to the end of the file) or write (completely erase and write from start) to a file. RandomAccessFile means we can randomly access the file from anywhere. We create that random access file and then open it asynchronously given a specific mode, here APPEND, then, we append that string to that file. closeSync() simply close that file. You may ask “Why should we close that file?”. Because file access works with a buffer ( a little section of memory), when we are putting information to the file, it may not get written to the disk immediately so we need to close the file which in turn calls flushSync() which puts everything to the file. For reading from a file, simply call readAsStringSync() on that file object.

Conclusion

That’s a lot to take in, right? Don’t worry if you really focus and practice on these concepts you will get hang of it. That’s it for now, we will talk more about the file system and asynchronous programming in the next and final article. See ya.