Flutter

Flutter, Bài 1: Giới thiệu về Dart

Nếu bạn chọn đi theo Flutter, trước tiên bạn phải có kiến thức về một ngôn ngữ lập trình mới toanh có tên là Dart. Bài hướng dẫn này không giới thiệu hoàn toàn về Dart, bởi ngôn ngữ này cũng có nhiều concepts và syntax như các ngôn ngữ lập trình khác, như Java và C++ hay Swift, và nó gần gũi hơn với JavaScript. Do đó, bài này chỉ đưa ra những khác biệt bạn cần nắm mà thôi. Ngoài ra, bạn cũng có thể tham khảo thêm trong Dart tour tại https://www.dartlang.org/guides/language/language-tour.

1. Các concepts và syntax tương tự các ngôn ngữ khác:

Dart hỗ trợ các concepts và syntax thường thấy ở các ngôn ngữ khác như for, while, if/else, switch/case, member, method, variable, classs, các class về String, int, double, bool (cho boolean), List (Array), Map và việc khai báo cũng hoàn toàn tương tự. Ở đây tôi chỉ giới thiệu một số điểm mà bạn cần lưu ý:

Class intdouble là hai class mặc định được extends từ class num tức Number. Một Number var là int khi nó không có dấu chấm thập phân, còn double thì có.

Khi khai báo một biến, bạn có thể lựa chọn một trong hai cách: Gán trực tiếp kiểu biến, tức class của nó trước tên của nó; hoặc dùng các từ khóa var, const tương tự như JavaScript hay final tương tự như Java. Cách khai báo biến giống với Java, JavaScript và C/C++ thay vì Kotlin hay Swift.

Khi khai báo một biến mà không gán giá trị, nó sẽ có giá trị mặc định là null, kể cả đó là int hay double. Tất cả các members trong Dart đều là Object cả, và mọi class đều được trực tiếp hay gián tiếp extends Object tương tự như Java. Tuy nhiên, khi khai báo một member hay method có kiểu dữ liệu có thể thuộc nhiều class khác nhau, với Java bạn thường chọn kiểu Object nếu các class của các “không ăn nhập gì nhau”, còn đối với Dart, dù bạn vẫn có thể khai báo kiểu Object nhưng sẽ tốt hơn nếu bạn dùng dynamic.

