Flutter

Flutter, Bài 11: Di chuyển giữa các màn hình, và Dialog

Hiện tại, ít có ứng dụng di động nào chỉ có một “màn hình” duy nhất. Đặc biệt là các ứng dụng có bố cục “mặt tiền” là một danh sách, thì khi người dùng nhấn vào một mục trên danh sách sẽ được chuyển sang màn hình khác có nội dung tương ứng. Flutter cũng không là ngoại lệ, và nó cũng có routes như React Native. Bài hướng dẫn này sẽ giúp các bạn di chuyển và gửi dữ liệu từ màn hình trước cho màn hình sau, cũng như màn hình sau về màn hình trước.

Flutter

1. Di chuyển sang màn hình mới

Đối với các bạn đã có kinh nghiệm với React Native, tôi xin nhắc lại câu nói thần thánh “Flutter Widget tương ứng với React.Component”, và sắp tới, chúng ta sẽ còn gặp tới người anh em cùng cha khác ông nội của (trước đây) ReactNative Navigation là Flutter Navigator nữa!!! Còn đối với các bạn đã và đang tập trung toàn lực vào Android hay iOS Sdk, chúng ta không hẳn là tạo một Activity hay ViewController mới, mà thay vào đó, là Flutter Widget, mà đúng hơn là một trong hai loại: StatelessWidget và StatefulWidget. Nếu bạn vẫn còn đang vấp phải một số viên đá nhỏ về chúng, hãy đọc lại Bài 7. Và cũng xin nhấn mạnh với các bạn, là chúng ta không di chuyển hẳn sang một Activity hay ViewController mới, mà chỉ là thêm một lớp Widget lớn nằm chồng lên và chiếm focus tạo cảm giác nhảy sang “màn hình” mới.

Bây giờ, chúng ta sẽ cần quay lại ứng dụng “cho sẵn” của Flutter, tức là các dòng code được tạo tự động khi bạn tạo một Flutter Project. Và bạn nên sử dụng Android Studio dù bạn đang có ý định lập trình cho cả iOS trên máy Mac. Nếu bạn đã vọc vạch “tan nát” project trong bài cũ, bạn nên tạo một project mới nếu muốn “tươi mới”. Không thì bạn cũng chỉ cần tạo một StatelessWidget hoặc StatefulWidget mới mà thôi. Dưới đây là code của project “sạch”:

Bây giờ, chúng ta sẽ tiến hành thay đổi nó một chút. Đầu tiên, bạn tạo cho tôi một method có tên là goToNext chứa hai tham số BuildContext context và String what. Tạm thời bạn để phần thân hàm rỗng. Đồng thời bạn thêm actions vào Scaffold#appBar như sau:

Xuống bên dưới, ngoài cả class _MyHomePageState, bạn tạo cho tôi một class có tên là MyNextPage có nhiệm vụ hiển thị nội dung được gửi từ MyHomePage. Vì vậy, phần code của nó chỉ đơn giản như sau:

Quay trở lại MyHomePage#goToNext, phần bo-đì của nó có tác dụng điều hướng từ MyHomePage sang MyNextPage. Đi kèm với nó là phần tham số String what để hiển thị sang màn hình bên kia. Trong Android, bạn thường gọi Context#startActivity(Intent), với iOS là ViewController#present(_:animated:completion:). Còn Flutter, bạn sẽ “thuê” một tên Navigator để “navigate”.

Chú ý 1: Với Android, việc tạo constructor có chứa tham số cho Activity, và tương tự với iOS ViewController, là điều hoàn toàn “cấm kị”, thì với Flutter, đây là con đường dễ nhất để truyền tham số sang màn hình sau. Sở dĩ có thể thực hiện, và phải nên thực hiện, điều này là vì, như tôi luôn nhắc tới nhắc lui với các bạn, là Flutter app không hoạt động theo kiểu Android Activity hay iOS ViewController mà thay vào đó, khi bạn Navigator.push, thì màn hình mới – thực chất là Widget – sẽ nằm chồng lên Widget gọi tới nó và chiếm focus, tạo cảm giác startActivity hay present. Với Android, bạn phải gửi gián tham số gián tiếp qua Intent, với iOS là segue, còn với Flutter chỉ đơn giản là dùng luôn constructor.

Chú ý 2: Ở đây tôi chỉ sử dụng trường hợp đơn giản nhất là chỉ gửi một tham số. Nếu bạn muốn gửi nhiều tham số cùng lúc, bạn có thể đặt thêm biến trong constructor của Widget nhận tham số – tức Widget sẽ xuất hiện. Tuy nhiên, vì Navigator.pop(BuildContext, T result) – bạn sắp gặp tới nó ở phần ngay bên dưới – chỉ chứa một result duy nhất, nên tốt nhất, nếu muốn truyền nhiều tham số một lúc, bạn nên đặt chúng trong một Map<K,V> duy nhất và là tham số duy nhất trong constructor của các màn hình.

