Dart la gi

Part 1. Mọi thứ đều bắt đầu từ những thứ cơ bản nhất

Khi nhắc đến việc làm sao để tạo ra 1 ứng dụng mobile thì thứ mọi người sẽ lặp tức nghĩ ngay đến là native app (Android và iOS), nhưng bên cạnh đó vẫn còn rất nhiều công nghệ có thể giúp bạn tạo ra 1 ứng dụng mobile như Cordova, Webview (WeChat), Ionic, Xamarin và React Native. Tất cả các framework, platform đó đều hổ trợ người dùng tạo ra được 1 ứng dụng mobile theo ý muốn của mình. Và để không bỏ lỡ cuộc vui thì Google cũng đã mang đến cho người dùng đứa con mới nhất của mình - Flutter - nó kế thừa cũng như nổi bật hơn các công nghệ hybrid app kia. Vậy thì làm sao để có thể sử dụng, có thể tạo ra được những sản phẩm theo ý muốn của mình? Bài viết với những kiến thức của bản thân mình sẽ đem đến cho bạn có được 1 số kiến thức cơ bản nhất để có thể tự mình "chiến" và "khám phá" Flutter.

Dart Language

Bao giờ cũng vậy, bạn muốn "chiến" hoặc "chỉ học để biết" 1 library, 1 framework, 1 platform thì ngôn ngữ (language) sử dụng để build lên nó là cái bạn cần phải quan tâm và tìm hiểu, cũng giống như 1 chiếc xe nếu không có xăng thì cũng chỉ để ngắm.

Flutter sử dụng ngôn ngữ Dart để lập trình, ngôn ngữ Dart ra đời từ năm 2011, nhắm đến tạo ra ứng dụng chạy đa nền tảng - web, mobile, desktop và IoT nhưng lúc ấy cộng đồng còn khá nhỏ và cũng không phải là sự lựa chọn cho các công ty (vì đây là ngôn ngữ phát triển bởi chính Google và các công ty cũng rất e dè điều này). Khi Google phát triển 1 os mới (operation system) - Fuchsia - thì Flutter được chọn làm nền tảng cho các ứng dụng đến lúc này thì Dart mới có được nhiều người biết đến và cộng đồng được mở rộng hơn.

Phiên bản hiện tại lúc viết bài này là 2.2. Những bản 1.x và 2.0 vẫn còn khá nhiều lỗi khiến coder lúc viết khá lúng túng, trình biên soạn vẫn còn chưa support tốt, tuy nhiên với việc ra bản 2.1 thì mọi thứ đã tốt hơn rất nhiều.

Dart là ngôn ngữ đơn giản, dễ tiếp cận và cũng khá dễ hiểu. Dart là ngôn ngữ tĩnh, theo hướng đối tượng (object oriented programming), functional programming và lexical scoped. Nó như 1 sự kết hợp giữa Java và JavaScript nên khi học nó nếu ai đã có nền tảng 1 trong 2 ngôn ngữ kia thì lúc tiếp cận sẽ khá dễ. Dart hỗ trợ khai báo biến linh hoạt (loose and strong typing) với cú pháp (syntax) kiểu C và biên dịch kiểu JavaScript. Okay, vài dòng giới thiệu về ngôn ngữ, giờ sẽ không mất thêm thời gian hãy đi vào coi thử làm sao để "code" với Dart.

1. Project structure

  • Thư mục bin: đây là nơi chứa các file public tools để chạy Dart scripts.
  • Thư mục lib: đây là nơi chứa các file public libraries để import vào trong các file ở thư mục bin.
  • pubspec.yaml: nơi import dependency, quản lí phiên bản của Dart, etc.. nó tương tự gradle trong Android và được viết bằng YAML language.

2. Important concepts

