1. Flutter là gì? Flutter là mobile UI framework của Google để tạo ra các giao diện chất lượng cao trên iOS và Android trong khoảng thời gian ngắn. Flutter hoạt động với những code sẵn có được sử dụng bởi các lập trình viên, các tổ chức. Flutter hoàn toàn miễn phí và cũng […]
1. Flutter là gì?
- Flutter là mobile UI framework của Google để tạo ra các giao diện chất lượng cao trên iOS và Android trong khoảng thời gian ngắn. Flutter hoạt động với những code sẵn có được sử dụng bởi các lập trình viên, các tổ chức.
- Flutter hoàn toàn miễn phí và cũng là mã nguồn mở.
2. Tại sao lại dùng Flutter?
- Nếu bạn đang tìm kiếm các phương pháp thay thế để phát triển ứng dụng Android, bạn nên cân nhắc thử Flutter của Google, một framework dựa trên ngôn ngữ lập trình Dart.
- Các ứng dụng được xây dựng với Flutter hầu như không thể phân biệt với những ứng dụng được xây dựng bằng cách sử dụng Android SDK, cả về giao diện và hiệu suất. Hơn nữa, với những tinh chỉnh nhỏ, chúng có thể chạy trên thiết bị iOS.
3. Tài liệu
bạn có thể tham khảo hướng dẫn tại đây
4 xây dựng ứng dụng đầu tiên
các bước cài đặt bạn có thể làm theo docs , mình không hướng dẫn lại tại đây (hihi)
Tạo dự án với các file mẫu có cấu trúc như sau
File api/api_list.dart cấu hình đường dẫn các đường dẫn api vào đây
class APIS {
static final String _baseUrl = "https://dummyjson.com";
static var postList = "$_baseUrl/products";
}
File components/bottomNav.dat , file này để làm cái menu dưới chân app
import 'package:flutter/material.dart';
import 'package:helloworld/screens/profileScreen.dart';
import 'package:helloworld/screens/homeScreen.dart';
class BottomNav extends StatefulWidget {
const BottomNav({Key? key}) : super(key: key);
@override
State createState() => _BottomNav();
}
int _selectedIndex = 0;
class _BottomNav extends State {
@override
Widget build(BuildContext context) {
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
switch (_selectedIndex) {
case 0:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
break;
case 1:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfileScreen()),
);
break;
}
}
return BottomNavigationBar(
currentIndex: _selectedIndex, // this will be set when a new tab is tapped
onTap: _onItemTapped,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.grey,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
)
],
);
}
}
File components/reusbaleRow.dat file này để định dạng lại các dữ liệu chi tiết
import 'package:flutter/material.dart';
class ReusbaleRow extends StatelessWidget {
String title , value ;
ReusbaleRow({Key? key , required this.title , required this.value}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title),
Text(value ),
],
),
);
}
}
File models/article.dart để định nghĩa cấu trúc dữ liệu api trả về
class Article {
final int id;
final String title;
final String description;
final double price;
final double discountPercentage;
final double rating;
final int stock;
final String brand;
final String category;
final String thumbnail;
Article(
this.id,
this.title,
this.description,
this.price,
this.discountPercentage,
this.rating,
this.stock,
this.brand,
this.category,
this.thumbnail
);
Article.fromJsonMap(Map map):
id = map["id"],
title = map["title"],
description = map["description"],
price = map["price"].toDouble(),
discountPercentage = map["discountPercentage"].toDouble(),
rating = map["rating"].toDouble(),
stock = map["stock"],
brand = map["brand"],
category = map["category"],
thumbnail = map["thumbnail"];
Map toJson() {
final Map data = new Map();
data['id'] = id;
data['title'] = title;
data['description'] = description;
data['price'] = price;
data['discountPercentage'] = discountPercentage;
data['rating'] = rating;
data['stock'] = stock;
data['brand'] = brand;
data['category'] = category;
data['thumbnail'] = thumbnail;
return data;
}
}
File screens/homeScreen.dart là dành cho màn hình trang chủ
import 'package:flutter/material.dart';
import 'package:helloworld/screens/detailPostScreen.dart';
import 'package:helloworld/models/article.dart';
import 'package:helloworld/components/bottomNav.dart';
import 'package:http/http.dart' as http;
import 'package:helloworld/api/api_list.dart';
import 'dart:async';
import 'dart:convert';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State createState() => _HomeScreen();
}
class _HomeScreen extends State {
List _data = [];
Future getPosts() async {
final response = await http.get(Uri.parse(APIS.postList), headers: {"Accept": "application/json"});
var res = jsonDecode(response.body.toString());
final products = res['products'];
if(response.statusCode == 200) {
setState(() {
_data = products.map((data) => Article.fromJsonMap(data)).toList();
});
return "success";
}
return "failed";
}
@override
void initState() {
super.initState();
getPosts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
automaticallyImplyLeading: false
),
body: ListView.builder(
itemCount: _data == null ? 0 : _data.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: ListTile( //use ListTile to show a Todo
leading: Image.network(
_data[index].thumbnail.toString(),
width: 120.0,
fit: BoxFit.cover
),
title: Text(_data[index].title),
subtitle: Text(_data[index].description),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPostScreen(detail: _data[index]),
settings: RouteSettings(
arguments: _data[index].id
)
),
);
},
)
);
},
),
bottomNavigationBar: BottomNav(),
);
}
}
File screens/detailPostScreen.dart là để dành cho màn chi tiết bài viết
import 'package:flutter/material.dart';
import 'package:helloworld/models/article.dart';
import 'package:helloworld/api/api_list.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
import 'package:helloworld/components/reusbaleRow.dart';
class DetailPostScreen extends StatefulWidget {
final Article detail;
DetailPostScreen({super.key, required this.detail});
@override
State createState() => _DetailPostScreen(detail);
}
class _DetailPostScreen extends State {
// Declare a field that holds the Todo
Article detail;
_DetailPostScreen(this.detail);
Future getDetailPosts() async {
final response = await http.get(Uri.parse(APIS.postList + '/' + detail.id.toString()), headers: {"Accept": "application/json"});
var res = jsonDecode(response.body.toString());
if(response.statusCode == 200) {
setState(() {
detail = Article(
res['id'],
res['title'],
res['description'],
res['price'].toDouble(),
res['discountPercentage'].toDouble(),
res['rating'].toDouble(),
res['stock'],
res['brand'],
res['category'],
res['thumbnail']
);
});
return "success";
}
return "failed";
}
@override
void initState() {
super.initState();
getDetailPosts();
}
@override
Widget build(BuildContext context) {
// Use the Todo to create our UI
return Scaffold(
appBar: AppBar(
title: Text("${detail.title}"),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Image.network(
detail.thumbnail.toString(),
fit: BoxFit.cover
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 15),
child: Text(
'${detail.description}',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
)
),
ReusbaleRow(title: 'Price', value: detail.price.toString() + " dolar"),
ReusbaleRow(title: 'DiscountPercentage', value: detail.discountPercentage.toString() + ' %'),
ReusbaleRow(title: 'Rating', value: detail.rating.toString() + ' star'),
ReusbaleRow(title: 'Stock', value: detail.stock.toString()),
ReusbaleRow(title: 'Brand', value: detail.brand.toString()),
ReusbaleRow(title: 'Category', value: detail.category.toString())
],
),
),
);
}
}
File screens/profileScreen.dart để làm chức năng đăng nhập và nếu đăng nhập thì hiển thị thông tin của user
import 'package:flutter/material.dart';
import 'package:helloworld/components/bottomNav.dart';
import 'dart:convert';
import 'package:http/http.dart';
import 'package:localstorage/localstorage.dart';
import 'package:helloworld/components/reusbaleRow.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({Key ? key}) : super(key: key);
@override
State createState() => _ProfileScreen();
}
class _ProfileScreen extends State {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool isLogin = false;
void login(String email , password) async {
try{
print("email: " + email);
print("password: "+password);
Response response = await post(
Uri.parse('https://reqres.in/api/login'),
body: {
'email' : email,
'password' : password
}
);
if(response.statusCode == 200){
var data = jsonDecode(response.body.toString());
print("token: " + data['token']);
addItemsToLocalStorage(email, data['token']);
print('Login successfully');
setState(() {
isLogin = true;
});
}else {
print('failed');
}
}catch(e){
print(e.toString());
}
}
final LocalStorage storage = new LocalStorage('localstorage_app');
void addItemsToLocalStorage(email, token) {
final info = json.encode({'email': email, 'token': token});
storage.setItem('info', info);
}
dynamic getItemsInLocalStorage() {
return storage.getItem('info');
}
void logout(){
storage.deleteItem('info');
setState(() {
isLogin = false;
});
}
@override
void initState() {
super.initState();
final info = getItemsInLocalStorage();
if(info != null) setState(() {
isLogin = true;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
automaticallyImplyLeading: false
),
body: isLogin == false ? Center(
child: Container(
padding: const EdgeInsets.all(80.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Login',
style: Theme.of(context).textTheme.headline3,
),
TextFormField(
controller: emailController,
decoration: const InputDecoration(
hintText: 'Email',
),
),
SizedBox(height: 20,),
TextFormField(
controller: passwordController,
decoration: const InputDecoration(
hintText: 'Password',
),
obscureText: true,
),
const SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {
login(emailController.text.toString(), passwordController.text.toString());
},
style: ElevatedButton.styleFrom(
primary: Colors.yellow,
),
child: const Text('LOGIN'),
)
],
),
),
) : Center(
child: Container(
padding: const EdgeInsets.all(80.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ReusbaleRow(title: 'Email', value: '111'),
const SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {
logout();
},
style: ElevatedButton.styleFrom(
primary: Colors.yellow,
),
child: const Text('LOGOUT'),
)
],
),
),
),
bottomNavigationBar: BottomNav(),
);
}
}
Và file main.dart sẽ có nội dung như sau
import 'package:flutter/material.dart';
import 'package:helloworld/screens/profileScreen.dart';
import 'package:helloworld/screens/homeScreen.dart';
import 'dart:developer';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
log('Load success');
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/login': (context) => const ProfileScreen(),
}
);
}
}
Dùng IDE inteliJ chạy file main.dart lên với máy ảo iphone
Tham khảo code mẫu tại đây