openplanning

Ví dụ đăng nhập và đăng xuất với Flutter Getx

  1. Cài đặt thư viện
  2. MainApp
  3. Model
  4. Login
  5. Dashboard
  6. AuthMiddleware
  7. Logout
Trong bài viết này chúng ta sẽ xem xét một ví dụ đăng nhập và đăng xuất với Flutter Getx.
  • Tạo một Form đăng nhập.
  • Sử dụng Middleware để kiểm tra và chuyển hướng người dùng tới trang đăng nhập nếu người dùng đang cố gắng truy cập vào một trang nào đó trong tình trạng chưa đăng nhập.
Các bài viết và ví dụ có liên quan tới GetX Middleware:

1. Cài đặt thư viện

pubspec.yaml
dependencies: 
  get: ^5.0.0-release-candidate-9
  shared_preferences: ^2.3.2
Chú ý: Thư viện shared_preferences được sử dụng để lưu trữ thông tin người dùng trên thiết bị sau khi người dùng đăng nhập thành công. Thông tin này là cơ sở để biết người dùng đã đăng nhập hay chưa.

2. MainApp

main.dart
import 'package:flutter/material.dart';
import 'user_controller.dart';
import 'package:get/get.dart';

import 'auth_middleware.dart';
import 'dashboard_screen.dart';
import 'login_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter GetX Login Demo',
      debugShowCheckedModeBanner: false,
      initialRoute: DashboardScreen.routeName,
      getPages: [
        GetPage(
          name: LoginScreen.routeName,
          page: () => const LoginScreen(),
        ),
        GetPage(
          name: DashboardScreen.routeName,
          page: () => const DashboardScreen(),
          middlewares: [
            AuthMiddleware(), // IMPORTANT!!
          ],
        ),
      ],
      binds: [
        // Make sure UserController is available for all Pages.
        Bind.lazyPut(() => UserController(), fenix: true),
      ],
    );
  }
}

3. Model

model.dart
import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

class User {
  static const String _key_ = "__userInfo__";

  String userName;
  String fullName;
  String role;

  User({
    required this.userName,
    required this.fullName,
    required this.role,
  });

  String toJson() {
    return jsonEncode({
      "userName": userName,
      "fullName": fullName,
      "role": role,
    });
  }

  void saveToSharedPreferences(SharedPreferences sp) {
    print("--> saveToSharedPreferences");
    String json = toJson();
    sp.setString(_key_, json);
  }

  void removeFromSharedPreferences(SharedPreferences sp) {
    print("--> removeToSharedPreferences");
    sp.remove(_key_);
  }

  static User fromJson(String json) {
    Map<String, dynamic> map = jsonDecode(json);
    return fromMap(map);
  }

  static User fromMap(Map<String, dynamic> map) {
    return User(
      userName: map['userName'],
      fullName: map['fullName'],
      role: map['role'],
    );
  }

  static User? fromSharedPreferences(SharedPreferences sp) {
    print("--> fromSharedPreferences");
    String? json = sp.getString(_key_);
    if (json == null) {
      return null;
    }
    return fromJson(json);
  }
}
user_controller.dart
import 'package:get/get.dart';

import 'model.dart';

class UserController extends GetxController {
  // Logged-In user
  User? user;
}

4. Login

Người dùng nhận được một thông báo lỗi khi nhập sai tên đăng nhập hoặc mật khẩu:
Khi đăng nhập thành công, người dùng sẽ được chuyển tới DashboardScreen.
login_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'dashboard_screen.dart';
import 'login_controller.dart';

class LoginScreen extends StatelessWidget {
  static const String routeName = "/login";

