Giới thiệu
Giới thiệu chung
Flutter là một framework UI di động miễn phí và mã nguồn mở do Google phát triển, giúp tạo ra các giao diện chất lượng cao cho cả iOS và Android một cách nhanh chóng Framework này cho phép các lập trình viên và tổ chức sử dụng mã nguồn có sẵn để tối ưu hóa quy trình phát triển ứng dụng.
Các ứng dụng phát triển bằng Flutter có giao diện và hiệu suất tương đương với những ứng dụng sử dụng Android SDK, khiến chúng khó bị phân biệt Thêm vào đó, với một số điều chỉnh nhỏ, các ứng dụng này cũng có thể hoạt động trên thiết bị iOS.
Flutter sử dụng Dart, một ngôn ngữ nhanh, hướng đối tượng với nhiều tính năng hữu ích như mixin, generic, isolate, và static type.
Flutter cung cấp các thành phần giao diện người dùng độc đáo và một cơ chế để hiển thị chúng trên cả nền tảng Android và iOS Hầu hết các thành phần này đều có sẵn và tuân thủ các nguyên tắc của Material Design.
Lịch sử phát triển
Tháng 5 năm 2017 phát hành bản alpha đầu tiên.
Tháng 8 năm 2017 phát hành ứng dụng thương mại đầu tiên.
Tháng 3 năm 2018 phát hành bản beta đầu tiên.
Tháng 5 năm 2018 flutter tham gia 100 đại diện dành đầu của github.
Tháng 12 năm 2018 Google phát hành bản flutter 1.0 và là bản ổn định để có thể dung.
Tháng 2 năm 2019 tại đại hội thế giới di động phát hành bản flutter 1.2.
Tháng 5 năm 2019 flutter cho phiên bản xem trước trên web.
Tháng 9 năm 2019 flutter phát hành bản 1.9.
Tháng 12 năm 2019 Flutter interact Flutter 1.12 và Dart 2.7 , Flutter web hỗ trợ trong bản beta.
Mục tiêu phát triển
Flutter được thiết kế để tạo ra giao diện với nhiều hiệu ứng phong phú, mang lại vẻ đẹp và sự sinh động cho ứng dụng Điều này giúp lập trình viên tiết kiệm đáng kể thời gian trong quá trình phát triển sản phẩm.
So sánh ưu nhược điểm
Ưu điểm
Flutter là bộ open-source SDK , tức là miễn phí và mở – cộng đồng developer có thể cùng tham gia phát triển
Flutter mang đến giao diện đẹp mắt với nhiều widget đa dạng, cho phép người dùng lựa chọn phong phú Giao diện sắc nét kết hợp với các API chuyển động phong phú, mang lại trải nghiệm cuộn tự nhiên và mượt mà, đồng thời tự nhận thức được nền tảng sử dụng.
Việc viết code ứng dụng trở nên nhanh chóng hơn nhờ vào tầng Framework của Flutter, được phát triển bằng ngôn ngữ Dart, hỗ trợ JIT (Just In Time) Tính năng hot reload cho phép bạn cập nhật ứng dụng ngay lập tức khi có thay đổi trong source code mà không cần phải biên dịch lại, chỉ cần nhấn nút hot reload Điều này giúp bạn tiết kiệm thời gian và nâng cao hiệu quả trong quá trình phát triển ứng dụng.
Flutter là một framework dễ học và sử dụng, cho phép bạn tạo ứng dụng di động một cách đơn giản Nếu bạn đã từng làm quen với Java, Swift hay React Native, bạn sẽ nhận thấy sự khác biệt rõ rệt khi sử dụng Flutter, đó là khả năng phát triển ứng dụng gốc thực sự mà không cần phải viết quá nhiều mã, từ đó giảm thiểu các lỗi phát sinh.
Hiệu năng mạnh mẽ: Static language nhưng với syntax hiện đại, compiler linh động giữa AOT (for archive, build prod) và JIT (for development, hot reload).
Giả lập mobile có thể được chạy trực tiếp trên web, mang lại sự tiện lợi cho quá trình phát triển ứng dụng Các công cụ đo lường hiệu suất được tích hợp sẵn giúp lập trình viên dễ dàng theo dõi và kiểm soát hiệu suất của ứng dụng một cách hiệu quả.
Framework hiện đại: Dễ dàng tạo giao diện người dùng của bạn với
Hỗ trợ đa nền tảng bao gồm Android, iOS, Desktop, Linux và hệ thống nhúng, giúp tiết kiệm thời gian và chi phí xây dựng ứng dụng Việc sử dụng một basecode duy nhất cho cả hai nền tảng Android và iOS cho phép viết code nhanh chóng, rút ngắn thời gian kiểm tra và sửa lỗi, từ đó giảm thiểu chi phí phát triển ứng dụng.
Nhược điểm
Thư viện và hỗ trợ của Flutter SDK còn hạn chế hơn so với các SDK gốc, do Flutter chỉ mới được phát hành chính thức vào năm 2017, dẫn đến cộng đồng phát triển chưa mạnh mẽ như các nền tảng native.
The file size of applications can vary significantly; for instance, a simple "Hello World" app built with Flutter has a size of 4.7MB, while a Kotlin app is only 550KB, and a native Java app is 539KB This discrepancy is largely due to the Flutter SDK's architecture, which includes libraries designed to support both iOS and Android platforms.
Bộ render UI gần như viết lại, không liên quan tới UI có sẵn của Framework native, dẫn đến memory sử dụng khá nhiều
Phải học thêm ngôn ngữ DART Dù dễ và thân thiện nhưng đây cũng là
1 rào cản quan trọng cần cân nhắc.
Mô hình dữ liệu mới trong Flutter bao gồm bloc pattern và DART Streaming Nếu bạn đã quen thuộc với Redux trong phát triển React Native, việc làm quen với mô hình dữ liệu trong Flutter có thể mất thời gian, nhưng thực tế không khó khăn.
Rapid updates are now commonplace, with users often waking up to discover a two-number version increase The current version is more stable, and updates rarely introduce breaking changes that affect previous source code.
So sánh với framework khác
Hình ảnh 1: Biểu đồ so sánh flutter và react native
Hướng dẫn cài đặt
Đầu tiên chúng ta vào trang web của Flutter để tiến hành cài đặt theo đường link sau https://flutter.dev/docs/get-started/install/windows
Hình ảnh 2: Tải gói flutter SDK Sau đó chúng ta giải nén và bỏ vào Documents
Hình ảnh 3: Giải nén flutter SDKTiếp theo chúng ta coppy đường dẫn có chứa tệp flutter trên và bỏ vào trong Edit the system enviroment variables.
Hình ảnh 4: Edit the system enviroment variables
Hình ảnh 7: Dán đường dẫn thư mục flutter SDK
Sau đó mở cmd kiểm tra với lệnh flutter doctor, sẽ hiển thị được như bên dưới hình
Hình ảnh 8: Kiểm tra cài đặt flutter
Tiếp theo chúng ta gõ lệnh flutter config –android-studio-dir=”C:\
Program Files\Android\Android Studio”
Hình ảnh 9: Sửa lỗi không nhận Android Studio
Next, type the command "flutter doctor android-licenses" in the console The screen will execute the command, and when prompted with a yes/no question, select "yes" to grant permission for Flutter.
Hình ảnh 10: Sửa lỗi Android toolchain Sau đó tất cả đều hiện tích xanh là chúng ta đã hoàn thành bước cài đặt.
Hình ảnh 11: Cài đặt thành công
Cấu trúc trong Flutter
Cấu trúc của flutter sau khi đợc tạo mới sẽ có các thư mục như hình bên dưới.
Hình ảnh 12: Cấu trúc thư mục flutter
Giới thiệu và cách thức giải quyết
Khi phát triển ứng dụng cung cấp thông tin thời tiết và số liệu ca nhiễm Covid-19, cần chú trọng đến hai phần chính: Backend và Frontend Phần Backend chịu trách nhiệm thu thập dữ liệu về thời tiết và tình hình Covid-19, bao gồm các thông tin như nhiệt độ, độ ẩm, số ca nhiễm và tử vong.
Về phần Fontend đóng vai trò là giao diện để người dùng tương tác và hiển thị dữ liệu ra cũng như các chức năng của ứng dụng.
Phần giao diện của ứng dụng gồm 2 thành phần là xem dữ liệu thời tiết và dữ liệu thống kê về tình hình Covid 19
Backend sử dụng API để lấy dữ liệu, ứng dụng sẽ đọc và xử lý thông tin từ API, sau đó hiển thị kết quả lên giao diện Frontend cho người dùng.
Phân tích hệ thống
Tên chức năng Mô tả chức năng
Thời tiết hiện thị tên thành phố và nhiệt độ, cùng với tình trạng thời tiết hiện tại Bạn có thể tìm kiếm và nhập tên thành phố mà bạn muốn xem thông tin thời tiết.
Covid 19 Bấm chưc năng này sẽ hiển thị ra tình hình covid trên thế giới và thành phố
Covid thế giới Hiển thị ra số liệu ca nhiễm, tử vong, lành bệnh trên toàn thế giới
Tìm kiếm thông tin Covid của quốc gia bằng cách nhập tên quốc gia vào thanh tìm kiếm Sau khi nhập, bạn sẽ nhận được dữ liệu về số ca nhiễm, tử vong và số người đã khỏi bệnh tại quốc gia đó.
Yêu cầu hệ thống
- Tốc độ xử lý nhanh, mượt ít tốn thời gian.
- Giao diện thân thiện dễ dùng gọn gàng sạch đẹp.
Tạo mới ứng dụng trong Flutter
Để tạo một dự án Flutter mới, bạn cần mở cmd và điều hướng đến thư mục chứa dự án Sau đó, gõ lệnh "flutter create myapp", trong đó "myapp" là tên dự án mà bạn muốn tạo.
Hình ảnh 14: Flutter sau khi được tạo mớiSau khi chạy dự án chúng ta sẽ có được demo như sau.
Xây dựng cấu trúc thư mục
Hình ảnh 16: Cấu trúc cây thư mục sau khi tạo mới
Thư mục lib chứa tất cả các tệp liên quan đến fontend và backend.
Thư mục pubspec.yaml dùng để chứa tất cả các liên kết đến các thư viện hỗ trợ cho flutter.
Thư mục Android và iOS chứa các tệp quan trọng phục vụ cho việc vận hành ứng dụng trên hai nền tảng này, đồng thời giúp lập trình viên tinh chỉnh và điều chỉnh ứng dụng theo yêu cầu cụ thể.
Thư mục build là nơi mà chúng ta chuyển đổi từ code ra ứng dụng để chạy trên nền tảng android hay ios.
Cài đặt các gói bổ sung
cupertino_icons: giúp flutter đùng được icons hỗ trợ bên ngoài.
font_awesome_flutter: giúp sử dụng nhiều font hơn.
http: để lấy dữ liệu từ các API http
Hình ảnh 17: Các gói bổ sung
3.4 Xây dựng bố cục cho ứng dụng
Hình ảnh 18: Giao diện tìm kiếm thời tiết thành phố
Hình ảnh 19: Giao diện thông tinm chi tiết của thành phố
Hình ảnh 20: Giao diện thông tin covid của thế giới
Hình ảnh 21: Giao diện thông tin covid được tìm kiếm
Xây dựng các thành phần cho ứng dụng
Xây dựng thanh navigation bên dưới
Phần code của giao diện:
Widget build(BuildContext context) { return Scaffold( body: Center( child: _widgetOption.elementAt(_selectedIndex),
), bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, selectedItemColor: kPrimaryColor, unselectedItemColor: Colors.grey, icon: Icon(FontAwesomeIcons.virus), label: "Covid-19",
], currentIndex: _selectedIndex, onTap: (int index) { setState(() {
Xây dựng thanh tìm kiếm
Phần code của giao diện:
Column( mainAxisAlignment: MainAxisAlignment.center, children: [
Padding( padding: EdgeInsets.fromLTRB(20, 0, 20, 0), child: TextField( controller: city, decoration: InputDecoration( focusedBorder: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(30.0),
), borderSide: BorderSide( color: Colors.black, width: 2.0), ), enabledBorder: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(30.0),
), borderSide: BorderSide( color: Colors.black, width: 2.0), ), hintText: 'Nhập Thành Phố', suffixIcon: IconButton( onPressed: () {
Xây dựng chức năng hiển thị nhiệt độ thành phố tạm thời
Container( height: 200, margin: EdgeInsets.fromLTRB(20, 0, 20, 0),
//color: Colors.black, decoration: BoxDecoration( borderRadius: BorderRadius.all(
), color: Color(0xff9FA8DA), offset: Offset(
), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [
: "Loading", style: TextStyle( color: Colors.black, fontSize: 45.0, fontWeight: FontWeight.bold, letterSpacing: 3, fontFamily: 'JosefinSans'),
Padding( padding: EdgeInsets.only(top: 20), child: Row(children: [
: "Loading", style: TextStyle( color: Colors.black, fontSize: 30.0,
//fontWeight: FontWeight.w700, fontFamily: 'JosefinSans', letterSpacing: 3,
Padding( padding: EdgeInsets.only(top: 20.0), child: Row(children: [
Icon(Icons.cloud_circle_sharp),
Xây dựng giao diện hiển thị thời tiết sau khi tìm kiếm
Widget build(BuildContext context) { return Scaffold( body: Column(children: [
To create a visually appealing container in your Flutter application, set its height to a fraction of the device's height (specifically, MediaQuery.of(context).size.height / 2.2) and its width to the full device width Enhance the container's aesthetics by applying a BoxDecoration that includes a background image sourced from 'assets/weather.gif', ensuring it covers the entire area with BoxFit.cover Additionally, you can incorporate a ColorFilter for added visual effects.
Colors.black.withOpacity(0.6), BlendMode.dstATop),
FontAwesomeIcons.search, size: 30, color: Colors.black,
Padding( padding: EdgeInsets.only(top: 40, bottom: 20), child: Text(
"Currently in ", style: TextStyle( color: Colors.black54, fontSize: 35.0, fontWeight: FontWeight.bold, letterSpacing: 2, fontFamily: 'JosefinSans'),
Text( widget.text.toUpperCase(), style: TextStyle( color: Colors.black, fontSize: 30, fontWeight: FontWeight.bold, letterSpacing: 3, fontFamily: 'JosefinSans'),
Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Padding( padding: EdgeInsets.only(top: 20), child: Row(children: [
Text( temp != null ? temp.toString() + "\u00b0" : "Loading", style: TextStyle( color: Colors.black, fontSize: 30.0,
//fontWeight: FontWeight.w700, fontFamily: 'JosefinSans', letterSpacing: 3,
]), padding: EdgeInsets.only(top: 20.0), child: Row(children: [
Icon(Icons.cloud_circle_sharp),
Text( currently != null ? currently.toString() : "Loading", style: TextStyle( color: Colors.black, fontSize: 30.0,
Expanded( child: Container( color: Color.fromRGBO(236, 229, 220, 0.6), child: Container( margin: EdgeInsets.fromLTRB(30, 50, 30, 80),
//color: Colors.red, decoration: BoxDecoration( borderRadius: BorderRadius.all(
BoxShadow( color: Colors.grey, blurRadius: 20.0, // soften the shadow spreadRadius: -10.0, //extend the shadow offset: Offset(
//child: Padding( title: Text("Temperature", style: TextStyle( fontFamily: 'Syne', fontWeight: FontWeight.bold)), trailing: Text( temp != null ? temp.toString() + "\u00b0" : "Loading", style: TextStyle(fontSize: 15)),
FaIcon(FontAwesomeIcons.cloud, color: Colors.black), title: Text("Weather", style: TextStyle( fontFamily: 'Syne', fontWeight: FontWeight.bold)), trailing: Text( description != null
FaIcon(FontAwesomeIcons.tint, color: Colors.black), title: Text("Humidity", style: TextStyle( fontFamily: 'Syne', fontWeight: FontWeight.bold)), trailing: Text( humidity != null
FaIcon(FontAwesomeIcons.wind, color: Colors.black), title: Text("Wind Speed", style: TextStyle( fontFamily: 'Syne', fontWeight: FontWeight.bold)), trailing: Text( windspeed != null
Xây dựng thanh navigation chia thế giới và quốc gia
Size size = MediaQuery.of(context).size; return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( backgroundColor: kPrimaryColors, elevation: 0, title: Text("Covid 19 App"), centerTitle: true,
), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Expanded( child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: kPrimaryColors, borderRadius: BorderRadius.only( bottomRight: Radius.circular(50), bottomLeft: Radius.circular(50)),
), child: AnimatedSwitcher( duration: Duration(milliseconds: 250), child: navigationStatus == NavigationStatus.GLOBAL
), title: "Thế Giới", selected: navigationStatus == NavigationStatus.GLOBAL, onSelected: () { setState(() { navigationStatus = NavigationStatus.GLOBAL;
NavigationOption( title: "Quốc Gia", selected: navigationStatus == NavigationStatus.COUNTRY, onSelected: () { setState(() { navigationStatus = NavigationStatus.COUNTRY; });
Xây dựng giao diện covid 19 thế giới
Phần code giao diện: return Column( children: [ buildCard(
"Tổng Ca Nhiễm Covid Ghi Nhận", summary?.cases ?? 0, summary?.todayCases ?? 0, kConfirmedColor,
"Tổng Số Ca Hiện Tại", summary?.active ?? 0, summary?.todayCases ?? 0, kActiveColor,
"Tổng Số Ca Hồi Phục", summary?.recovered ?? 0, summary?.todayRecovered ?? 0, kRecoveredColor,
"Tổng Số Ca Chết", summary?.deaths ?? 0, summary?.todayDeaths ?? 0, kDeathColor,
Widget buildCard(String title, int totalCount, int todayCount, Color color) { return Card( elevation: 1, child: Container( height: 90, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( children: [
Text( title, style: TextStyle( color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 14,
Column( crossAxisAlignment: CrossAxisAlignment.start, children: [
"Tổng", style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 15,
Text( totalCount.toString().replaceAllMapped( reg, (Match match) => '${match[1]}.'), style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 28,
Column( crossAxisAlignment: CrossAxisAlignment.end, children: [
"Hôm nay", style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 15,
Text( todayCount.toString().replaceAllMapped( reg, (Match match) => '${match[1]}.'), style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 28,
Xây dựng thanh tìm kiếm quốc gia
Widget build(BuildContext context) { return FutureBuilder( future: countryList, builder: (context, snapshot) { if (snapshot.hasError) { return Center( child: Text("Error"),
} switch (snapshot.connectionState) { case ConnectionState.waiting: return Center( child: Text("Loading"),
: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [
EdgeInsets.symmetric(horizontal: 4, vertical: 6), child: Text(
"Tên Quốc Gia", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14,
), hintStyle: TextStyle(fontSize: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide( width: 0, style: BorderStyle.none,
), filled: true, fillColor: Colors.grey[200], contentPadding: EdgeInsets.all(20), prefixIcon: Padding( padding: EdgeInsets.only(left: 24.0, right: 16.0), child: Icon(
Icons.search, color: Colors.black, size: 28,
), suggestionsCallback: CovidService.getCountrySummary, itemBuilder: (context, CountryModel? suggestion) { final country = suggestion!; return ListTile( title: Text(country.country),
(context, suggestionsBox, controller) { return suggestionsBox;
}, onSuggestionSelected: (CountryModel? suggestion) { this._typeAheadController.text suggestion?.country ?? ''; setState(() { summaryList = CovidService.getCountrySummary( suggestion!.country);
FutureBuilder( future: summaryList, builder: (context, snapshot) { if (snapshot.hasError) return Center( child: Text("Error"),
); switch (snapshot.connectionState) { case ConnectionState.waiting:
: CountryStatistics( summaryList: snapshot.data as List, );
Xây dựng giao diện covid 19 quốc gia
"Số Ca Ghi Nhận", summaryList[summaryList.length - 1].cases, kConfirmedColor,
"Số Ca Nhiễm", summaryList[summaryList.length - 1].active, kActiveColor,
"Số Ca Hồi Phục", summaryList[summaryList.length - 1].recovered, kRecoveredColor,
"Số Ca Chết", summaryList[summaryList.length - 1].deaths, kDeathColor,
"Ca Nhiễm / Ngày", summaryList[summaryList.length - 1].todayCases, kConfirmedColor,
"Ca Nhiễm / 1 Triệu", summaryList[summaryList.length - 1].casesPerOneMillion, kConfirmedColor,
"Ca Hồi Phục / Ngày", summaryList[summaryList.length - 1].todayRecovered, kRecoveredColor,
"Ca Chết / Ngày", summaryList[summaryList.length - 1].todayDeaths, kDeathColor,
The `buildCard` function creates a customizable card widget in Flutter, featuring two sections: the left section displays a title and value with a specified color, while the right section showcases a different title and value, also with a designated color The card has a slight elevation for a shadow effect and includes padding for visual comfort, ensuring the content is presented in a balanced row layout.
Column( crossAxisAlignment: CrossAxisAlignment.start, children: [
Text( leftTitle, style: TextStyle( fontSize: 14,
replaceAllMapped(reg, (Match match) => '${match[1]}.'), style: TextStyle( color: leftColor, fontWeight: FontWeight.bold, fontSize: 28,
Column( crossAxisAlignment: CrossAxisAlignment.end, children: [
Text( rightTitle, style: TextStyle( color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 14,
replaceAllMapped(reg, (Match match) => '${match[1]}.'), style: TextStyle( color: rightColor, fontWeight: FontWeight.bold, fontSize: 28,
Xây dựng chức năng rest API
String urlCity = "http://static.pipezero.com/covid/data.json";
Future getGlobalSummary() async { final response = await http.get(Uri.parse(urlAll)); if (response.statusCode == 200) { var summary = GlobalSummaryModel.fromJson(jsonDecode(response.body)); return summary;
} else { throw Exception('Unexpected error occured!');
} static Future getCountrySummary(String query) async { final response await http.get(Uri.parse("https://disease.sh/v3/covid-19/countries")); if (response.statusCode == 200) {
// print(response.body); final List summaryList = json.decode(response.body); return summaryList
map((json) => CountryModel.fromJson(json))
where((country) { final countryLower = country.country.toLowerCase(); final queryLower = query.toLowerCase(); return countryLower.contains(queryLower);
Future getCountryList() async { final response = await http.Client().get(Uri.parse(urlCountry)); if (response.statusCode == 200) {
List countries = json.decode(response.body); return countries.map((e) => CountryModel.fromJson(e)).toList();
} else { throw Exception('Unexpected error occured!');
Future getCityList() async { final response = await http.Client().get(Uri.parse(urlCity)); if (response.statusCode == 200) {
List city = json.decode(response.body); return city.map((e) => CountryModel.fromJson(e)).toList();
} else { throw Exception('Unexpected error occured!');
Xây dựng chức năng nhập vào ô tìm kiếm covid quốc gia hiện từ liên quan
TypeAheadFormField( textFieldConfiguration: TextFieldConfiguration( controller: this._typeAheadController, decoration: InputDecoration( hintText: 'Nhập tên quốc gia', hintStyle: TextStyle(fontSize: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide( width: 0, style: BorderStyle.none,
Icons.search, color: Colors.black, size: 28,
), suggestionsCallback: CovidService.getCountrySummary, itemBuilder: (context, CountryModel? suggestion) { final country = suggestion!; return ListTile( title: Text(country.country),
(context, suggestionsBox, controller) { return suggestionsBox;
}, onSuggestionSelected: (CountryModel? suggestion) { this._typeAheadController.text suggestion?.country ?? ''; setState(() { summaryList = CovidService.getCountrySummary(
//snapshot.data!.firstWhere((e) => e.country == suggestion).query suggestion!.country);
Kết quả
Hình ảnh 23: Kết quả của giao diện covid 19
[1] Lập trình ứng dụng mã nguồn mở, ThS Trương Thị Giang Giang.
[2] Tài liệu học lập trình Flutter trên trang web https://docs.flutter.dev/
[3] Tài liệu học lập trình Flutter trên trang web https://dart.dev/
Ý KIẾN CỦA NGƯỜI HƯỚNG DẪN
Đối với khóa luận tốt nghiệp (Đánh dấu và ký tên vào ý kiến chọn lựa sau):
Ký tên Đồng ý thông qua báo cáo
Không đồng ý thông qua báo cáo
Buôn Ma Thuột, ngày tháng năm 2021
(Ký và ghi rõ họ tên)