[Chapter 1] - Ngụp lặn với Flutter (Dive to Flutter)
flutter
4
dart
5
White

Trần Đức Tâm viết ngày 05/03/2019

$ Flutter và Flutter Architecture Components

# [Chapter 1] - Ngụp lặn với Flutter (Dive to Flutter)

Sau sáu tháng cày cuốc tiếng Nhật, năm mới, thử thách mới lại quay lại với việc học một ngôn ngữ mới, một nền tảng mới. Nền tảng được lựa chọn lần này là Flutter - một nền tảng dạng Cross-platform application development frameworks (lưu ý đây không phải là Hybrid app) viết trên nền ngôn ngữ Dart. Lí do của lựa chọn này thực là lạ kì bởi một đêm nọ khi đang mơ tưởng đến người trong mộng thì bỗng một cuộc điện thoại gọi đến. Màn hình điện thoại hiển thị lên giao diện chính của Google Fuchsia - một hệ điều hành đứng đằng sau bởi Google phát triển nhằm thay thế Android. Trong thế giới trong mơ đó, Google Fuchsia thay thế hoàn toàn Android. Cách duy nhất để viết nên các ứng dụng mobile chỉ còn lại Rust, SwiftDart (on Flutter). Vậy là tỉnh giấc, toát mồ hôi hột, chẳng lẽ JavaKotlin mình đã học không còn dùng được nữa sao? Nếu không học Flutter từ bây giờ thì thật là đáng ngại.

Kì thực ra mình tiếp xúc với Dart từ năm 2015 khi vẫn còn làm thành viên của Google Developer Groups chapter Hanoi. Ngày đó, Google chính thức publish Dart rộng rãi với triết lí về một ngôn ngữ Component BaseComponent Based Language, mọi thứ đều được đóng gói thành các component. Khi ấy vì buộc phải học để mang đi chém gió cho đúng nhiệm vụ chứ ngôn ngữ này ngày đó đối với mình mà nói.

  • Một ngôn ngữ lằng nhằng đưa ra thêm một triết lý mới khó hiểu như Functional Programming.
  • Nhìn cái cách code mọi thứ lồng vào nhau thế kia thì merger Git mỗi lần conflict chỉ có thể là ngồi merger đến chết.
  • Với single executor, Dart gần như hoàn toàn coroutines (vì thực ra bạn vẫn tạo được thread nhưng quản lí nó thì tự đi mà làm). Cách thức hoạt động thì tương tự như Python hay Javascript nhưng cách sử dụng thì lại phải thêm một lần nữa học lại kĩ càng.
  • Cách thức code hoàn toàn không hợp mắt khi mà tất cả các ngôn ngữ khác mình học thì đều thêm chức năng theo chiều ngang tức là viết thêm code thì thêm một hàng. Còn với Dart thì mọi ngữ lại lồng vào nhau theo chiều dọc dạng Hadouken skill. Hadouken skill - Ảnh ăn cắp (Hadouken skill - Ảnh ăn cắp.)

Đại để là ngày đó mình cảm thấy rằng Dart sẽ chết nhanh thôi. Một dự án 20% với đầy nhưng khó khăn khi tiếp cận, tài liệu thì ít ỏi, những thứ nó mang lại thì không đáng để ngạc nhiên. Thế nhưng nó không chết, cùng với một dự án 20% khác là Google Fuchsia, Flutter ra đời hứa hẹn là một điểm tựa vững chắc cho sự sống còn của Dart.

TL;DR

  • Những khái niệm căn bản của Flutter/Dart để hiểu được rốt cục thì chúng là ai, từ đâu đến và đến để làm gì.
  • Kiến trúc cơ bản của Flutter và những đặc điểm cơ bản để hiểu được Flutter.

Bạn cần gì để đọc bài viết

  • Kiến thức cơ bản về Dart. Viết thử hello_world.dart để vượt qua được bước nhập môn.
  • Hiểu được từ khóa và quy tắc đặt tên trong Dart.