  const LoginScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login"),
        backgroundColor: Colors.blue,
      ),
      body: GetBuilder(
        init: Get.put(LoginController()),
        builder: (LoginController controller) {
          return Padding(
            padding: const EdgeInsets.all(10),
            child: Column(
              children: [
                TextField(
                  controller: controller.userNameController,
                  decoration: const InputDecoration(
                    label: Text('User Name'),
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 10),
                TextField(
                  controller: controller.passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                    label: Text('Password'),
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 10),
                const Text("Test with user/pass: tom/123"),
                const SizedBox(height: 10),
                ElevatedButton(
                  onPressed: () {
                    controller.doLogin();
                  },
                  child: const Text("Login"),
                ),
                const SizedBox(height: 10),
                ElevatedButton(
                  onPressed: () {
                    Get.toNamed(DashboardScreen.routeName);
                  },
                  child: const Text("Go to Dashboard Screen"),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
login_controller.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'dashboard_screen.dart';
import 'model.dart';
import 'user_provider.dart';

class LoginController extends GetxController {
  final UserProvider userProvider = Get.put(UserProvider());

  final TextEditingController userNameController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();

  Future<void> doLogin() async {
    String userName = userNameController.text;
    String password = passwordController.text;
    try {
      User? user = await userProvider.doLogin(userName, password);

      SharedPreferences sp = await SharedPreferences.getInstance();
      user!.saveToSharedPreferences(sp);
      Get.toNamed(DashboardScreen.routeName);
    } catch (e, stacktrace) {
      // print(stacktrace);
      Get.showSnackbar(
        GetSnackBar(
          message: "Error: $e",
          duration: const Duration(seconds: 5),
        ),
      );
    }
  }

  @override
  void dispose() {
    userNameController.dispose();
    passwordController.dispose();
    super.dispose();
  }
}
UserProvider là một lớp "Provider". Provider thực hiện các hoạt động CRUD (Create, Read, Update, Delete), nó gửi các yêu cầu GET, POST, PUTDELETE tới máy chủ REST.
user_provider.dart
import 'model.dart';

class UserProvider {
  Future<User?> doLogin(String userName, String password) async {
    // Send POST request to server...
    if (userName == 'tom' && password == '123') {
      return User(
        userName: 'tom',
        fullName: 'Tom Cat',
        role: 'ADMIN',
      );
    }
    throw "Invalid userName or password";
  }
}

5. Dashboard

DashboardScreen hiển thị thông tin người dùng khi người dùng đã đăng nhập thành công.
dashboard_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'logout_controller.dart';
import 'user_controller.dart';

class DashboardScreen extends StatelessWidget {
  static const String routeName = "/dashboard";

  const DashboardScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Dashboard"),
        backgroundColor: Colors.blue,
      ),
      body: GetBuilder(
        builder: (UserController controller) {
          return Center(
            child: Column(
              children: [
                Text("Hello: ${controller.user!.userName}"),
                Text("Full Name: ${controller.user!.fullName}"),
                Text("Role: ${controller.user!.role}"),
                const SizedBox(height: 10),
                TextButton(
                  onPressed: _doLogout,
                  child: const Text("Logout"),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  void _doLogout() {
    LogoutController logoutController = Get.put(LogoutController());
    logoutController.doLogout();
  }
}

6. AuthMiddleware

Middleware là một thực thể logic nằm giữa người dùng và trang mục tiêu mà người dùng mong muốn. Code của Middleware sẽ được thực thi trước khi yêu cầu của người dùng được gửi tới trang mục tiêu. Bạn có thể sử dụng Middleware để chuyển hướng người dùng tới một trang khác tuỳ thuộc vào các điều kiện được xác định trong Middleware.
main.dart (**)
GetMaterialApp( 
  ...
  getPages: [ 
    ...
    GetPage(
      name: DashboardScreen.routeName,
      page: () => const DashboardScreen(),
      middlewares: [
        AuthMiddleware(), // IMPORTANT!!
      ],
    ),
  ],
  ...
)
Trong ví dụ này người dùng cố gắng truy cập vào DashboardScreen. AuthMiddleware sẽ kiểm tra xem người dùng đã đăng nhập chưa, nếu chưa yêu cầu sẽ được chuyển hướng tới LoginScreen.
Chú ý: Khi người dùng đã đăng nhập thành công, thông tin người dùng sẽ được lưu vào SharedPreferences. AuthMiddleware dựa vào điều này để kiểm tra xem người dùng đã đăng nhập hay chưa.
auth_middleware.dart
import 'dart:async';

import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'login_screen.dart';
import 'model.dart';
import 'user_controller.dart';

class AuthMiddleware extends GetMiddleware {
  @override
  FutureOr<RouteDecoder?> redirectDelegate(RouteDecoder route) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    try {
      User? user = User.fromSharedPreferences(sp);
      if (user == null) {
        print(" Not logged in --> redirect to login page");
        return RouteDecoder.fromRoute(LoginScreen.routeName);
      }
      // UserController was created in GetMaterialApp (** @see: main.dart)
      UserController userController = Get.find();
      userController.user = user!;
    } catch (e, stacktrace) {
      print(stacktrace);
      print(" Error: --> redirect to login page");
      return RouteDecoder.fromRoute(LoginScreen.routeName);
    }
    return super.redirectDelegate(route);
  }
}
Xem thêm bài viết chuyên sâu về Getx Middleware:
  • Hướng dẫn và ví dụ Flutter Getx middleware

7. Logout

void _doLogout() {
  LogoutController logoutController = Get.put(LogoutController());
  logoutController.doLogout();
}
logout_controller.dart
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'login_screen.dart';
import 'model.dart';
import 'user_controller.dart';

class LogoutController extends GetxController {
  Future<void> doLogout() async {
    UserController? userController = Get.findOrNull();
    userController?.user = null;
    //
    SharedPreferences sp = await SharedPreferences.getInstance();
    User? user = User.fromSharedPreferences(sp);
    user?.removeFromSharedPreferences(sp);
    // Go to LoginScreen and remove route histories.
    Get.offNamed(LoginScreen.routeName);
  }
}

Các hướng dẫn lập trình Flutter

Show More