신승호. 2023. 3. 12. 21:11
  • Getx AuthController 강의를 보고 따라서 제작하시오.

 

  • AuthController에는 User의 정보만을 담고있다. 로그인을 하면 유저를 식별할 수 있는 Token 값도 함께 받아볼 수 있는데, 해당 Token 값을 AuthController 내에 저장할 수 있도록 하고, 코드를 제시하시오.
    • 해당 API의 정보는 다음과 같다
      • API URL
      • // <http://52.79.115.43:8090/api/collections/users/auth-with-password>
      • API Request
      • // Method : POST // data : identity(String), password(String) // Teddy/sfac12341234
      • API Response
{
  "token": "JWT_TOKEN",
  "record": {
    "id": "RECORD_ID",
    "collectionId": "_pb_users_auth_",
    "collectionName": "users",
    "created": "2022-01-01 01:00:00Z",
    "updated": "2022-01-01 23:59:59Z",
    "username": "username123",
    "verified": false,
    "emailVisibility": true,
    "email": "test@example.com",
    "name": "test",
    "avatar": "filename.jpg"
  }
}

 

 

  • MainController에는 readDocuments라는 멤버 함수(메서드)를 제작하시오.
    • 해당 API의 정보는 다음과 같다 이 때, AuthController를 find하여 Token값이 존재하면 (로그인 되었다면) 실행할 수 있도록 한다.
      • API URL
      • // <http://52.79.115.43:8090/api/collections/documents/records>
      • API Request (필수)
    • // Method: GET
      // 해당 API는 인증된 사용자만 사용할 수 있기 때문에
      // 로그인 시 획득한 Token을 반드시 Request 헤더에 Authorization을 포함시켜야만합니다.

      • API Response (성공시) 
  • 위 API 정보를 토대로 응답 데이터형식에 맞게 Document 커스텀 클래스를 제작하고, MainPage의 Home이 다음과 같이 출력되도록 한다.

  • 아래 FAB를 누르면 readDocuments를 실행하고 결과를 화면에 출력한다.
    • document리스트는 MainController 멤버변수로 저장한다.
  • 다음의 제공되는 코드를 사용할 수 있도록 한다.
    • /lib/model/document.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first, non_constant_identifier_names

class Document {
  String title;
  String content;
  String sec_level;
  String? attachment_url;
  Document({
    required this.title,
    required this.content,
    required this.sec_level,
    this.attachment_url,
  });

  factory Document.fromMap(Map<String, dynamic> map) {
    return Document(
      title: map['title'] as String,
      content: map['content'] as String,
      sec_level: map['sec_level'] as String,
      attachment_url:
          map['attachment_url'] != '' ? map['attachment_url'] : null,
    );
  }
}

 

 

 

 

lib/controller/auth_controller.dart

import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:myapp32/model/user.dart';
import 'package:myapp32/util/api_routes.dart';
import 'package:myapp32/util/app_routes.dart';

class AuthController extends GetxController {
  final Rxn<User> _user = Rxn();
  Dio dio = Dio();
  String? _token;

  User? get user => _user.value; //user를 불러오면 _user의 value를 전달해
  String? get token => _token; //토큰 전달

  login(String id, String pw) async {
    try {
      var res = await dio.post(
          'http://52.79.115.43:8090${ApiRoutes.authWithPassword}',
          data: {'identity': id, 'password': pw}); //데이터따로관리해준
      if (res.statusCode == 200) {
        var user = User.fromMap(res.data['record']);
        _user(user); //유저정보 저장
        _token = res.data['token']; //토큰저장
      }
    } on DioError catch (e) {
      print(e.message);
    }
  }

  logout() {
    _user.value = null;
  }

  _handleAuthChanged(User? data) {
    if (data != null) {
      //메인페이지 이동
      Get.toNamed(AppRoutes.main);
      return;
    }
    //로그인페이지 이동
    Get.toNamed(AppRoutes.login);
    return;
  }

  @override
  void onInit() {
    super.onInit();
    ever(_user, _handleAuthChanged);
  }
}

token...어케하지 눈치것 썼는데..

 

lib/controller/login_controller.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/auth_controller.dart';

class Logincontroller extends GetxController {
  var idController = TextEditingController();
  var pwController = TextEditingController();

  login() {
    Get.find<AuthController>().login(idController.text, pwController.text);
    //Authcontroller는 전역에서 사용가능하기 때문에 find할수있게됨
  }
}

lib/controller/main_controller.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/auth_controller.dart';
import 'package:myapp32/model/document.dart';
import 'package:myapp32/util/api_routes.dart';

class MainController extends GetxController {
  var pageController = PageController();
  RxInt curPage = 0.obs;
  RxList<Document> documents = RxList();
  Dio dio = Dio();

  onPageTapped(int v) {
    pageController.jumpToPage(v);
    curPage(v);
  }

  logout() {
    Get.find<AuthController>().logout();
  }

  readDocuments() async {
    dio.options.baseUrl = 'http://52.79.115.43:8090';
    var token = Get.find<AuthController>().token;
    try {
      documents.clear();
      var res = await dio.get(ApiRoutes.documents,
          options: Options(headers: {'authorization': token}),
          data: {token: token});
      if (res.statusCode == 200) {
        List<Map<String, dynamic>> data =
            List<Map<String, dynamic>>.from(res.data['items']);
        documents.addAll(data.map((e) => Document.fromMap(e)).toList().obs);
      }
    } on DioError catch (e) {
      print(e.message);
    }
  }
}

lib/model/document.dart 고대로 사용함

// ignore_for_file: public_member_api_docs, sort_constructors_first, non_constant_identifier_names

class Document {
  String title;
  String content;
  String sec_level;
  String? attachment_url;
  Document({
    required this.title,
    required this.content,
    required this.sec_level,
    this.attachment_url,
  });