Từ chối trách nhiệm

  • Vì tại thời điểm viết bài, mình học Dart + Flutter tổng cộng được 3 tháng kết hợp với đào sâu vào đọc sách và tìm hiểu source của Flutter. Dẫn đến có nhiều điểm trong bài viết là ý hiểu của bản thân, chưa được dẫn nguồn. Cập nhật sau.
  • Tại thời điểm này, Dart 2.2 đã được release. Bài viết vẫn sử dụng code của Dart 2.1.
  • Thuật ngữ tiếng anh được giữ nguyên ở tiếng anh. Chỉ dịch qua tiếng Việt một lần trong dấu () khi xuất hiện lần đầu trong bài.

Chuỗi bài viết

  • [Chapter 1] - Ngụp lặn với Flutter (Dive to Flutter) ★ Đang được đọc
  • [Chapter 2] - Giới thiệu về Flutter Architecture Components.
  • [Chapter 3] - Những Widget quan trọng trong Flutter.
  • [Chapter 4] - Những thư viện hữu ích trong Flutter.

# Dart là gì?

Dart là ngôn ngữ lập trình dạng hướng đối tượng. Cũng là một ngôn ngữ viết ở một lần dùng ở nhiều nơi nhưng khác với Java, Dart thay vì tạo ra những môi trường trung gian giữa code thực thi và môi trường thiết bị thì Dart sử dụng những trình biên dịch khác nhau để biên dịch ra mã máy tương ứng. Hiện tại Dart đang hỗ trợ để tạo ra những ứng dụng trên iOS, Android, FuchsiaWeb. Riêng với Web Application, Dart biên dịch bản thân thành code Javascript (ở trường hợp này, Dart được coi như là một synxtax sugar cho Javascript)1. Để hiểu Component Oriented Programming mình đề cập đến ở đầu bài viết là gì, bạn cần code một vài lần kết hợp đọc các ví dụ mình để cập bên dưới để hiểu rõ hơn. Bài viết sẽ không lạm bàn về lĩnh vực này.

Dart đồng thời cũng là một ngôn ngữ hỗ trợ asynchrony. Tức nghĩa là trong Dart chỉ có một dòng chảy tuần tự chảy từ trên xuống dưới. Code được đưa vào hàng đợi và hoàn toàn có thứ tự khi được thực thi. Đọc thêm về Dart Language Asynchrony Support hoặc tóm tắt về Dart lang.

# Flutter là gì?

Flutter là một Framework viết trên nền ngôn ngữ Dart. Flutter được sinh ra như một Cross-platform framework nhưng khác với những Cross-platform hiện tại, Flutter viết mã và build ra các đoạn mã thực thi tương ứng trên các thiết bị khác nhau thay vì build ra thành các đoạn mã được tối ưu và chạy trên những môi trường trung gian.

# Flutter Framework Model cấu thành như thế nào?

Flutter được viết chia làm hai tầng. Tầng ở trên sử dụng ngôn ngữ Dart cung cấp các đoạn mã xây dựng lên một ứng dụng Flutter. Các đoạn mã này cung cấp phương tiện để có thể thay đổi và chỉnh sửa chúng. Từ đó giúp ứng dụng của lập trình viên có thể được tùy chỉnh theo mong muốn. Tầng Application này giúp lập trình viên thay đổi mã nguồn ứng dụng ở thời điểm compile time. Tầng thứ hai của Flutter nằm ở sâu bên dưới và được viết bằng C++. Tầng Shell này chứa các công tụ trợ giúp ứng dụng Flutter trong quá trình chạy. Nổi bật cần lưu ý ở tầng này là máy ảo Dart VM. Khái niệm máy ảo là khái niệm về một ứng dụng chạy song song với mã nguồn chính như một phần của ứng dụng. Máy ảo Dart VM có ba nhiệm vụ chính bao gồm.

  • Làm ứng dụng trung gian giữa mã nguồn được viết bởi Dart và thiết bị phần cứng (hoặc phần mềm nằm ngoài ứng dụng).
  • Thông dịch các đoạn mã Dart theo phương thức JIT (Just in time - mã nguồn chỉ được thông dịch khi được gọi đến) hoặc AOT (Ahead of Time - Mã)2
  • Thực thi các đoạn mã đã được thông dịch hoặc biên dịch cũng như cung cấp các runtime system bao gồm garbage collector, một vài các thư viện cần có của ngôn ngữ.