Đây là phần chú ý đầu tiên khi bạn đọc trên trang chủ của Dart. Vì thế hãy đọc kỹ hướng dẫn trước khi liều.

  1. Bất kì thứ gì có thể đặt vào 1 biến đều là object và mọi object đều là thể hiện của 1 class. Số, hàm, null đều là object. Mọi object thì đều xuất phát từ Object class.
  2. Tuy Dart là strong typed nhưng Dart vẫn hỗ trợ loose typed. Có nghĩa là nếu bạn chưa chắc chắn về kiểu dữ liệu cho biến thì vẫn có thể khai báo với kiểu dynamic.
  3. Dart hỗ trợ generic type, ví dụ ListList.
  4. Dart hỗ trợ top-level function (như hàm main()). Bạn có thể tạo ra 1 hàm bên trong hàm khác (nested hoặc local function).
  5. Dart cũng hỗ trợ top-level variables.
  6. Không như Java, Dart không có các từ khóa để set access modifier. Nếu 1 indentifier bắt đầu với ( _ ) thì nó là private trong library của nó.
  7. Identifier có thể bắt đầu bằng một chữ cái hoặc dấu gạch dưới ( _ ).
  8. Dart vừa hỗ trợ expressions, vừa hỗ trợ statements. Bạn có thể sử dụng biểu thức ? exp1 : exp2 và cũng có thể sử dụng câu lệnhif else.
  9. Dart tools có thể báo cho bạn 2 loại vấn đề: warnings và errors. Warnings là những dấu hiểu chỉ ra rằng code của bạn có thể không hoạt động, nhưng chương trình của bạn vẫn có thể chạy. Errors có thể là error lúc compile-time hoặc run-time. Error lúc compile-time hiển nhiên sẽ khiến code bạn không chạy được, còn kết quả của error run-time sẽ là những exceptions được throw ra khi chạy.

3. Variables

  • String - để thao tác với chuỗi và kí tự.

     String name = 'huy pham';

  • num - để thao tác với số.

     num number = 17;
     num number1 = 17.03;

    • int - để thao tác với số nguyên.
    • double - để thao tác với số thập phân.
  • bool - để khai báo biến boolean.

  • const - để khai báo biến hằng.

  • dynamic - có thể dùng để khai báo cả chuỗi và số.

     dynamic name = 'huy pham';
     dynamic age = 25;
     dynamic isHandsome = true;

  • var - để khai báo biến khi chưa biết là chuỗi hay số, không thể dùng để khai báo kiểu trả về của hàm.

  • Runes - để sử dụng các emoji.

     Runes clap = Runes('\u{1f44f}');
     print(String.fromCharCodes(clap));

Tất cả biến trong Dart đều không phải là dạng nguyên thủy nó đều là đối tượng.

4. Collection

  • Enumerated types - còn được gọi là enum, một kiểu class đặc biệt sử dụng để biểu diễn một tập hợp giá trị không đổi.

     enum color {
     	red,
     	blue,
     	green,
     	orange
     }

  • List - một collection thông dụng, dùng để tập hợp các phần tử vào 1 mảng, trong Dart chi có object list và nó làm hết công việc của ArrayList như add, remove, insert.

     List<num> numbers = [0, 1, 2, 3, 4, 5];
     List mixin = [69, 'kimochi', true]; // List

  • Map - 1 object thông dụng khác, chứa cặp key-value. Dart hỗ trợ map literals và kiểu dữ liệu Map.

     Map roles = {'H': 'dev', 'U': 'tester', 'Y': 'designer'};
     
     Map<String, int> languages = Map();
     languages.putIfAbsent('java', () => 1890);
     languages.putIfAbsent('dart', () => 2011);
     languages.putIfAbsent('java', () => 1995);

  • Set - tập hợp các giá trị không theo thứ tự và không lặp lại giá trị đã có.

     Set<int> numbers = new Set<int>();
     numbers.add(3);
     numbers.add(2);
     numbers.add(2);
     numbers.add(7);
     
     var age = {1, 'a', true};

  • Queue - 1 collection có thể thao tác cả 2 đầu vào của mình. 1 đầu để đưa dữ liệu và 1 đầu xóa dữ liệu. Hữu dụng khi xây dựng collection theo kiểu first in, fist out.

     Queue numbers = new Queue();

