Flutter cơ bản-Gửi nhận dữ liệu với post và get kèm JWT tới Web API
Đăng lúc: 02:20 PM - 26/06/2024 bởi Charles Chung - 124Trong bài này tôi sẽ hướng dẫn các bạn gọi Web API login và nhận token, sau đó gửi các request kèm token tới Web API để nhận dữ liệu.
![](\images\posts\041852-25062024-post-get-api-jwt-flutter.jpg)
1. Chuẩn bị Web API
- Địa chỉ https://api.hanam88.com cung cấp 1 loạt api để các bạn có thể thực hành việc gọi api để sử dụng trên các nền tảng khác nhau, tài liệu api tham khảo tạo đây: https://drive.google.com/file/d/1DEZGmPYQ94t-vP-AliIwSPDx2mPCrc4Z/view
- Hãy đọc tài liệu và gọi một số api như sau
- Login với tài khoản sinhvien01/123465 để nhận thông tin tài khoản và token bearer (cấu trúc json đọc tài liệu)
- Lấy danh sách lớp (cấu trúc json đọc tài liệu)
- Lấy danh sách sinh viên theo lớp (cấu trúc json đọc tài liệu)
2. Yêu cầu
- Ứng dụng khởi động xong sẽ vào màn hình Login, tại đây người dùng nhập username và password như trên để đăng nhập,
- Nếu có lỗi thì xuất thông báo lỗi khi không thành công.
- Nếu thành công chuyển qua màn hình HomePage, lưu thông tin người dùng và token vào SharedPreference để dùng sau đó (lưu ý tại HomePage không thể quay lại Login được trừ khi chọn Đăng xuất)
- Tại màn hình HomePage
- Lấy danh sách lớp từ API về và hiển thị lên DropDown để chọn.
- Khi người dùng chọn 1 lớp từ DropDown thì hiển thị danh sách sinh viên thuộc lớp đó xuống ListView.
- Khi người dùng kích vào biểu tượng = tại góc trên trái màn hình HomePage thì hiển thị Drawer với ảnh Avatar, Họ và tên và 2 menu "Cấu hình", "Thoát" như hình, nếu người dùng kích vào thoát thì tài lại màn hình Login
3. Các bước thực hiện
Bước 1: Tạo mới Flutter Project với tên "consumingapi"
Bước 2: Mở file pubspec.yaml khai báo thêm dependencies như hình dưới
Bước 3: Tạo các thư mục và tệp tin như hình dưới
- Lớp Account: lớp này biểu diễn dữ liệu tài khoản, nó sử dụng để tạo đối tượng chứa thông tin tài khoản khi đăng nhập thành công.
class Account { String accountId; String username; String fullname; String avatar; String role; String accesstoken; String refreshtoken; DateTime expiryminisecond; Account(this.accountId, this.username, this.fullname, this.avatar, this.role, this.accesstoken, this.refreshtoken, this.expiryminisecond); factory Account.fromJson(Map<String, Object?> json) { return Account( json['accountId'].toString(), json['username'].toString(), json['fullname'].toString(), json['avatar'].toString(), json['role'].toString(), json['accesstoken'].toString(), json['refreshtoken'].toString(), DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecond+int.parse(json['expiryminisecond'].toString())) ); } }
- Lớp ClassRoom: lớp này biểu diễn dữ liệu lớp học, nó sử dụng để tạo đối tượng chứa thông tin lớp học.
class ClassRoom { int classId; String className; ClassRoom(this.classId, this.className); factory ClassRoom.fromJson(Map<String, Object?> data){ return ClassRoom(int.parse(data['classId'].toString()), data['className'].toString()); } Map<String, Object?> toMap(){ return { "classId":classId, "className":className }; } }
- Lớp Student: lớp này biểu diễn dữ liệu sinh viên, nó sử dụng để tạo đối tượng chứa thông tin sinh viên.
class Student { String studentId; String firstName; String lastName; String email; DateTime birthday; bool sex; String picture; String phone; String note; int classId; int status; bool hasAccount; Student( this.studentId, this.firstName, this.lastName, this.email, this.birthday, this.sex, this.picture, this.phone, this.note, this.classId, this.status, this.hasAccount); factory Student.fromJson(Map<String, Object?> data) { return Student( data['studentId'].toString(), data['firstName'].toString(), data['lastName'].toString(), data['email'].toString(), DateTime.parse(data['birthday'].toString()), bool.parse(data['sex'].toString()), data['picture'].toString(), data['phone'].toString(), data['note'].toString(), int.parse(data['classId'].toString()), int.parse(data['status'].toString()), bool.parse(data['hasAccount'].toString())); } Map<String, Object?> toMap() { return { "studentId": studentId, "firstName": firstName, "lastName": lastName, "email": email, "birthday": birthday, "sex": sex, "picture": picture, "phone": phone, "note": note, "classId": classId, "status": status, "hasAccount": hasAccount }; } }
- Màn hình login
import 'dart:convert'; import 'package:consumeapi/models/Account.dart'; import 'package:consumeapi/screens/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; class LoginScreen extends StatelessWidget { String uri = "https://api.hanam88.com/quiz/accounts/login"; final _keys = GlobalKey<FormState>(); final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.fromLTRB(20, 50, 20, 20), child: SingleChildScrollView( child: Form( key: _keys, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Image.network( 'https://cdn-icons-png.flaticon.com/512/295/295128.png', width: 200), TextFormField( controller: _usernameController, decoration: const InputDecoration( hintText: 'email hoặc username', labelText: 'Tên đăng nhập', ), validator: (value) { if (value == null || value.isEmpty) { return 'Hãy nhập tên đăng nhập'; } return null; }, ), const SizedBox(height: 20.0), TextFormField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( hintText: 'password', labelText: 'Mật khẩu', ), validator: (value) { if (value == null || value.isEmpty) { return 'Hãy nhập mật khẩu'; } return null; }), const SizedBox(height: 20.0), ElevatedButton( onPressed: () { String username = _usernameController.text; String password = _passwordController.text; Map<String, String> user = { "username": username, "password": password }; Map<String, String> headers = <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', }; if (_keys.currentState!.validate()) { http .post(Uri.parse(uri), headers: headers, body: jsonEncode(user)) .then((value) async { var data = jsonDecode( const Utf8Decoder().convert(value.bodyBytes)); if (data['msg'] != null) { // Show dialog showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Thông báo lỗi'), content: Text(data['msg']), actions: <Widget>[ TextButton( child: const Text('Đóng'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }); } else { var acc = Account.fromJson(data); var prefs = await SharedPreferences.getInstance(); prefs.setString("avatar", acc.avatar); prefs.setString("fullname", acc.fullname); prefs.setString("accountid", acc.accountId); prefs.setString("accesstoken", acc.accesstoken); if (!context.mounted) return; Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomeScreen())); } }); } }, child: Text('Đăng nhập'), ), ], ), ), ), ), ); } }
- Màn hình HomePage
import 'dart:convert'; import 'package:consumeapi/models/ClassRoom.dart'; import 'package:consumeapi/models/Student.dart'; import 'package:consumeapi/screens/login_screen.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; class HomeScreen extends StatefulWidget { @override State<StatefulWidget> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { final String _domain = "https://api.hanam88.com"; final String _uri = "https://api.hanam88.com/quiz"; Map<String, String> headers = <String, String>{}; String? _fullname; String? _avatar; String? _accesstoken; List<ClassRoom> classRooms = []; String? _classId; List<Student> students = []; @override void initState() { super.initState(); _loadAccount(); } Future<void> _loadAccount() async { final pref = await SharedPreferences.getInstance(); setState(() { _fullname = pref.getString("fullname"); _avatar = pref.getString("avatar"); _accesstoken = pref.getString("accesstoken"); }); headers = { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer $_accesstoken' }; http.get(Uri.parse('$_uri/classrooms'), headers: headers).then((value) { var data = jsonDecode(const Utf8Decoder().convert(value.bodyBytes)) as List; setState(() { classRooms = data.map((e) => ClassRoom.fromJson(e)).toList(); }); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'hanam88', home: Scaffold( appBar: AppBar( title: const Text('DANH SÁCH SINH VIÊN'), backgroundColor: Colors.red, ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( color: Colors.lightGreen, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ const Text('Chọn lớp'), DropdownButtonHideUnderline( child: DropdownButton<String>( isExpanded: false, hint: const Text('-----------'), items: classRooms .map((ClassRoom item) => DropdownMenuItem<String>( value: item.classId.toString(), child: Text( item.className, ), )) .toList(), value: _classId, onChanged: (String? value) { setState(() { _classId = value; }); http .get( Uri.parse( '$_uri/students/by-class/$_classId'), headers: headers) .then((value) { var data = jsonDecode(const Utf8Decoder() .convert(value.bodyBytes)) as List; // print(data); setState(() { students = data.map((e) => Student.fromJson(e)).toList(); }); }); }, ), ) ], ), ), Expanded( child: ListView.builder( itemCount: students.length, itemBuilder: (context, index) { final st = students[index]; return ListTile( leading: st.picture.isEmpty ? Image.network( 'https://d1nhio0ox7pgb.cloudfront.net/_img/g_collection_png/standard/512x512/user.png') : Image.network('$_domain${st.picture}'), title: Text('${st.firstName} ${st.lastName}'), subtitle: Text(st.studentId), trailing: Text(st.sex ? "Nam" : "Nữ"), ); })) ], ), ), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ DrawerHeader( decoration: const BoxDecoration(color: Colors.red), child: CircleAvatar( backgroundImage: NetworkImage('https://api.hanam88.com$_avatar'), child: Text('$_fullname'), )), ListTile( title: const Text('Cấu hình'), onTap: () { // Navigator.push(context, MaterialPageRoute(builder: (context)=>MyChildPage('Cấu hình'))); }, ), ListTile( title: const Text('Đăng xuất'), onTap: () { // Tự gọi api logout nhé // ........... // Chuyển qua màn hình login Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginScreen())); }, ) ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { // Navigator.of(context) // .push(MaterialPageRoute(builder: (context) => FormAdd(classRooms: classRooms,))); }, child: const Icon(Icons.add_box)), ), debugShowCheckedModeBanner: false, ); } }
- Tệp main.dart
import 'package:consumeapi/screens/login_screen.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Hanam88', home: LoginScreen(), debugShowCheckedModeBanner: false, ); } }
- Khởi động Android Emulator và Run ứng dụng
Video demo
Chúc bạn code vui vẻ!
thay lời cảm ơn!
Các bài cũ hơn
- Flutter cơ bản-Sử dụng font chữ (11:49 AM - 26/06/2024)
- Flutter cơ bản-Thiết kế màn hình hiển thị sản phẩm giống trang Shopee (07:58 PM - 24/06/2024)
- Flutter cơ bản-Thiết kế màn hình hồ sơ cá nhân (10:17 PM - 23/06/2024)
- Flutter cơ bản-Thiết kế các màn hình LogIn-Forgot Password-SignUp trong Flutter (09:05 AM - 22/06/2024)
- Flutter cơ bản-Hiển thị cấu trúc các widget trong Flutter-Android Studio (09:15 AM - 21/06/2024)