Máy ảo Dart VM chịu trách nhiệm lớn nhất trong việc quản lý các runtime system, hỗ trợ debugging hoặc hot reload cho các ứng dụng viết bằng Flutter. Đọc thêm về Dart VM tại đây.

Flutter Framework Architecture
(Flutter Framework Architecture)

# So sánh với các Cross-platform Framework khác.

Một ứng dụng để chạy được trên các thiết bị thông thường sẽ tạm quan tâm tới hai thành phần. Thành phần gần với lập trình viên hơn là lớp Application. Lớp Application chứa các đoạn mã được viết bởi lập trình viên và các công cụ giúp ứng dụng hoạt động và quản lý hoạt động của ứng dụng. Thành phần được quan tâm thứ hai là lớp Platform ở đây dùng để chỉ các hệ điều hành hoặc các ứng dụng trung gian giữa ứng dụng của lập trình viên và thiết bị. Trong phạm vi lập trình ứng dụng mobile, Lớp Platform được chia làm ba thành phần bao gồm.

  • Services: Bao gồm các ứng dụng được viết và cài đặt mặc định trong thiết bị có trách nhiệm quản lý giao tiếp giữa các ứng dụng trong thiết bị với nhau, cung cấp thông tin của thiết bị cho các ứng dụng hoặc thực thi các yêu cầu của ứng dụng để điều khiển thiết bị (Location, Camera, Sensors...).
  • Canvas/Events: Là các ứng dụng giúp hiển thị giao diện lên màn hình hoặc các thiết bị trình chiếu đồng thời nhận lại các sự kiện xuất hiện trên các giao diện này (ví dụ như tương tác của người dùng hoặc các sự kiện từ các animation...).
  • OEM Widgets: Thành phần phụ được cấu thành do sự thống nhất giữa các vendor (nhà cung cấp thiết bị (HTC), đơn vị phát hành thiết bị (NTT Docomo)) nhằm thống nhất một quy chuẩn chung cho các UI (thành phần giao diện) được sử dụng trong các Platform. Quy chuẩn được thống nhất này tạo ra một vài giới hạn cho việc viết lên UI sẽ được nói kĩ ở phần sau.

Thành phần sẽ được so sánh trong phạm vi bài viết này là kiến trúc của lớp Application cũng như cách mà lớp Application giao tiếp với lớp Platform. Giống như đã đề cập ở phần trước. Tầng Application của Flutter cung cấp kiến trúc cho một ứng dụng viết bằng Flutter và tầng Shell cung cấp khả năng giao tiếp giữa ứng dụng viết bằng Flutter và lớp Platform. Lấy ví dụ về một ứng dụng được viết bằng đoạn mã Native. Toàn bộ mã thực thi được viết ở dạng Native sẽ được biên dịch hoặc thông dịch qua dạng mà tầng Platform có thể hiểu được và thực thi trên đó.

Đối với ứng dụng viết ở dạng Hybrid, các đoạn mã giao diện của ứng dụng được viết bằng HTML/CSS và được vẽ ra với DOM qua dạng hiển thị được trên WebView. WebView sẽ trực tiếp điều khiển Canvas vẽ lên UI và nhận các event ngược lại thông qua WebView đó. Mã thực thi của các ứng dụng viết dựa trên Hybrid được sử dụng dưới dạng Javascript. JavaScript có thể dễ dàng điều khiển HTML/CSS thông qua giao tiếp với WebView tuy nhiên nhằm mục đích bảo mật, WebView không được cung cấp khả năng giao tiếp với các services hoặc điều khiển phần cứng của lớp Platform dẫn đến các ứng dụng viết dựa trên Hybrid cần một module trung gian thường gọi là Bridge để làm điều này. Module này phải đọc hiểu Javascript và tồn tại luôn trong mã nguồn của ứng dụng dẫn đến kích thước của ứng dụng bị tăng lên đáng kể3.