5. Flow control

  • assert - ngắt 1 thực thi thông thường khi điều kiện trả về sai.

  • if else - như mọi ngôn ngữ khác, sử dụng để điều hướng.

     if(condition) {
     	// do something
     } else {
     	// do another
     }

  • switch case - như mọi ngôn ngữ khác, cũng sử dụng để điều hướng.

     var condition;
     switch(condition) {
     	case decision:
     		// do something;
     		break;
     	case decison1: 
     		// do another thing;
     		break;
     	default:
     		// do default result;
     		break;

  • Loop - vòng lặp.

    • while

     while(condition) {
     	// do something
     }

    • do while

     do {
     	// do something
     } while(condition);

    • for loop

     List alphabets = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'];
     
     // for with index
     for(int i = 0; i < alphabets.length; i++) {
     	// do something
     }
     
     // for each item
     for(var word in alphabets) {
     	// do something
     }
     
     alphabets.forEach((String item) {
     	// do something
     });

6. Function

  • Basic function - Dart là ngôn ngữ hướng đối tượng, hàm trong Dart cũng là một object và có một kiểu dữ liệu cho nó. Điều đó có nghĩa là có thể gán 1 function cho 1 biến hoặc dùng nó làm đối số (argument) cho 1 hàm khác.

     bool isYourName(String name) {
     	return name == 'huy pham';
     }

    • Nếu không khai báo kiểu dự liệu thì sẽ mặc định là void hoặc tùy vào kết quả trả về của hàm.

     isYourName(String name) {
     	return name == 'huy pham';
     }

    • Để rút gọn thì ta nên dùng cú pháp => đối với hàm không có block code.

     isYourName(String name) => name == 'huy pham';

  • Optional named parameter function - khi gọi hàm bạn có thể chỉ định tên tham số đã khai báo trong hàm và gọi tham số tùy ý không cần quan tâm đến thứ tự của nó.

     void declareYourCharacter(bool isHumorous, {bool isQuiet, bool isKind = false}) {
     	// do something
     }
     
     declareYourCharacter(true, isKind: true, isQuiet: true);
     

  • Optional unnamed parameter function - bạn có thể tạo ra 1 tham số tùy chọn khi khởi tạo hàm, lúc gọi làm bạn có thể truyền hoặc không truyền đối số vào cho nó và phải gọi đúng thứ tự như lúc khởi tạo.

     void declareYourCharacter(bool isHumorous, [bool isQuiet, bool isKind = false]) {
     	// do something
     }
     
     declareYourCharacter(true, true, true);

  • Anonymous function - hầu hết mọi hàm đều có 1 cái tên để thể hiện hàm đó làm gì nhưng chúng ta vẫn có thể khởi tạo 1 hàm không tên và gọi nó là anonymous function hoặc có thể là lambda hoặc là closure. Anonymous function được khởi tạo lúc chạy runtime, nó có thể gán cho 1 biến, gọi 1 hàm khác.

     var alphabets = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'];
     
     alphabets.forEach((String item) {
     	print('${alphabets.indexOf(i)item);
     });
     
     var sayHello = () => print('Hello guys');
     
     sayHello();

7. Null aware operation

  • ?: (Ternary Operator) - đa số ngôn ngữ đều hỗ trợ, thay thế cho if else.

     height < 175 ? 'Short' : 'Tall';

  • ?? - toán tử kiểm tra null, nếu biến trả về bằng null thì nó sẽ gán biến đó cho 1 giá trị mặc định.

     String name = user.name() ?? 'huy pham';

  • ??= - kiểm tra biến có null không, nếu biến null thì gán giá trị nếu không thì không thực thi.

     Alphabet alphabet;
     alphabet ??= Alphabet('alpha');

  • ?. - truy cập vào 1 method hoặc 1 object, nếu nó không null, ngược lại thì trả về null.

     Alphabet alphabet;
     // nếu aphabet có giá trị character thì gán cho biến character ngược lại quăng ra exception
     String character = alphabet?.character;
     
     // tốt hơn thì nên gán giá trị mặc định nếu giá trị trả về là null
     String character = alphabet?.character ?? 'alpha';

8. Classes

Dart là ngôn ngữ hướng đối tượng với các class và thừa kế dựa trên mixin (mixin-based inheritance). Mỗi object đều là thể hiện của 1 class và tất cả class đều xuất phát từ Object class. Thừa kế dựa trên mixin có nghĩa là mỗi class đều có 1 superclass (trừ Object class), phần bên trong của mỗi class đều có thể được tái sử dụng trong nhiều class được phân cấp.

  • Create a class

     class Vehicle {
     	int wheel;
     	int speed;
     }

  • Constructor

     class Vehicle {
     	int wheel;
     	int speed;
     }
     
     Vehicle(this.wheel; this.speed);

9. Object oriented programming

  • Encapsulation - tính bao đóng là đối tượng được bảo vệ không cho các truy cập từ code bên ngoài như thay đổi trạnng thái hay nhìn trực tiếp. Tính bao đóng của Dart được thể hiện bằng cách khai báo ( _ )trước biến hoặc hàm, phạm vi hoạt động của nó là trong library chứa nó và kể cả khi import thì bạn cũng không thể gọi trực tiếp được.

     int _x;
     
     void _init() {
     	// do something
     } 

  • Inheritance - tính kế thừa là khả năng cho ta xây dựng class dựa trên tính chất của class đã có. Đó là class Cha, các class Con phát sinh từ class Cha và đương nhiên nó cũng sẽ được thừa kế tính chất của Cha. Class Con có thể sử dụng các tính chất như định nghĩa ở class Cha thông qua super hoặc có thể định nghĩa lại.

     class Vehicle {
     	void countNumberOfWheels(int wheel) {
     		print('This vehicle has $wheel wheels');
     	}
     }
     
     class Bike extends Vehicle {
     	@override
     	void countNumberOfWheels(int wheel) {
     		super.countNumberOfWheels(wheels);
     	}
     }

  • Polymorphism - tính đa hình là các object trong cùng 1 họ tuân thủ theo 1 thể hiện nhưng có thể thực thi theo nhiều cách khác nhau.

    • Override - khi cùng 1 phương thức nhưng cách bên trong phương thức đó thực hiện ở từng nơi lại khác nhau. Ví dụ phương thức countNumberOfWheels ở ví dụ trên, xe máy sẽ trả về 2 nhưng xe ô tô lại trả về 4.
    • Overload - không được support trong Dart nhưng bạn có thể sử dụng optional parameter để thay thế, cùng 1 phương thức nhưng có tham số khai báo trong nó có thể được xài hay không được xài.
  • Abstraction - tính trừu tượng là sự tập trung vào các tính chất cối lõi nhất của 1 object, loại bỏ đi những tính chất rườm rà xung quanh.

     abstract class Vehicle {
     	void honk();
     	void speed();
     }
     
     // extend toàn bộ phương thức được khai báo trong Vehicle 
     // có thể thừa kế lại những tính chất đã định nghĩa trong class Vehicle thông qua super
     class Motobike extends Vehicle {
     	@override
     	void honk() {
     		super.honk();
     	}
     	
     	@override
     	void speed() {
     		print('Very fast');
     	}
     }

  • Interface - Dart không hổ trợ từ khóa interface để khai báo nhưng mỗi class đều định nghĩa là một interface.

     class Vehicle {
     	//phải định nghĩa phương thức đó để làm gì
     	void honk() => print('honk honk);
     	void speed() => print('322 km/h');
     }
     
     // implement toàn bộ phương thức được khai báo trong Vehicle 
     // nó sẽ không thừa kế lại từ Vehicle và bạn phải định nghĩa lại phương thức đó sẽ làm gì
     class Motobike implements Vehicle {
     	@override
     	void honk() {
     		print('beep beep');
     	}
     	
     	@override
     	void speed() {
     		print('Very fast');
     	}
     }

  • Mixins và mixin - đây là khái niệm khiến nhiều người khi tiếp xúc với Dart khá lúng túng. Vậy mixin là gì? Mixins là gì? và liệu một chiếc xe máy có thể chạy trên nước?

    • Mixins - cách để trừu tượng hóa và tái sử dụng code trong các class khác nhau.

     class Motobike {
     	bool isRunFast() {
     		return true;
     	}
     	
     	void show() {
     		print('This is a motobike');
     	}
     }
     
     class Plane {
     	bool isFlyHigh() {
     		return true;
     	}
     	
     	void show() {
     		print('This is a plane');
     	}
     }
     
     class Ship() {
     	bool isRunInWater() {
     		return true;
     	}
     	
     	void show() {
     		print('This is a ship');
     	}
     }
     
     mixin Mixin on Ship {
     	@override
     	bool isRunInWater() {
     		super.isRunInWater();
     	}
     	
     	bool isHaveSail() {
     		return true;
     	}
     }
     
     class Scooter extends Motobike with Plane, Ship {...}
     
     main() {
     	Scooter myScooter = Scooter();
     	myScooter.isRunFast(); // true
     	myScooter.isFlyHigh(); // true
     	myScooter.isFlyHigh(); // true
     	myScooter.show(); // This is a ship
     }

    • Như ví dụ trên, class Scooter có thể sử dụng các thương thức được khai báo ở các class Motobike, Plane, Ship mà không gặp phải một trở ngại gì. Đây chỉ là cách để tái sử dụng lại code trong Dart chứ không phải là đa thừa kế, đừng nhầm lẫn chỗ này.
    • Tương tự, khi gọi phương thức show() thì kết quả trả về là This is a ship, bởi vì khi extend qua các class như vậy thì các class đó sẽ được sếp vào 1 stack theo tuyến tính. Class Motobike được extend đầu tiên sẽ nằm trên đầu, sau đó lần lượt các class Plane và Ship sẽ xếp sau trong stack. Vì là last in, first out nên khi thực thi thì phương thức show() ở class Ship sẽ được thực thi.
    • mixin - khai báo việc có thể áp dụng mixin vào class. Class Mixin có thể extends hoặc implements các phương thức được khai báo ở class Ship(). Khi khởi tạo với từ khóa mixin thì trong class đó chỉ có khai báo các phương thức, bạn không thể khởi tạo constructor hoặc getter, setter. Đây giống như 1 chiếc thùng chứa tất cả các phương thức, khi nào bạn cần sử dụng phương thức nào thì extends hoặc implements đến nó và lấy ra sử dụng.

All right, hy vọng với những kiến thức cơ bản này thì mình mong bạn đã có thể tự tin tự mình tiếp tục "khám phá" Dart. Và những kiến thức nâng cao hơn như asynchronous, generic, socket hoặc BLoC pattern thì mình sẽ nói đến ở những bài viết theo, hy vọng các bạn tiếp tục ủng hộ :)

Flutter framework

Flutter là 1 mobile SDK do Google phát triển, nó giúp người dùng có thể tạo ra được 1 ứng dụng chạy trên cả iOS và Android. Là một Cross-flatform framework nhưng khác với các Cross-flatform hiện tai, Flutter không thông qua bridge, mà nó sẽ chạy engine render riêng (viết bằng C++) và dùng Flutter framework (viết bằng Dart) để giao tiếp với các service. Cả 2 bộ này sẽ được đóng gói cùng ứng dụng và thông qua SDK nó có thể chạy trên đa nền tảng. Kì vọng mà team phát triển Flutter nhắm đến là có thể chạy trên đa nền tảng, Flutter ngoài chạy trên mobile thì còn có thể chạy trên nền web thông qua dự án mang tên Hummingbird, chạy trên các thiết bị IoT và cả desktop. Tuy vậy, Flutter cũng còn rất "non trẻ", nó cần thêm thời gian để có thể phát triển hơn nữa và việc chọn Flutter để học cũng là 1 cách để đầu tư cho tương lai.

Dart la gi

Sau gần 1 năm với các phiên bản preview thì phiên bản stable 1.0 của nó được ra mắt vào sự kiện Flutter Live (4/12/2018) và phiên bản hiện tại lúc mình viết bài này là 1.2.2.

Để bắt đầu tìm hiểu thì chúng ta sẽ đi vào tìm hiểu về lifecycle cụ thể là stateful widget lifecycle và stateless widget lifecycle, đối với mình tìm hiểu về lifecycle là điều đầu tiên mình quan tâm khi tìm hiểu về framework nào đó. Bởi vì hiểu được lifecycle thì chúng ta sẽ dễ dàng tiếp cận và biết được nó sẽ hoạt động như thế nào. Ở đây mình sẽ viết theo những gì mình biết và mình hiểu nên có sai sót hoặc thiếu xót cần góp ý thì hãy comment phía dưới cho mình :)

1. Widget and Widget tree

  • Trong Flutter, mọi thứ đều là Widget. Widget là thứ mà bạn có thể nhìn thấy, có thể tương tác với ứng dụng của bạn.

  • Widget được tổ chức thành dạng cây, gồm có parent Widget và children Widgets.

     @override
     Widget build(BuildContext context) {
     return Scaffold(
       appBar: AppBar(
         title: Text('Demo'),
       ),
       body: Center(
         child: Column(
           children: <Widget>[
             Text('Hello guys'),
             Text('Welcome to my article'),
           ],
         ),
       ),
       floatingActionButton: FloatingActionButton(
         onPressed: _onPressed,
         child: Icon(Icons.add),
       ),
      );
    }

    • Như ví dụ trên, Scaffold là parent Widget chứa 3 children widgets là Center, Column và Text. Ta sẽ có được sơ đồ widget tree như sau:

    Ảnh của Didier Boelens

2. BuildContext

  • BuildContext là tham chiếu đến vị trí của mỗi widget trên widget tree. Mỗi widget có 1 BuildContext thể hiện. Nếu 1 widget có các children widgets thì BuildContext của widget đó cũng sẽ là parent BuildContext của các children BuildContexts chứa trong nó. Điều đó có nghĩa chúng ta sẽ có một BuildContext tree.

    Ảnh của Didier Boelens

3. StatelessWidget

  • Đây là những widget không quan tâm đến việc thay đổi, tạo ra 1 lần duy nhất hoặc hiểu cách đơn giản hơn thì đây là widget chỉ nhận khởi tạo có sẵn rồi thực thi nó và không có thay đổi State. Ví dụ như widget Text, Center, MaterialApp, ...

  • Cấu trúc của một StatelessWidget như sau:

     class MyWidget extends StatelessWidget {
       MyWidget({Key key, this.param,}) : super(key: key);
       String param;
       
       @override
       Widget build(BuildContext context) {
         return Container(
           child: Text('Hello guys'),
         );
       }
     }

    • Bạn có thể thấy, chúng ta có thể thêm vài tham số vào constructor. Tuy nhiên, hãy nhớ rằng những tham số này sẽ không thay đổi (immutable) ở những lần build sau.
    • Lifecycle của StatelessWidget gồm:
      • Khởi tạo
      • Render widget thông qua build()

4. StatefulWidget

  • Trái ngược với StatelessWidget thì StatefulWidget sẽ xử lí các dữ liệu bên trong nó, lắng nghe những thay đổi, dữ liệu này sẽ liên tục thay đổi (mutable) trong suốt lifecycle của widget.
  • Tập hợp những dữ liệu thay đổi được giữ bởi StatefulWidget, thay đổi trong suốt lifecycle của widget và dữ liệu có thể đọc 1 cách đồng bộ khi widget đã được build thì ta gọi đó là State. State định nghĩa ra phần hành vi mà StatefulWidget thể hiện. 1 State chứa các thông tin tương tác với widget thông qua các khía cạnh về hành vi (behavior) và layout. Mỗi khi thay đổi State thì widget đó sẽ thay đổi theo.
  • Giữa State và BuildContext có 1 mối quan hệ khá khăn khít. Mỗi khi 1 State được tạo ra thì nó sẽ gắn với 1 BuildContext và lúc này State là mounted.

5. StatefulWidget lifecycle

  • Lifecycle của StatefulWidget được thể hiện như sau:

    • createState()
    • mounted == true
    • initState()
    • didChangeDependencies()
    • build()
    • didUpdateWidget()
    • setState()
    • deactivate()
    • dispose()
    • mounted == false

     class MyWidget extends StatefulWidget {
       @override
       _MyWidgetState createState() => _MyWidgetState();
     }
    
     class _MyWidgetState extends State<MyWidget> {
     
       @override
       void initState() {
         super.initState();
       }
     
       @override
       void didChangeDependencies() {
         super.didChangeDependencies();
       }
     
       @override
       Widget build(BuildContext context) {
         return null;
       }
     
       @override
       void didUpdateWidget(TestMain oldWidget) {
         super.didUpdateWidget(oldWidget);
       }
     
       @override
       void setState(fn) {
         super.setState(fn);
       }
     
       @override
       void deactivate() {
         super.deactivate();
       }
     
       @override
       void dispose() {
         super.dispose();
       }
       
     }

    • createState() - khi tạo ra 1 StatefulWidget thì createState() sẽ được gọi để tạo ra State cho widget và thêm widget vào widget tree.
    • mounted == true - khi chúng ta đã tạo ra State thì 1 BuildContext sẽ được gán cho State.
    • initState() - đây là phương thức đầu tiên được gọi lúc ta khởi tạo widget. Nó được gọi 1 và chỉ 1 lần duy nhất và khi override lại thì cần phải gọi lại super.initState(). Chúng ta sử dụng phương thức này cho:
      • Khởi tạo dữ liệu cho 1 BuildContext cụ thể
      • Khởi tạo các thuộc tính cho parent widget
      • Khi chúng ta đăng kí 1 Stream, khởi tạo dữ liệu hoặc thay đổi dữ liệu của widget
    • didChangeDependencies() - đây là phương thức tiếp theo sẽ được gọi sau khi initState() được khởi tạo. Override phương thức này có nghĩa là widget đã được liên kết với 1 InheritedWidget hoặc bạn muốn State lắng nghe sự thay đổi của widget được thừa kế . Một khi đã liên kết widget với InheritedWidget thì mỗi khi widget rebuild thì phương thức này sẽ được gọi.
    • build() - đây là phương thức sẽ được gọi mỗi khi State thay đổi hoặc là khi InheritedWidget cần thông báo cho các widget đã đăng kí.
    • didUpdateWidget() - phương thức được gọi nếu parent widget thay đổi hoặc có widget cần rebuild, nhưng nó cần phải cùng runtimeType. Flutter có cơ chế tái sử dụng State trong thời gian dài, do đó, trong trường hợp này ta cần khởi tạo lại dữ liệu.
    • setState() - đây là phương thức mà bạn sẽ phải "vọc" khá nhiều khi làm việc với Flutter. Nó thông báo cho framework biết dữ liệu đã thay đổi và widget nên rebuild lại. setState() nhận 1 callback đồng bộ.
    • deactivate() - hiếm khi ta phải đụng đến phương thức này, bởi vì nó được gọi khi 1 widget bị xóa khỏi widget tree nhưng có thể thêm vào lại nếu frame vẽ lên widget đó chưa kết thúc. Phương thức này được tạo để phục vụ việc di chuyển State từ điểm này đến điểm khác trên widget tree.
    • dispose() - gọi khi State đã được xóa hoàn toàn.
    • mounted == false - khi này State không thể nào remount lại được nữa.

6. StatefulWidget or StatelessWidget?

  • Nếu widget của bạn sẽ phải thường xuyên thay đổi State và khi thay đổi thì widget của bạn sẽ được rebuild thì bạn nên sử dụng StatefulWidget. Còn nếu widget của bạn State từ lúc khởi tạo đến lúc kết thúc vẫn không có gì thay đổi thì bạn nên sử dụng StatelessWidget.
  • Ví dụ widget của bạn là 1 TextField, bạn cần phải biết được State lúc nào nó được nhập, lúc nào nó đã được nhập xong, State của nó thay đổi liên tục. Còn nếu widget của bạn chỉ là 1 Text hiển thị thông báo và nó không bao giờ thay đổi thì bạn nên sử dụng StatelessWidget.

7. Some common widgets

Ở bài viết này mình chỉ liệt kê và giới thiệu tên vào widget thường hay dùng nên sẽ không đi sâu vào các thuộc tính của nó. Sẽ có 1 bài viết hoặc nhiều bài viết khác mình giới thiệu về các widget trong Flutter. Thế giới widget trong Flutter rất đa dạng nên 1 bài viết chẳng thể nào nói hết được sự thú vị của nó. Đây là thứ hấp dẫn mình đến với Flutter, cảm giác khi làm việc với các widget y như khi bạn chơi với một bộ lego, bạn tha hồ lắp ghép và sáng tạo với nó.

  • Text - hiển thị nội dung bạn muốn thông báo và bạn có thể custom nó thông qua thuộc tính style với khá nhiều thứ thú vị.

     @override
       Widget build(BuildContext context) {
         return Text('Hello guys');
       }

  • TextField - giúp bạn có thể nhập nội dung từ bàn phím.

     @override
       Widget build(BuildContext context) {
         return TextField();
       }

  • RaisedButton - nút bấm giúp bạn có thể truy cập đến các phần khác nhau tùy theo ý muốn của bạn. Widget này bắt buộc bạn phải truyền vào 1 callback cho thuộc tính onPressed.

     @override
       Widget build(BuildContext context) {
         return RaisedButton(onPressed: _onPressed,);
       }

  • Scaffold - đây là 1 widget cung cấp cho bạn một giao diện hoàn chỉnh gồm phần appbar, body và 1 floatingActionButton chính hiệu Google.

     @override
       Widget build(BuildContext context) {
         return Scaffold();
       }

  • Container - widget thông dụng cung cấp 1 widget con, nó là wiget vừa có các thuộc tính để padding và có các thuộc tính để margin, width và height. Nếu sử dụng widget này bạn sẽ tốn khá nhiều thời gian để bạn ngồi canh chỉnh view sao cho đẹp nhất.

     @override
       Widget build(BuildContext context) {
         return Container(
           child: Text('Hi guys'),
         );
       }

  • Center - widget giúp bạn canh giữa cho widget con bên trong, lúc đầu mình cũng khá "bối rối" với widget này vì việc canh giữa bây giờ đã được nâng lên một tầm cao mới nhưng đến khi xài nó thì cảm thấy khá tiện lợi.

     @override
       Widget build(BuildContext context) {
         return Center(
           child: Text('Hi guys'),
         );
       }

  • Padding - widget đúng như tên của nó là padding widget con bên trong. Bạn bắt buộc phải khai báo thuộc tính padding của nó thông qua class EdgeInsetsGeometry.

     @override
       Widget build(BuildContext context) {
         return Padding(
           padding: EdgeInsets.all(69.96),
           child: Text('Hi guys'),
         );
       }

  • Expanded - widget giúp bạn bọc widget con bên nó và phân vùng hiển thị để không bị bể giao diện. Nó tương tự như layout_weight bên Android.

     @override
       Widget build(BuildContext context) {
         return Expanded(
           child: Text('Hi guys'),
         );
       }

  • Row và Column - đây là 2 widget giúp bạn hiển thị tập hợp nhiều widget sắp xếp theo hàng hoặc cột. Nó tương tự như thuộc tính orientation trong LinearLayout của Android.

     @override
       Widget build(BuildContext context) {
         return Row(
           children: <Widget>[
             Text('Hello guys'),
             Text('This is row'),
           ],
         );
     }

     @override
       Widget build(BuildContext context) {
         return Column(
           children: <Widget>[
             Text('Hello guys'),
             Text('This is column'),
           ],
         );
     }

Okay, mong rằng 1 vài hiểu biết cơ bản của mình về Flutter sẽ giúp bạn có hiểu được Flutter là gì và các khái niệm cơ bản khi bạn tiếp cận với Flutter.

Tham khảo

  • Widget — State — BuildContext — InheritedWidget
  • Dart: What are mixins?