Chú ý 3: Bản thân Navigator.push là một Future. Trong trường hợp các bạn quên, thì Future<T> sẽ trả về một T trong result nếu bạn sử dụng nó với .then((T result) => …) hoặc trực tiếp T nếu sử dụng với await. Do đó, nó có tác dụng bắt kết quả được trả về từ màn hình được trỏ tới.

2. Quay về và trả kết quả về màn hình trước

Điều này tương tự như Activity#setResult và Activity#onActivityResult của Android hay sử dụng segue để bắt kết quả trên iOS. Nhưng với Flutter thì mọi thứ có vẻ đơn giản hơn một chút với Navigator.pop(BuildContext, [T]), trong đó đối số thứ hai là kết quả. Nếu bạn không truyền đối số này vào thì xem như nó null, và bạn không trả kết quả gì về màn hình trước. Chẳng hạn:

Để màn hình ban đầu nhận kết quả, bạn sẽ dùng tính chất của FutureOr<T> Navigator.push đã được trình bày trong Chú ý 3 bên trên. Do đó, bạn có thể hiệu chỉnh goToNext như sau:

Hay đối với bạn nào thích dùng .then:

Để thử nghiệm, bạn hãy tạo một Button trong _MyNextPageState rồi dùng backToHome gán vào onPressed của nó.

Chú ý 1: Trong AppBar, nếu route của Widget hiện tại (xin xem về routes bên dưới) không phải là ‘home’, tự nó sẽ hiện một leading mặc định là một IconButton chứa Icon là Icons.arrow_back (ic_back trong bộ Material Design Icon của Google) và khi nhấn vào, nó sẽ gọi Navigator.pop(BuildContext), tức không có result. Vì vậy, để “dán” result vào nó để bạn không cần làm một Button riêng rẽ, bạn buộc phải định nghĩa lại leading của AppBar. Ví dụ:

Chú ý 2: Vì Navigator.pop(BuildContext, [T]) chỉ chứa một đối số result duy nhất, nên nếu bạn có nhiều result khác nhau thì nên đặt chúng vào một Map<K,V>.

3. Routes:

Tương tự như React Native, Flutter cũng hỗ trợ Routes. Đối với các bạn chưa có kinh nghiệm về React Native Routes, thì hướng tư duy ở đây tương đối giống với làm Web, tức là dùng link href để trỏ tới trang nào đó. Với React, Angular và Vue thì việc dùng Routes để di chuyển giữa các trang, hay đúng hơn là các Component. Với React Native và Flutter, bạn dùng Routes để nhảy tới màn hình khác (React.Component trong React hay Flutter Widget) cho nhanh. Tuy nhiên, cách làm này đôi khi đi kèm với hiểm nguy mà các bạn vốn có tính bất cẩn, hoặc học qua nhanh chỉ để lấy tiếng là có biết qua nhiều thứ, hoặc các bạn có thói quen gặp lỗi lớn nhỏ gì cũng đi hỏi sẽ gặp hoang mang. Vì vậy, tôi chỉ giới thiệu ngắn gọn phần này mà thôi.

Route trong tiếng Anh có nghĩa là các nhánh đường. Tương tự như việc bạn đang đi đến một ngã ba, ngã tư, ngã bảy và bạn phải rẽ sang một trong các hướng, thì mỗi hướng là một route. Bây giờ, bạn quay lại đầu tập tin main.dart. Nếu bạn lười cuộn lên thì nó đây:

Bạn sẽ để ý ngoài hai props title và theme, chúng ta còn có home. Props này chính là Route mặc định, khi bật ứng dụng lên lần đầu thì nó sẽ chạy vào Route này, nghĩa là nó sẽ load MyHomePage đầu tiên. Ngoài ra, để định nghĩa các Routes khác, bạn có thể làm tương tự như sau, ở đây tôi chỉ dùng thêm một Widget nữa là MyNextPage:

Để sử dụng Route có tên, bạn dùng Navigator.pushNamed(BuildContext context, String routeName). Chẳng hạn:

Chú ý: Nếu sử dụng cách này, bạn sẽ bị “bó buộc” vào constructor mặc định được định nghĩa trong MaterialApp.routes. Và điều này sẽ gây khó khăn trong việc truyền tham số giữa các màn hình. Vì vậy, cách này chỉ nên sử dụng khi một màn hình, tức Widget là cố định. Nhưng tốt nhất, nếu bạn chưa có kinh nghiệm với React Native, bạn không nên sử dụng Routes.