Đối với các ứng dụng được viết ở dạng WebNative hoặc Cross compiled, sẽ không có phần WebView được sử dụng mà thay vào đó module Bridge sẽ thực hiện toàn bộ các hành vi trung gian giữa lớp Application và lớp Shell. Mã nguồn của ứng dụng cũng không bị giới hạn bởi Javascript nữa mà tùy thuộc vào nhà phát triển các nền tảng dạng này. Có thể là Native Code có thể là Non-native code. Tùy họ.

Đối với Flutter, mã nguồn được cross compile thành native code. Dựa vào máy ảo Dart VM, những thành phần ít thay đổi sẽ được compile dạng AOT còn những thành phần thường xuyên thay đổi sẽ được compile thành dạng JIT. Flutter không cam kết với các OEM Widgets. Flutter cho phép lập trình viên thay đổi và điều khiển từng pixel trên màn hình và không cần quan tâm đến những giới hạn của OEM Widgets. Tuy nhiên khác với các ứng dụng viết ở dạng Hybrid khi lập trình viên phải tự quan tâm đến các chuẩn của OEM Widgets, Flutter cũng cung cấp các Widget mặc định hỗ trợ hai nền tảng giao diện là Material Design (cho Android/Fuchsia) và Cupertino (cho iOS). Bạn cũng có thể quay trờ về với Flat Design thông qua một số thư viện trôi nổi trên thị trường. Sự so sánh kiến trúc của các nền tảng này được vẽ trong biểu đồ bên dưới.

alt text
(So sánh các Cross-platform Framework) - Bản đẹp nhưng là hàng ăn cắp ở đây.

# Trong Flutter thì mọi thứ đều là Widget.

Trong Flutter, mọi thứ đều là Widgets vậy Widget là gì? Chúng ta xét qua thử một đoạn code mẫu bên dưới.

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();
...
}

abstract class Element extends DiagnosticableTree implements BuildContext {
  Element(Widget widget) : assert(widget != null), _widget = widget;

  Element _parent;

  @override
  Widget get widget => _widget;
  Widget _widget;

  bool get dirty => _dirty;
  bool _dirty = true;

  @protected
  void performRebuild();
...
}

Đây là hai class quan trọng nhất trong Flutter. Theo đó, class Widget chịu trách nhiệm hình thành cấu trúc của Diagnosticable Tree4 (Cây chuẩn đoán là một cấu trúc dữ liệu dạng cây hỗ trợ việc định nghĩa cấu trúc giao diện được vẽ lên trong ứng dụng viết bằng Flutter) trong khi class Element chịu trách nhiệm quản lý trạng thái của từng Widget trên cái cây đó. Tư tưởng cơ bản của Flutter tương tự như một Web Application. Tất cả trong một. Flutter căng ra một mảnh vải là canvas và vẽ tất cả mọi thứ lên đó. Khi trạng thái của dữ liệu thay đổi ví dụ như người dùng chuyển màn hình hay thay đổi dữ liệu trên màn hình được phản ánh thông qua việc xóa đi các Widget cũ và vẽ lên các Widget mới. Chính vì vậy thay vì phải biết đến các StoryBoard (trong iOS) hay các Activity (trong Android), Flutter đưa tất cả các khái niệm liên quan đến giao diện về một khái niệm duy nhất gọi là Widget. Việc quản lý trạng thái của các Widget cũng được đưa về cho lập trình viên thực hiện. Điểm khác biệt này cũng đặc biệt hơn khi Dart là ngôn ngữ Component Based. Trong Flutter, khi muốn điều chỉnh trạng thái về kích thước hay vị trí của một Widget nào đó, ta không thực hiện việc đó trong Widget hiện tại mà được khuyến khích bọc Widget đó trong một Widget nào đó chỉ chuyên làm việc này. Điều này khiến cho tính chất "Trong Flutter thì mọi thứ đều là Widget" lại càng chuẩn hơn bao giờ hết. Màn hình là Widget, thành phần giao diện cũng là Widget, thậm chí cả thông tin về layout cũng là Widget...

