(Lưu ý: Bài viết này được viết ở phiên bản dưới Flutter version 2.0).
Trong bài viết này, mình sẽ hướng dẫn cho bạn cách sử dụng Flutter để xây dựng một ứng dụng hiển thị giá hiện tại của các loại tiền điện tử khác nhau. Mình sẽ hướng dẫn bạn qua các nguyên tắc cơ bản của Flutter và Dart.
Đối với hướng dẫn này, mình sẽ sử dụng Android Studio, nhưng bạn cũng có thể sử dụng IntelliJ hoặc Visual Studio Code.
Bắt đầu nào
Trên Android Studio hoặc IntelliJ, nhấp vào File menu -> New -> New Flutter Project. Nếu bạn không thấy tùy chọn New Flutter Project, hãy đảm bảo rằng bạn đã cài đặt Flutter plugin. Nếu bạn đang sử dụng Visual Studio Code, hãy làm theo các bước ở đây để tạo một dự án mới.
Khi trang mở ra, chọn Ứng dụng Flutter và nhấp vào nút Next.
Trang tiếp theo cho phép bạn thiết lập dự án. Bạn có thể sử dụng một thiết lập tương tự như hình ảnh bên dưới. Chỉ cần đảm bảo rằng đường dẫn Flutter SDK trỏ đến thư mục mà bạn đã tải xuống Flutter.
Trang cuối cùng cho phép bạn thiết lập tên miền công ty của mình và bạn có thể đặt nó thành bất kỳ tên miền nào. Sau đó, nhấp vào nút Finish.
Việc tạo dự án sẽ bắt đầu sau khi nhấp vào nút Finish, thường mất vài phút.
Khi hoàn tất, dự án của bạn sẽ trông như thế này.
Một tệp có tên làmain.dart
đã được tạo trong thư mụclib
. Nó chứa code cho một ứng dụng demo. Vì chúng ta sẽ xây dựng ứng dụng của mình từ đầu, hãy mở tệpmain.dart
và delete/clear tất cả code trong đó.
Nếu dự án của bạn bao gồm một thư mụctest
có chứa tệpwidget_test.dart
, hãy xóa tệp này trước khi tiếp tục. Nó chứa các unit test của đoạn code chúng ta vừa xóa.
Ứng dụng Flutter được viết bằng ngôn ngữ lập trình Dart. Tệpmain.dart
là Dart source file (phần mở rộng.dart
). Quy ước của Dart là đặt tên cho các source file bằng cách sử dụnglowercase_with_underscores
.
Hãy bắt đầu viết một số Dart code. Chúng ta sẽ bắt đầu với truyền thống lập trình: printing “Hello World!”
Để làm được điều đó, chúng ta sẽ phải tạo một thứ gọi là hàmmain
(main function). Hàmmain
(main function) là hàm (function) cấp cao nhất mà mọi ứng dụng Flutter có, đóng vai trò là entry point của ứng dụng. Hãy coi nó giống như lối vào một ngôi nhà.
Khi bạn chạy ứng dụng của mình trên thiết bị, quá trình thực thi sẽ bắt đầu từ hàmmain
. Hãy tạo một hàmmain
đơn giản, sau đó hãy nhập code vào tệpmain.dart
của bạn.
// This is where the app starts executing.
void main() {
print('Hello World'); // Prints Hello World! to the console
}
Như bạn có thể thấy, việc tạo hàmmain
rất dễ dàng. Dòng thứ hai chứa khai báo hàmmain
: kiểu trả về (void
) và tên (main
). Hàm main trả vềvoid
nghĩa là nó không trả về gì cả.
Dòng thứ ba thực hiện việc in ra console. Chúng ta gọi hàmprint
và truyền một đối số chuỗi cho nó. Lưu ý rằng trong Dart, bạn có thể sử dụng dấu ngoặc kép (‘string’
) hoặc dấu ngoặc kép (“string”
) khi khai báo một chuỗi ký tự.
Để chạy code, hãy nhấp vào nút run (play) màu xanh lục ở đầu Android Studio hoặc IntelliJ. Hãy đảm bảo rằng bạn có một thiết bị thực được kết nối hoặc bạn có một emulator đang chạy.
Sau khi ứng dụng khởi động thành công, bạn sẽ thấy “Hello World!” được in trên bảng điều khiển (console).
Nhưng nếu bạn kiểm tra thiết bị hoặc emulator của mình, bạn sẽ thấy điều gì đó đáng thất vọng.
Chà, điều này đã được biết rõ từ trước, vì chúng ta hiện chỉ in ra bảng điều khiển. Không có gì được thêm vào giao diện người dùng ứng dụng và đó là lý do tại sao nó trống.
Vì vậy, hãy khắc phục điều này bằng cách thêm một số element vào giao diện người dùng ứng dụng. Ứng dụng của chúng ta sẽ sử dụng material design, vì vậy hãy thêm một package vào tệpmain.dart
để giúp thực hiện điều đó.
import 'package:flutter/material.dart';
Cũng giống như bất kỳ ngôn ngữ lập trình hiện đại nào, bạn có thể import một thư viện/package để sử dụng trong code của mình. Ở đây chúng ta đang import material.dart
package. Package này chứa code giúp chúng ta tạo một ứng dụng theo kiểu material.
material.dart
package có một hàm gọi làrunApp
.runApp
lấy một widget và gắn nó vào màn hình. À, vậy widget là gì?
Bạn có thể coi các widget là các khung nhìn (View) hoặc các element giao diện người dùng. Chúng là những thứ bạn nhìn thấy (và một số thứ bạn không thấy) khi bạn chạy ứng dụng của mình trên một thiết bị. Trong Flutter, bạn sẽ sử dụng rất nhiều widget, vì ý tưởng chính là giao diện người dùng ứng dụng của bạn được tạo hoàn toàn từ widget.
Flutter đi kèm với một bộ widget mạnh mẽ có sẵn như Text và Image. material.dart
package mà chúng ta vừa nhập có một số material design widget mà chúng ta sẽ sử dụng trong thời gian ngắn.
Hãy sử dụngrunApp
method ngay bây giờ để hiển thị “Hello World!” ở giữa màn hình thiết bị. Thay thế nội dung của hàm chính bằng đoạn code bên dưới.
void main() {
print('Hello World!');
// Runs the MaterialApp widget
runApp(new MaterialApp(
// This is the widget that is displayed first when the application is started normally
home: new Center(
// The Text widget is wrapped in a center widget to center it on the screen
child: new Text('Hello World!'),
),
));
}
Hãy để mình giải thích một số nội dung mới trong đoạn code trên
new MaterialApp()
: Ở đây chúng ta đang tạo một widget object mới có tên làMaterialApp
.MaterialApp
widget tạo ra một số thứ hữu ích cần thiết cho một ứng dụng material design.home:
Trong Dart, chúng ta có thể nêu rõ tên của từng tham số trong function/constructor call. Widget được chuyển vào làm tham sốhome
(home parameter) được hiển thị đầu tiên khi ứng dụng được khởi động bình thường.new Center(child: new Text('Hello World!'))
: Chúng ra wrap Text widget bên trong Center widget để văn bản được căn giữa trên màn hình. Text widget là một child của Center widget. Và các vật dụng có thể được lồng vào nhau.
Nếu bạn chạy lại code và mở thiết bị của mình, bạn sẽ nhận được một màn hình tốt hơn một chút ngay bây giờ.
Tuyệt vời! Chúng ta có thể hiển thị một văn bản trông xấu xí ở giữa màn hình thiết bị.
Các bước tiếp theo
Bây giờ chúng ta hãy thực hiện một vài bước trước đó. Chúng ta sẽ lấy giá tiền tiền điện tử (cryptocurrency) từ CoinMarketCap API. API trả về một mảng JSON. Đây là phản hồi mẫu từ API:
[
{
"name": "Bitcoin",
"price_usd": "11525.7",
"percent_change_1h": "-0.18",
...
},
...
]
Chúng ta sẽ thực hiện các request đến CoinMarketCap API để lấy dữ liệu và giải mã JSON từ ứng dụng. Chúng ta sẽ phải thêm một vài package mới vào tệpmain.dart
.
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
view raw
Dưới đây là tổng quan về các package mới:
dart:async
: Cung cấpFuture
mà mình sẽ nói rõ hơn ở phần dưới.dart:convert
: Cung cấpjson
variable mà chúng ta sẽ sử dụng để decode chuỗi JSON.package:http/http.dart
: Cung cấp hàm mà chúng ta sẽ sử dụng để thực hiện các yêu cầu HTTP GET.
Hãy thêm một hàm mới vào tệpmain.dart
để thực hiện yêu cầu đối với CoinMarketCap API.
Future<List> getCurrencies() async {
String apiUrl = 'https://api.coinmarketcap.com/v1/ticker/?limit=50';
// Make a HTTP GET request to the CoinMarketCap API.
// Await basically pauses execution until the get() function returns a Response
http.Response response = await http.get(apiUrl);
// Using the JSON class to decode the JSON String
return JSON.decode(response.body);
}
Hãy xem qua code mới
→ Future<List>
: Về cơ bản chúng ta đang nói rằng hàmgetCurrencies()
sẽ trả về List
lúc nào đó trong tương lai. Nó sẽ thực hiện một yêu cầu HTTP đến CoinMarketCap API và trả lạiList
các loại tiền tệ khi hoàn tất.
HàmgetCurrencies()
không đồng bộ. Nếu bạn có một số kinh nghiệm về JavaScript, bạn có thể coiFutures
làPromises
. Mình đã tạo những hình ảnh dưới đây để giúp bạn hiểu vềFutures
trong Dart.
→ async and await
:
Biểu thức Await
cho phép bạn viết code không đồng bộ gần như là code đồng bộ. Hàmhttp.get(url)
không đồng bộ, trả về mộtFuture<Response>
ngay lập tức khi nó được call. Chúng ta muốn đợiResponse
để có thể decode chuỗi JSON, nhưng chúng ta cũng không muốn sử dụng các ugly callback.
Biểu thức Await
thực hiện http.get(url)
, sau đó tạm dừng hàm hiện đang chạy (getCurrencies()
) cho đến khi kết quả sẵn sàng - nghĩa là cho đến khi Future hoàn thành.
Để sử dụngAwait
, code phải nằm trong một hàm được đánh dấu là không đồng bộ. Hàmasync
là một hàm có nội dung được đánh dấu bằng masync
modifier. Khi bạn call một hàmasync
, nó trả về Future ngay lập tức . Phần thân của hàm được lên lịch để thực thi sau đó.
Bạn có thể đọc thêm về async
vàAwait
trong Dart tại đây.
→ http.get(url)
: Thực hiện yêu cầu HTTP GET tới CoinMarketCap API. Hàm này là hàm bất đồng bộ và trả về Future.
JSON.decode(response.body)
: Decode chuỗi JSON.
Hãy kiểm tra hàmgetCurrencies()
mà chúng ta vừa tạo. Chúng ta thực hiện điều đó bằng cách thực hiện cuộc gọi đến nó trong hàmmain
của chúng ta và in giá trị trả về vào bảng điều khiển.
// Since we are using await within the main function, we have to make it asynchronous too
void main() async {
// Testing the getCurrencies function
// We wait for the currency data to arrive
List currencies = await getCurrencies();
// Before printing it to the Console
print(currencies);
runApp(new MaterialApp(
home: new Center(
child: new Text('Hello World!'),
),
));
}
Nếu bạn chạy code ở trên, bạn sẽ thấy phản hồi API được in ra bảng điều khiển.
Trong thế giới thực, những điều tồi tệ luôn có thể xảy ra. Ví dụ: bạn có thể không kết nối được với internet, vì vậy yêu cầu tới CoinMarketCap API sẽ không thành công. Đối với hướng dẫn này, chúng ta sẽ giả sử chúng ta đang ở Wakanda.
Trong ứng dụng sản xuất, bạn sẽ phải xử lý lỗi mạng. Bạn làm điều đó bằng cách đưa lệnh gọi HTTP vào khốitry…catch
.
Xây dựng UI
Bây giờ chúng ta đã có danh sách các đơn vị tiền tệ, hãy tiếp tục xây dựng giao diện người dùng để hiển thị danh sách đó.
Khi viết ứng dụng Flutter, bạn thường sẽ tạo các class widget mới. Công việc chính của widget là triển khai hàmbuild
, hàm này mô tả widget dưới dạng các widget cấp thấp hơn.
Hãy tạo một Widget mới có tên là CryptoListWidget. Thêm code bên dưới vào cuối tệpmain.dart
của bạn.
class CryptoListWidget extends StatelessWidget {
// This is a list of material colors. Feel free to add more colors, it won't break the code
final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];
// The underscore before a variable name marks it as a private variable
final List _currencies;
// This is a constructor in Dart. We are assigning the value passed to the constructor
// to the _currencies variable
CryptoListWidget(this._currencies);
@override
Widget build(BuildContext context) {
// Build describes the widget in terms of other, lower-level widgets.
return new Text('Hello World!');
}
}
Hãy xem qua code mới ở trên:
- StatelessWidget: Bạn thường sẽ tạo các Widget là subclass của StatelessWidget hoặc StatefulWidget, tùy thuộc vào việc widget của bạn quản lý state nào. Chúng mình đang sử dụng StatelessWidget vì chúng mình đã có dữ liệu của mình và chúng mình sẽ không cập nhật dữ liệu đó trong hướng dẫn này.
- final List<MaterialColor> _colors: Các variable trong StatelessWidget phải là cuối cùng (có nghĩa là chúng không thay đổi). Ở đây chúng mình đang khai báo một variable cuối cùng chứa danh sách các MaterialColors. Dấu gạch dưới (_) trước tên variable làm cho nó ở chế độ riêng tư (không thể truy cập từ các class khác).
CryptoListWidget(this._currencies)
: Đây là constructor cho widget của chúng ta. Nó chỉ định danh sách các đơn vị tiền tệ mà chúng ta chuyển vào constructor cho _currencies variable.build
function: Build method trả về một Widget cấp thấp hơn (Text
) mô tả nó sẽ trông như thế nào.
Hãy thay thế Text widget trong hàmbuild
ở trên bằng một widget mới có tên làScaffold
.Scaffold
widget triển khai cấu trúc bố cục hình ảnh material design cơ bản. Thay thế hàmbuild
ở trên bằng hàm bên dưới.
@override
Widget build(BuildContext context) {
return new Scaffold(
body: _buildBody(),
backgroundColor: Colors.blue,
);
}
Scaffold cung cấp các API để hiển thị drawers, float action button, bottom bar, snack bar, v.v. Chúng ta sẽ thêm một floating action button sau.
Bạn sẽ nhận được cảnh báo rằng_buildBody()
không được định nghĩa cho classCryptoListWidget
. Hãy tiếp tục tạo hàm _buildBody()
bên trong class CryptoListWidget
.
Widget _buildBody() {
return new Container(
// A top margin of 56.0. A left and right margin of 8.0. And a bottom margin of 0.0.
margin: const EdgeInsets.fromLTRB(8.0, 56.0, 8.0, 0.0),
child: new Column(
// A column widget can have several widgets that are placed in a top down fashion
children: <Widget>[
_getAppTitleWidget(),
_getListViewWidget()
],
),
);
}
Cú pháp ở đây bạn khá là quen thuộc. Chúng ta đang sử dụng hai Widget mới:
Container
widget:Container
widget chỉ là một container :) (đối với các widget khác).- Column widget:
Column
widget bố trí danh sách các child widget theo hướng dọc. Nó có một tham số được gọi làchildren
để lấy danh sách các widget.
Hãy tạo hai hàm mà chúng ta đã gọi trong hàm_buildBody()
. Cái đầu tiên là_getAppTitleWidget()
.
Widget _getAppTitleWidget() {
return new Text(
'Cryptocurrencies',
style: new TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 24.0),
);
}
Hàm này trả về một Text
widget thông thường có kiểu làm cho nó đậm và trắng với kích thước phông chữ là 24.0. Văn bản sẽ trông như thế này khi chúng ta chạy ứng dụng.
Chúng ta chưa thể chạy ứng dụng vì chúng ta chưa tạo hàm khác có tên_getListViewWidget()
. Hãy nhanh chóng tạo nó bằng cách sử dụng code bên dưới.
Widget _getListViewWidget() {
// We want the ListView to have the flexibility to expand to fill the
// available space in the vertical axis
return new Flexible(
child: new ListView.builder(
// The number of items to show
itemCount: _currencies.length,
// Callback that should return ListView children
// The index parameter = 0...(itemCount-1)
itemBuilder: (context, index) {
// Get the currency at this position
final Map currency = _currencies[index];
// Get the icon color. Since x mod y, will always be less than y,
// this will be within bounds
final MaterialColor color = _colors[index % _colors.length];
return _getListItemWidget(currency, color);
}));
}
_getListViewWidget()
trả vềListView
widget được wrap trongFlexible
widget. Chúng ta sử dụngListView.builder
để dễ dàng tạo danh sách. Chúng ta chuyển vàoitemCount
cho builder biết có bao nhiêu đơn vị tiền tệ để hiển thị.
itemBuilder
callback sẽ được call cho từng item và bạn phải trả lại một widget mới. Trong đoạn code trên, chúng ta đang call một hàm có tên_getListItemWidget()
trả về một Widget.
Trước khi chúng ta tạo hàm_getListItemWidget()
, hãy nhanh chóng tạo các element riêng lẻ cho ListView item widget. Chúng ta muốn mỗi mục trong ListView trông như thế này:
Vì vậy, chúng ta có ba widget chính:
- Round icon widget có ký tự đầu tiên của tên đơn vị tiền tệ
- Một text widget có tên đơn vị tiền tệ
- Một text widget với giá và phần trăm thay đổi trong 1 giờ.
Hãy tạo các widget. Vì lợi ích đơn giản, mình đã tạo một hàm cho mỗi cái trong số chúng. Hàm đầu tiên được gọi là_getLeadingWidget()
trả về biểu tượng tròn có văn bản.
CircleAvatar _getLeadingWidget(String currencyName, MaterialColor color) {
return new CircleAvatar(
backgroundColor: color,
child: new Text(currencyName[0]),
);
}
Widget sẽ trông như thế này:
Hàm thứ hai được gọi là_getTitleWidget
trả vềText
widget cho tên đơn vị tiền tệ.
Hàm thứ ba được gọi là _getSubtitleWidget() trả vềText
widget cho giá tiền hiện tại và phần trăm thay đổi trong 1 giờ.
Text _getTitleWidget(String currencyName) {
return new Text(
currencyName,
style: new TextStyle(fontWeight: FontWeight.bold),
);
}
Text _getSubtitleWidget(String priceUsd, String percentChange1h) {
return new Text('\$$priceUsd\n1 hour: $percentChange1h%');
}
Cả hai widget này sẽ trông như thế này:
Hãy wrap tất cả ba widget trong một thứ gọi là ListTile
widget. ListTile
widget dựa trên tài liệu Material Design List. Nó hiển thị tất cả ba widget theo kiểu này.
Chúng ta sẽ tạo một hàm mới có tên _getListTile
trả về một ListTile
widget.
ListTile _getListTile(Map currency, MaterialColor color) {
return new ListTile(
leading: _getLeadingWidget(currency['name'], color),
title: _getTitleWidget(currency['name']),
subtitle: _getSubtitleWidget(
currency['price_usd'], currency['percent_change_1h']),
isThreeLine: true,
);
}
Cuối cùng, hãy tạo_getListItemWidget()
. Nó sẽ trả về mộtContainer
widget có top padding là 5.0 và có Card
widget child. Card widget cóListTile
được_getListTile
trả về khi là child của nó.
Container _getListItemWidget(Map currency, MaterialColor color) {
// Returns a container widget that has a card child and a top margin of 5.0
return new Container(
margin: const EdgeInsets.only(top: 5.0),
child: new Card(
child: _getListTile(currency, color),
),
);
}
Widget sẽ trông như thế này.
Chúng ta đã hoàn thànhCryptoListWidget
của mình thành công. Nhưng chúng ta phải cập nhật hàmmain
để hiển thị widget mới tạo thay vìText
widget.
void main() async {
// Bad practice alert :). You should ideally show the UI, and probably a progress view,
// then when the requests completes, update the UI to show the data.
List currencies = await getCurrencies();
print(currencies);
runApp(new MaterialApp(
home: new CryptoListWidget(currencies),
));
}
Bạn có thể nhấn lại nút run. Nếu mọi thứ hoạt động tốt và bạn được kết nối với Internet, bạn sẽ có một màn hình trông như thế này.
Thật tuyệt phải không?
Ứng dụng bạn thấy sẽ hơi khác so với ảnh chụp màn hình ở trên:
- Nó không có nút tác vụ nổi ở dưới cùng bên phải.
- Màu văn bản của phần trăm thay đổi trong 1 giờ là màu đen.
Mình quyết định không đưa chúng vào hướng dẫn. Nhưng bạn có thể kiểm tra kho lưu trữ Github của dự án để xem mình đã có thể làm được nó như thế nào.
Ứng dụng hoàn thành có thể được tải xuống tại đây.
Cảm ơn bạn đã đọc và hy vọng bạn cũng thích Flutter như mình.
Bài viết được lược dịch từ Elvis Chidera.
Chau Le
Cực thích đồ ngọt như sữa, kem. Thích phiêu lưu mạo hiểm nhưng vì Covid mà vô tình trở thành người chia sẻ nội dung lập trình :))
follow me :
Bài viết liên quan
Tự học Dart: Các Dart Operators (toán tử) bạn cần biết
Sep 02, 2023 • 18 min read
Flutter cơ bản: Điều cần biết khi lập trình ứng dụng đầu tiên
Aug 23, 2023 • 13 min read
Dart là gì? Giới thiệu cơ bản về ngôn ngữ lập trình Dart
Aug 21, 2023 • 11 min read
Flutter là gì? Vì sao nên học công cụ lập trình Flutter?
Aug 19, 2023 • 11 min read
Flutter cơ bản: Widget Tree, Element Tree & Render Tree
Aug 19, 2023 • 10 min read
So sánh StatelessWidget và StatefulWidget
Jul 17, 2022 • 12 min read