Ngoài ra, để xem các methods và properties khác của Navigator, vui lòng tham khảo thêm tại: https://docs.flutter.io/flutter/widgets/Navigator/Navigator.html. Còn đối với MaterialApp, thì tham chiếu đầy đủ tới nó nằm ở: https://docs.flutter.io/flutter/material/MaterialApp-class.html.

4. Show Dialog

Thật dễ dàng để hiển thị Dialog trong Flutter. Bạn chỉ cần gọi method showDialog trong các Widgets đóng vai trò “màn hình” mà thôi. Syntax của method này là:

Trong đó, bool barrierDismissible để chỉ khả năng bạn cho người dùng đóng nhanh Dialog bằng việc nhấn vào phía bên ngoài Dialog đó, còn Widget builder không là một Widget trực tiếp như Text, mà phải là một closure chứ một param duy nhất là BuildContext, và return một Dialog class, thường là SimpleDialog hoặc AlertDialog mà tôi sẽ giới thiệu bên dưới. Chẳng hạn:

Trước khi nói về hai class Dialog, tôi cần lưu ý với các bạn về method showDialog. Thực chất, khác với Android và iOS Dialog, Dialog của Flutter không thuộc “màn hình” đã gọi showDialog, mà tương tự như việc bạn chuyển sang một màn hình mới. Màn hình này là một ModalBarrier có độ trong suốt khoảng 50% và chứa nội dung Dialog. Do đó, showDialog là một Future chứ không phải void, và cách bạn xử lí các event click của các Button trong Dialog giống như bạn trả kết quả về màn hình trước, tức màn hình gọi showDialog, bằng Navigator.pop(BuildContext context, T result) như tôi dùng trong ví dụ bên trên, thay vì trực tiếp gọi hàm từ màn hình gọi showDialog như trong Android hay iOS Sdk. Thông tin thêm về hàm showDialog này nằm ở https://docs.flutter.io/flutter/material/showDialog.html.

4.1. AlertDialog

Constructor của nó không phức tạp, và đối với các bạn đã có kinh nghiệm sử dụng AlertDialog.Builder#show trong Android, thì bạn không hề xa lạ với Flutter AlertDialog:

Trong đó, Widget title là tiêu đề của Dialog, tương ứng với AlertDialog.Builder#setTitle của Android, Widget content tương ứng với AlertDialog.Builder#setView trong Android Sdk hoặc Support Lib, chỉ nội dung của AlertDialog. Cuối cùng, ta có List<Widget> actions là các Buttons nằm dưới AlertDialog tương ứng với possitiveButton, negativeButton và neutralButton của Android.

Nếu phần giới thiệu trên không thỏa mãn sự tìm tòi của bạn, hãy click vào đường link https://docs.flutter.io/flutter/material/AlertDialog-class.html.

4.2. SimpleDialog

Constructor của SimpleDialog cũng không kém phần đơn giản. Bạn chỉ cần chú ý tới phần List<Widget> children đóng vai trò nội dung cho nó.

5. Tổng kết

Vậy là tôi đã giới thiệu cho các bạn vừa đủ những kiến thức cơ bản về Flutter để bạn có thể dựng một ứng dụng đơn giản chạy theo cùng một cách trên cả Android và iOS chỉ với một và chỉ một Flutter project. Đối với các vấn đề khác mang tính nâng cao, tôi sẽ trình bày trong những bài viết đơn lẻ. Sang bài 12 thì tôi sẽ hướng dẫn các bạn build releases cho Android và iOS. Nhưng lưu ý, là việc build cho iOS cần macOS.

3 thoughts on “Flutter, Bài 11: Di chuyển giữa các màn hình, và Dialog”

  1. Cảm ơn Bảo Huy nhé, nhờ loạt bài về Flutter của bạn đã giúp mình dễ dàng hơn khi tiếp xúc với Flutter mặc dù mình đang làm .NET. Trong 12 bài mình thấy bài này khá khó tiếp cận hơn vì bài hơi dài và nhiều kiến thức hơn. Mình sẽ cố gắng đọc kỹ để nắm vững kiến thức hơn.

    1. Trân trọng cảm ơn bạn và rất hi vọng các bài viết trên EitGuide.Net sẽ giúp ích được cho bạn trong tương lai.

  2. Cảm ơn tác giả rất nhiều. Qua loạt bài này mình nắm rõ hơn về phương thức hoạt động của Flutter.

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.