Container(
  height: 200.0,
  width: 200.0,
  child: CustomWidget(
    child: Padding(padding: const EdgeInsets.all(0), child: child),
  ),
);

Một điểm nữa cần lưu tâm cũng là đặc tính của Widget trong Flutter đó là thuộc tính Widget#key. Mọi Widget đều độc lập. Lí do là vì Widget không thể tự vẽ lại bản thân. Để thực hiện điều này ta cần thông báo cho Element. Cách dễ nhất để có thể dễ dàng thông báo cho Element là làm bẩn (dirty) instance tương ứng dẫn đến Widget Tree được vẽ lại. Widget Tree được vẽ lại thì instance của Widget cũ sẽ không còn. Trong một vài trường hợp cần phải tìm lại giá trị của Widget tại vị trí được vẽ lại, ta sử dụng key làm chìa khóa cho vấn đề này.

Kiến trúc về Widget của Flutter
(Kiến trúc về Widget của Flutter)

# StatefulWidgetStatelessWidget trong Flutter.

Khi thực hiện vẽ Widget Tree. Luôn có một trong hai trường hợp xảy ra đối với các lá trong cây. Trường hợp thứ nhất là lá không bao giờ tự bản thân chúng thay đổi. Các Widget tĩnh này có thể là các nhãn cố định trên màn hình hoặc các nút bấm. Trường hợp thứ hai là các Widget động có thể tự bản thân sẽ thay đổi trong quá trình xuất hiện trên Widget Tree. Tương ứng với hai loại Widget này, Flutter giới thiệu hai abstract sub-class của WidgetStatefulWidgetStatelessWidget.

# StatefulWidget là gì?

Đi qua một ví dụ về StatefulWidget để dễ hình dung.

class ExampleStatefulWidget extends StatefulWidget {
  @override
  State<ExampleStatefulWidget> createState() {
    return ExampleStatefulWidgetState();
  }
}

class ExampleStatefulWidgetState extends State<ExampleStatefulWidget> {
  @override
  bool get mounted => super.mounted;

  @override
  void initState() => {};

  @override
  void dispose() => {};

  @override
  void setState(fn) => {};

  @override
  void didUpdateWidget(ExampleStatefulWidget oldWidget) => {};

  @override
  Widget build(BuildContext context) => null;
}

Khi tạo mới một StatefulWidget, ta quan tâm đến hai class là StatefulWidgetState của Widget đó. StatefulWidget là phần không thay đổi trong Widget Tree, thay vào đó, State là phần được thay đổi. Mỗi khi Widget bị làm bẩn, state của Widget đó sẽ được khởi tạo lại thông qua việc gọi đến phương thức StatefulWidget#createState(). Generic type5 (kiểu giữ chỗ) của State định nghĩa ra kiểu dữ liệu của thuộc tính State#widget chứa trong nó. Thuộc tính này chính là instance của StatefulWidget đã tạo ra State đó. Chính nhờ vậy, từ trong State hoàn toàn có thể lấy được những giá trị được truyền vào bên trong StatefulWidget thay vì phải truyền vào thông qua hàm khởi tạo.

/// Không nên
class ExampleStatefulWidget extends StatefulWidget {
  final String text;
  ExampleStatefulWidget(this.text);

  @override
  State<ExampleStatefulWidget> createState() {
    return ExampleStatefulWidgetState(text);
  }
}

class ExampleStatefulWidgetState extends State<ExampleStatefulWidget> {
  final String text;
  ExampleStatefulWidget(this.text);

  @override
  Widget build(BuildContext context) => null;
}

/// Nên
class ExampleStatefulWidget extends StatefulWidget {
  final String text;
  ExampleStatefulWidget(this.text);

  @override
  State<ExampleStatefulWidget> createState() {
    return ExampleStatefulWidgetState();
  }
}