  factory Document.fromMap(Map<String, dynamic> map) {
    return Document(
      title: map['title'] as String,
      content: map['content'] as String,
      sec_level: map['sec_level'] as String,
      attachment_url:
          map['attachment_url'] != '' ? map['attachment_url'] : null,
    );
  }
}

lib/model/user.dart

// ignore_for_file: public_member_api_docs, sort_constructors_first, non_constant_identifier_names

class Document {
  String title;
  String content;
  String sec_level;
  String? attachment_url;
  Document({
    required this.title,
    required this.content,
    required this.sec_level,
    this.attachment_url,
  });

  factory Document.fromMap(Map<String, dynamic> map) {
    return Document(
      title: map['title'] as String,
      content: map['content'] as String,
      sec_level: map['sec_level'] as String,
      attachment_url:
          map['attachment_url'] != '' ? map['attachment_url'] : null,
    );
  }
}

lib/util/api_routes.dart

//Api가 계속 늘어날수도 있기때문에 따로 관리해줌

class ApiRoutes {
  static const String authWithPassword =
      '/api/collections/users/auth-with-password';
  static const String documents = '/api/collections/documents/records';
}

lib/util/app_routes.dart

import 'package:myapp32/view/login_page.dart';
import 'package:myapp32/view/main_page.dart';

class AppRoutes {
  static const main = MainPage.route; //메인 페이지 라우트
  static const login = LoginPage.route; //로그인 페이지 라우트
}

lib/util/apppages.dart

import 'package:get/get.dart';
import 'package:myapp32/util/app_routes.dart';
import 'package:myapp32/view/login_page.dart';
import 'package:myapp32/view/main_page.dart';

class AppPages {
  static final pages = [
    GetPage(name: AppRoutes.main, page: () => const MainPage()),
    GetPage(name: AppRoutes.login, page: () => const LoginPage()),
  ];
}

lib/view/main_page.dart

// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/auth_controller.dart';
import 'package:myapp32/controller/main_controller.dart';
import 'package:myapp32/view/homescreen.dart';
import 'package:myapp32/view/myscreen.dart';

class MainPage extends GetView<MainController> {
  const MainPage({super.key});
  static const String route = '/main';

  @override
  Widget build(BuildContext context) {
    var user = Get.find<AuthController>().user;
    return Scaffold(
      body: SafeArea(
          child: PageView(
        controller: controller.pageController,
        children: [
          //Home페이지
          Obx(() =>
              HomeScreen(document: controller.documents.toList(), user: user!)),
          //My페이지
          MyScreen(user: user!, onTap: controller.logout)
        ],
      )),
      bottomNavigationBar: Obx(
        () => BottomNavigationBar(
            currentIndex: controller.curPage.value,
            onTap: controller.onPageTapped,
            items: [
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.home,
                  ),
                  label: 'Home'),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.person,
                  ),
                  label: 'My')
            ]),
      ),
      floatingActionButton: Obx((() {
        if (controller.curPage.value == 0) {
          return FloatingActionButton(
            onPressed: controller.readDocuments,
            child: Icon(Icons.refresh),
          );
        }
        return SizedBox();
      })),
    );
  }
}

lib/view/login_page.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/login_controller.dart';

class LoginPage extends GetView<Logincontroller> {
  const LoginPage({super.key});
  static const String route = '/login';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: controller.idController,
            ),
            TextField(
              controller: controller.pwController,
            ),
            ElevatedButton(onPressed: controller.login, child: Text('로그인하기'))
          ],
        ),
      ),
    );
  }
}

lib/view/homescreen

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/login_controller.dart';

class LoginPage extends GetView<Logincontroller> {
  const LoginPage({super.key});
  static const String route = '/login';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: controller.idController,
            ),
            TextField(
              controller: controller.pwController,
            ),
            ElevatedButton(onPressed: controller.login, child: Text('로그인하기'))
          ],
        ),
      ),
    );
  }
}

lib/view/myscreen

import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/auth_controller.dart';
import 'package:myapp32/model/user.dart';

class MyScreen extends StatelessWidget {
  const MyScreen({
    super.key,
    required this.user,
    required this.onTap,
  });

  final User user;
  final VoidCallback onTap; //입력 인자가 없는 함수를 호출할 수 있는 타입

  @override
  Widget build(BuildContext context) {
    var user = Get.find<AuthController>().user!;
    return Scaffold(
        body: //My페이지
            Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
      ListTile(
        title: Text(user.username),
        subtitle: Text(user.name),
      ),
      ListTile(
        title: Text('로그아웃'),
        leading: Icon(Icons.logout),
        onTap: onTap,
      ),
    ]));
  }
}

main.dart

// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myapp32/controller/auth_controller.dart';
import 'package:myapp32/controller/login_controller.dart';
import 'package:myapp32/controller/main_controller.dart';
import 'package:myapp32/util/app_routes.dart';
import 'package:myapp32/util/apppages.dart';
import 'package:myapp32/view/login_page.dart';
import 'package:myapp32/view/main_page.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialBinding: BindingsBuilder(() {
        Get.put(AuthController()); //initialBinding이 실행되면서 Get.put이실행됨
        Get.lazyPut(() => Logincontroller());
        Get.lazyPut(
          () => MainController(),
        );
      }),
      getPages: AppPages.pages,
      home: Scaffold(
        body: Center(
          child: TextButton(
            onPressed: () => Get.toNamed(AppRoutes.login),
            child: Text('로그인페이지로 가실?'),
          ),
        ),
      ),
    );
  }
}

결과가 나왔당

어려운데 할만한거 같기도 하고...

내용정리를 아직못해서  시간이 더 걸릴거다..