[flutter] 과제일지 day16: Auth

KoEunseo·2023년 10월 16일
0

flutter

목록 보기
26/45

Auth API

http://52.79.115.43:8090/api/collections/users/auth-with-password
Request Params : identity, password (Teddy/sfac12341234)
Response (200) : {
"token": "JWT_TOKEN",
"record": {
"id": "RECORD_ID",
"created": "2022-01-01 01:00:00Z",
"updated": "2022-01-01 23:59:59Z",
"username": String,
"email": String,
"name": String,
"avatar": String,
}
}

GetX AuthController

controller/auth_controller.dart

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

  User? get user => _user.value;

  login(String id, String pw) async {
    dio.options.baseUrl = 'http://52.79.115.43:8090';
    try {
      var res = await dio.post(ApiRoutes.authWithPassword, data: {
        'identity': id,
        'password': pw,
      });
      if (res.statusCode == 200) {
        var user = User.fromMap(res.data['record']);
        _user(user);
      }
    } on DioException catch (e) {
      print(e.message);
      print(e.requestOptions.path);
    }
  }

  logout() {
    _user.value = null;
  }

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

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

controller/login_controller.dart

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

  login() {
    Get.find<AuthController>().login(idController.text, pwController.text);
  }
}

controller/main_controller.dart

class MainController extends GetxController {
  var pageController = PageController();
  RxInt curPage = 0.obs;

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

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

model/user.dart

class User {
  String id;
  String username;
  String email;
  String name;
  User({
    required this.id,
    required this.username,
    required this.email,
    required this.name,
  });

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'id': id,
      'username': username,
      'email': email,
      'name': name,
    };
  }

  factory User.fromMap(Map<String, dynamic> map) {
    return User(
      id: map['id'] as String,
      username: map['username'] as String,
      email: map['email'] as String,
      name: map['name'] as String,
    );
  }
}

util/api_routes.dart

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

view/pages/login_page.dart

class LoginPage extends GetView<LoginController> {
  //GetView<제네릭>을 extends
  const LoginPage({super.key});
  static const String route = '/login'; //path 정의

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextField(
                controller: controller.idController, //컨트롤러 연결
                decoration: const InputDecoration(
                  hintText: 'ID',
                ),
              ),
              TextField(
                controller: controller.pwController,
                decoration: const InputDecoration(
                  hintText: 'PW',
                ),
              ),
              ElevatedButton(
                onPressed: controller.login, //로그인 함수 연결
                child: const Text('로그인하기'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

view/pages/main_page.dart

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

  
  Widget build(BuildContext context) {
    var user = Get.find<AuthController>().user!; //로그인해야만 들어올 수 있는 페이지이기 때문에 ! 사용 가능.

    return Scaffold(
      bottomNavigationBar: Obx(
        () => BottomNavigationBar(
          currentIndex: controller.curPage.value,
          onTap: controller.onPageTapped,
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
            BottomNavigationBarItem(icon: Icon(Icons.person), label: 'My'),
          ],
        ),
      ),
      body: SafeArea(
        child: PageView(
          controller: controller.pageController,
          children: [
            Column(
              children: [
                Text(
                  '${user.username}님 안녕하세요.',
                  style: const TextStyle(
                    fontSize: 32,
                  ),
                ),
              ],
            ),
            Column(
              children: [
                ListTile(
                  leading: const CircleAvatar(),
                  title: Text(user.username),
                  subtitle: Text(user.email),
                ),
                ListTile(
                  title: const Text('logout'),
                  leading: const Icon(Icons.logout),
                  onTap: controller.logout, //로그아웃 연결
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

main.dart

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

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

  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      theme: ThemeData(useMaterial3: true),
      initialBinding: BindingsBuilder(() {
        Get.put(AuthController());
        Get.lazyPut(() => LoginController());
        Get.lazyPut(() => MainController());
      }),
      getPages: AppPages.pages,
      home: Scaffold(
        body: Center(
          child: TextButton(
            onPressed: () => Get.toNamed(LoginPage.route),
            child: const Text('Login page'),
          ),
        ),
      ),
    );
  }
}

요구사항

  • 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
      - 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,
        );
      }
    }
  • 개발팀 사원리스트(최신) 게시글의 attachment_url에는 해당 이미지의 URL이 담겨있다.
    ”김스팩의 비고란에 무엇이 써져있는지 비밀”을 알아내도록 한다.
  • (앱 디자인 방식은 자유로, 평가요소에 포함되어있지 않음)

요구사항이 길기도 하다.. 그치만 과제코드는 의외로 길지 않다. AuthController 강의부분이 선행되어서..

controller/main_controller.dart

처음에는 readDocuments를 실행해서 데이터를 받도록 하려다가, 리스트를 제공하도록 수정했다. 위젯 자체를 async로 만들고싶지 않아서...

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

  Dio dio = Dio();

  readDocuments() async {
    dio.options.baseUrl = 'http://52.79.115.43:8090';
    dio.options.headers['Authorization'] = Get.find<AuthController>().token;

    try {
      var res = await dio.get(ApiRoutes.authWithDocuments);
      if (res.statusCode == 200) {
        var data = List<Map<String, dynamic>>.from(res.data['items']);
        documents = data.map((e) => Document.fromMap(e)).toList();
      }
    } on DioException catch (e) {
      print(e.message);
      print(e.requestOptions.path);
    }
  }

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

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

auth_controller.dart : 토큰 저장

class AuthController extends GetxController {
  final Rxn<User> _user = Rxn();
  final Rxn _token = Rxn(); //토큰

  Dio dio = Dio();

  User? get user => _user.value;
  String? get token => _token.value; //토큰

  login(String id, String pw) async {
    dio.options.baseUrl = 'http://52.79.115.43:8090';
    try {
      var res = await dio.post(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 DioException catch (e) {
      print(e.message);
      print(e.requestOptions.path);
    }
  }
...
}

main_page.dart

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

  
  Widget build(BuildContext context) {
    var user = Get.find<AuthController>().user!;
    List<Document>? docs = controller.documents;

    return Scaffold(
      body: SafeArea(
        child: PageView(
          controller: controller.pageController,
          children: [
            Column(
              children: [
                Text(
                  '${user.username}님 안녕하세요.',
                  style: const TextStyle(
                    fontSize: 32,
                  ),
                ),
                docs != null
                    ? ListView.builder(
                        shrinkWrap: true,
                        itemCount: docs.length ?? 0,
                        itemBuilder: (context, index) {
                          return Column(
                            children: [
                              ListTile(
                                title: Text(docs[index].title),
                                subtitle: Text(docs[index].content),
                              ),
                              (docs[index].attachment_url != '')
                                  ? Image.network(docs[index].attachment_url!)
                                  : Container(),
                            ],
                          );
                        },
                      )
                    : Container()
              ],
            ),
          ],
        ),
      ),
    );
  }
}

화면

profile
주니어 플러터 개발자의 고군분투기

0개의 댓글