class ExampleStatefulWidgetState extends State<ExampleStatefulWidget> {
  // widget.text; Sử dụng biến ở đâu đó.

  @override
  Widget build(BuildContext context) => null;
}

ExampleStatefulWidget class nằm trong Widget Tree và ít thay đổi. Thay vì tạo một class dễ thay đổi trong cây, Flutter cung cấp cho Widget này một thành phần gọi là State. State sẽ được quản lý với vòng đời như bên dưới.

Vòng đời của StatefulWidget
(Vòng đời của StatefulWidget)

Trong State chứa một thể hiện của Element tương ứng trong cây thông qua State#_element. Khi cần vẽ lại, có hai sự kiện được sử dụng để làm bẩn ElementdidUpdateWidget()setState(). Ở hàm thứ nhất, khi configuration bị thay đổi, Widget sẽ được thông báo về sự thay đổi này. Lập trình viên @override lại phương thức didUpdateWidget() để bắt sự kiện trước khi Flutter build lại. Còn để thực hiện yêu cầu Widget thay đổi State, lập trình viên sử dụng phương thức setState(). Trong phương thức setState()có cho phép truyền vào một hàm. Mục đích của việc này là đôi khi công việc build lại Widget sẽ tốn một khoảng thời gian trước khi mọi thứ hoàn thành. Hàm callback được truyền vào sẽ đảm bảo Flutter thực thi được trọn vẹn công việc của mình. Ví dụ sau khi thay đổi kích thước của một list view mà muốn scroll xuống cuối list.

Thêm một lưu ý quan trọng nữa là chỉ có thể gọi đến setState() sau khi Widget đã được mount.

# StatelessWidget là gì?

Bên cạnh khái niệm về StatefulWidget ta cũng có StatelessWidget. Cách thức để khai báo thì đơn giản hơn rất nhiều và cũng vô cùng nhạt nhẽo. Đại để là cái gì mà đưa nó thành StatelessWidget được thì nên để nó là StatelessWidget.

class ExampleStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) => null;
}

Vòng đời của StatelessWidget
(Vòng đời của StatelessWidget)

# Phương thức render của Flutter.

Flutter sử dụng Diff Algorithm để xác định sự thay đổi trong Widget Tree. Nhờ vậy, Flutter biết được nên build chỉ một phần của Widget Tree giúp cho việc vẽ lại giao diện được nhanh hơn.

Phương thức render
(Phương thức render)

Tiếp theo:

  • [Chapter 2] - Giới thiệu về Flutter Architecture Components.

Thuật ngữ và từ viết tắt.

Tham khảo.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Trần Đức Tâm

6 bài viết.
135 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
57 9
Phần I.I Machine Learning là gì? TL;DR Mình nghiên cứu Machine Learning(Link) bắt đầu từ khoảng một năm về trước, mất khoảng sáu tháng để tự luyệ...
Trần Đức Tâm viết 2 năm trước
57 9
White
38 13
Phần I.2 Tensorflow và bài toán Hồi quy đơn giản đầu tiên TL;DR Qua bài viết trước, chúng ta đã biết được đến sự tồn tại của một vài khái niệm cơ ...
Trần Đức Tâm viết 2 năm trước
38 13
White
13 13
Observer Pattern (Nội dung bài viết hoàn toàn hư cấu nhưng không nhất thiết phải sai sự thật) (Bài viết không hẳn là Today I learned nhưng vì khô...
Trần Đức Tâm viết 1 năm trước
13 13
Bài viết liên quan
White
1 0
Control flow statements Anh em có thể tham khảo tại (Link). Vì phần này khá cơ bản và giống Java, không có gì đặc biệt nên mình sẽ bỏ qua. Throw...
ngohado viết hơn 1 năm trước
1 0
White
4 0
Functions as firstclass objects Bạn có thể truyền 1 function như 1 parameter của 1 function khác: void printElement(int element) { print(elemen...
ngohado viết hơn 1 năm trước
4 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
6 bài viết.
135 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!