CÔNG NGHỆ THÔNG TIN >> BÀI VIẾT CHỌN LỌC

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 - 255

Trong 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.

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!

QUẢNG CÁO - TIẾP THỊ