String của Dart hỗ trợ expression. Và bạn dùng dấu nháy, đơn như () hay kép như (), thay vì dùng backtick (`) như JavaScript ES6, kèm dấu ${} đi với tên biến. Đối với việc bạn sử dụng trực tiếp tên biến thì bạn không cần cặp ngoặc nhọn, và cũng không nên. Dart String cũng hỗ trợ nối như các ngôn ngữ khác. Để định nghĩa một String sẽ cần nhiều dòng, bạn chỉ cần dùng ba dấu nháy đơn, hoặc một đơn một kép thay cho một nháy đơn hoặc một nháy kép khi viết String một dòng, mà không cần nhiều dấu cộng như Java hay JavaScript. Đối với các kí tự Unicode, tức Dart Runes, bạn cần đặt nó trong cặp ngoặc nhọn, ví dụ (?) là ‘\u{1f600}’.

List<E> trong Dart tương tự với List trong Swift hơn và bạn có thể khai báo trực tiếp như Array trong JavaScript hay Swift. Khác với Array trong Java, bạn khai báo trực tiếp với dấu ngoặc vuông luôn, và thoải mái thêm thắt hay loại bớt hay thay đổi bất kì phần tử nào thay vì phải dùng ArrayList<E> và add(E) vốn gây nên sự phức tạp không đáng có. Mặc định, với Dart List, bạn không khai báo thêm kiểu <E> thì nó sẽ nhận vào <int>, để khai báo EString hay bất kì kiểu nào khác, bạn phải định nghĩa cụ thể, như List<String> cho dù trong cặp ngoặc vuông bạn có chỉ gồm các phần tử String hay không.

Kiểu Map<K,V> vừa có thể định nghĩa từng phần tử ngay như trong JavaScript, hoặc có thể khai báo trước rồi định nghĩa từng phần tử như trong Java. Nếu bạn tìm tới value V của một key K chưa có trong một map, nó đơn giản chỉ return null như trong Java.

Các hàm, tức Functions, trong Dart cũng được định nghĩa tương tự như trong Java cùng kiểu dữ liệu sẽ trả về. Dart cũng hỗ trợ void, và ngoài void, bạn phải return một function. Nhưng nếu bạn không cho return trong một Object func() thì mặc định nó sẽ return null.

Nếu một Function chỉ chứa đúng một dòng body, thì bạn có thể return nó với fat arrow =>. Còn với lambda syntax (hay closure trong Swift), bạn chỉ dùng cặp ngoặc nhọn ngay sau cặp ngoặc tròn mà thôi.

Dart không hỗ trợ public và private. Nếu bạn muốn giới hạn scope của một member hay function thành mức package-private tương ứng Java, bạn chỉ cần thêm kí tự gạch chìm, hay underscore trước tên biến hoặc hàm. Vd _sayHello() là package-private, còn sayHello() là public.

Cũng như các ngôn ngữ khác, functions trong Dart cũng chấp nhận các tham số. Một điểm tương đồng với Kotlin là bạn không cần thiết phải truyền đúng thứ tự biến vào, mà có thể dùng tên biến đi kèm với giá trị tương ứng của nó. Tuy nhiên, để “thi triển” được “tuyệt chiêu” này thì bạn lại phải đặt các tham số ở hàm gốc trong cặp ngoặc nhọn. Nếu không có ngoặc nhọn, thứ tự các tham số phải đúng thứ tự khi gọi hàm. Ngoài ra, chúng ta cũng có những optional params, tức những tham số có cũng được, không có cũng không sao, chúng sẽ được đặt trong cặp ngoặc vuông, và cuối hàm như … trong Java vậy.Ngoài ra, một điểm trừ lớn là việc bạn chỉ có một tên function duy nhất trong class. Ví dụ như với Android TextView, ta có hai hàm setText là setText(CharSequence) và setText(int), hoặc với Activity thì ta có startActivity(Intent) và startActivity(Intent, Bundle) còn với Dart thì không được, rất nhọ.

Để giải quyết một phần vấn đề, tức cái startActivity bên trên thì Dart cung cấp giải phát default param, tức tham số mặc định. Nhưng hướng giải quyết này cũng tương đối “nửa mùa” khi các default param phải là công xăng, hoặc một new instance cố định.

Và cuối cùng, Dart cũng có hàm main() như Java hay C++, và bạn lưu ý chỗ này: Ngay khi viết ứng dụng Flutter, bạn vẫn cần hàm main() này, khác với Android thì main() được Zygote định nghĩa sẵn. Về lí do tại sao bạn phải có hàm main() trong Flutter, tôi sẽ giải thích khi bước vào phần Flutter. Yêu cầu là main() phải nằm trong tập tin main.dart. Extension của dart src file là dart tương tự như Java và C++ và Swift hay JS.

Dưới đây sẽ là ví dụ tổng hợp:

Một điểm tương đồng của Dart với JavaScript là bạn được định nghĩa một function trong lòng một function. Ngoài ra, chúng ta còn có Closure tương đối khá giống với cách mà chúng ta truyền function làm tham số trong JavaScript, ví dụ:

Có lẽ điều đáng thất vọng nhất ở Dart là việc vẫn cần dấu chấm phẩy ; ở cuối mỗi câu lệnh. Hiện JavaScript đã không còn cần dấu ; nữa rồi, nhưng Dart thì không có là không được. Hi vọng phiên bản Dart3 sẽ bỏ đi yêu cầu này.

2. Constructor:

Cũng như các ngôn ngữ khác, các class đối tượng trong Dart cần có constructor để khởi tạo và “thoát null”. Việc tạo constructor không khác so với các ngôn ngữ phổ biến khác:

Ngoài ra, mỗi class cũng có thể có nhiều constructors khác nhau, và tương tự như Java, để constructor này gọi constructor khác thì bạn chỉ cần gọi thông qua this:

Bạn để ý constructor đầu, tên field members và tên params là giống nhau, vì vậy, để đơn giản hóa thì Dart cung cấp một sugar syntax để dễ dàng khai báo, tương tự như Kotlin. Đồng thời, một điểm khá hay của Dart là việc bạn có thể đặt tên constructor. Việc gọi constructor để gán giá trị cho biến có từ khóa new như trong Java, nhưng sang Dart2 thì không cần new nữa – có cũng được, không có cũng không sao:

Cũng như Java hay C++, bạn có thể extends một class ra một sub class, chẳng hạn như tạo class Student extends Person như bên dưới. Tuy nhiên, bạn cần lưu ý là khác với Java, Dart không mặc định cho kế thừa constructor mà bạn phải tự chỉ định lại. Để gọi constructor của class gốc, bạn cũng dùng từ khóa super như Java:

3. Typecast

Tương tự như Java hay các ngôn ngữ khác, việc cần typecast là điều không thể tránh khỏi, dù thực tế với Flutter việc này không diễn ra thường xuyên như Java hay Swift vì bạn không thao tác trực tiếp với các Widget. Đầu tiên, để kiểm tra một instance có phải là đối tượng của một class hay không, bạn dùng từ khóa is – như Kotlin:

Để typecast một Object về đúng kiểu class của nó, bạn dùng từ khóa as như với Kotlin:

4. Gán giá trị khi và chỉ khi object đang null

Dart có một điểm rất hay là bạn có thể dễ dàng và gọn gàng trong việc gán giá trị cho một object khi và chỉ khi nó đang null, còn nếu nó đã thoát kiếp null rồi thì thao tác sẽ không được thực thi, với ??=. Chẳng hạn:

Thay vì thao tác if (i != null) i = giGiDo như với Java hay C++.

5. Gọi method liên hoàn trên object

Khi code Android, bạn đã bao giờ chịu cảnh:

Do mỗi method setText, setTextColor, v.v… đều return void, nên mỗi lần gọi method khác là mỗi lần bạn phải “lôi” cái TextView text ra, khá phiền phức. Hiện Google đã có các Kotlin extension giúp bạn dễ thở hơn với Kotlin#apply { … }, nhưng với Dart, nó đã có cú pháp giúp bạn từ lâu lắm rồi. Chẳng hạn:

Lưu ý: Cú pháp dấu .. sẽ tính object được gọi là object ngay phía trước dấu .. đầu tiên. Do đó, object ở đây phải được đặt trực tiếp trước dấu .. đầu tiên. Nếu bạn “quên” mà chỉ gọi một dấu . thì sẽ fail ngay, ví dụ như bên dưới, vì Dart compiler sẽ tính object chính là printName và vì printName returns void nên bạn toi chắc:

6. Conditional statement và safe null:

Conditional statement trong Dart tương tự như Java và C++ và không “vớ vẩn” và phiền phức như Kotlin:

Dart cũng hỗ trợ tránh null với ?. như Swift và Kotlin:

Tới đây, tôi tạm kết thúc bài đầu tiên về Dart. Sang bài sau, tôi sẽ giới thiệu những vấn đề khác phức tạp hơn. Ngay bây giờ, bạn có thể thử viết vài dòng Dart code và cho chạy thử tại DartPad: https://dartpad.dartlang.org/.

1 thought on “Flutter, Bài 1: Giới thiệu về Dart”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.