로그인 진행
- 페이지에서 ViewModel로 로그인 진행을 위임한다.
- VM에서는 유저가 입력한 값을 넘겨 받아서 Map으로 파싱한다.
- 파싱한 데이터를 서버에 통신으로 보내는데, 이것을 Repository에 위임한다.
- Repo에서는 서버에 통신을 할 때 넘겨받은 데이터를 Dio를 사용해서 통신한다.
- 통신이 끝나면 응답받은 데이터(Response 데이터)를 가지고, VM에서 비즈니스 로직을 진행한다.
- VM에서는 상태를 갱신하거나, 오류가 있는 응답의 경우 처리를 한다.
login_page ⇒ login_body
login_body를 CostomerWidget으로 변경한다.
build 에 WidgetRef 를 추가해 준다.
로그인은 SessionGvm gvm = ref.read(sessionProvieder.Notifire) >> write를 해준다.
필드에 사용자가 username과 password를 입력하면,
그 값을
gvm.login(_username.text.trim(), _password.text.trim());
로 넘긴다. session_gvm (글로벌 뷰 모델)
login() 이 실행된다. 이때 통신을 하기 때문에 async를 걸어준다.
_username과 _password로 받은 String들을 Map으로 파싱 해 준다.
Repository에다가 파싱한 데이터를 넘겨준다.
repository (통신)
서버에 요청을 보낸다. (Map으로 파싱된 데이터 넘겨줌)
Response response = await dio.post("/login", data: data);
Map<String, dynamic> body = response.data; // 응답 데이터
응답이 오게 되는데, 이때 정상적으로 로그인이 된다면 ResponseHeader에 토큰이 포함되어 온다.
이 토큰을 꺼내야한다.
String accessToken = "";
try {
accessToken = response.headers["Authorization"]![0] ?? ""; // 토큰 꺼냄
} catch (e) {}
해당 코드로 response.headers에서 [”Authorization]의 첫번째 배열에 들어가
있는 값을 꺼내면 그것이 토큰이다.
플러터에서는 두 개의 데이터를 한번에 return 할 수 있다.
그래서 Map<String, dynamic> 과 String 타입 두 개를 return 할 수 있다.
통신 전체 코드
Future<(Map<String, dynamic>, String)> findByUsernameAndPassword(
Map<String, String> data) async {
Response response = await dio.post("/login", data: data);
Map<String, dynamic> body = response.data;
//Logger().d(body); // test 코드 작성 - 직접해보기
// 토큰꺼내기
String accessToken = "";
try {
accessToken = response.headers["Authorization"]![0] ?? "";
//Logger().d(accessToken);
} catch (e) {}
return (body, accessToken);
}
session_gvm (글로벌 뷰 모델) 로 돌아와서
final (responseBody, accessToken) =
await userRepo.findByUsernameAndPassword(body);
통신이 끝나면 두개의 데이터가 return으로 받을 때는
final (responseBody, accessToken) << 구조 분해 할당으로 받아낸다.
이후 비즈니스를 진행한다.
실패의 경우
로그인이 정상적으로 처리되지 않았을 때 >> id또는 pw 잘못 입력의 경우
if (!responseBody["success"]) {
ScaffoldMessenger.of(mContext!).showSnackBar(
SnackBar(content: Text("로그인 실패 : ${responseBody["errorMessage"]}")),
);
return;
}
responseBody의 “success”가 아닐 경우 == 로그인 실패 처리
화면에 스넥바를 띄우고 해당 로그인 페이지에 머물게 처리를 한다.
성공의 경우
- 서버에서 받은 토큰을 Storage에 저장한다. << I/O에 저장
- SessionUser 갱신해준다.
- Dio에 토큰을 세팅해준다. << 메모리 저장
- 메인 화면으로 가게 해준다.
토큰을 I/O에 저장 할 때는 secureStorage를 사용해서 저장해 줄 수 있다.
await secureStorage.write(
key: "accessToken",
value: accessToken); // I/O << 비동기가 디폴트라서 동기로 묶어줘야 한다.
I/O 까지 도달하는 것은 시간이 걸리는 일이기 때문에 await를 걸어주어 동기화 시킨다.
로그인 한 유저의 정보를 SessionUser에 갱신해주어야 한다.
ResponseBody(응답데이터)의 body에 있는 유저 정보를 갱신하여 상태 update
Map<String, dynamic> data = responseBody["response"];
state = SessionUser(
id: data["id"],
username: data["username"],
accessToken: accessToken,
isLogin: true);
메모리에도 토큰을 세팅 해 준다. >> Dio 토큰 세팅
dio.options.headers = {"Authorization": accessToken};
이후 페이지 전환 >>
Navigator.
popAndPushNamed
(mContext, "/post/list");
해당 메인 페이지에서 유저 정보를 보여주고 싶을 경우
CostomerWidget으로 변환
ref.watch() 또는 ref.read() 를 사용해서 Session에서 유저정보 꺼내서 사용
gvm 전체코드
Future<void> login(String username, String password) async {
// 파싱
final body = {
"username": username,
"password": password,
};
// 유효성 검사
// 통신
final (responseBody, accessToken) =
await userRepo.findByUsernameAndPassword(body);
if (!responseBody["success"]) {
ScaffoldMessenger.of(mContext!).showSnackBar(
SnackBar(content: Text("로그인 실패 : ${responseBody["errorMessage"]}")),
);
return;
}
// 1. 토큰을 Storage 저장
await secureStorage.write(
key: "accessToken",
value: accessToken); // I/O << 비동기가 디폴트라서 동기로 묶어줘야 한다.
// 2. SessionUser 갱신
Map<String, dynamic> data = responseBody["response"];
state = SessionUser(
id: data["id"],
username: data["username"],
accessToken: accessToken,
isLogin: true);
// 3. Dio 토큰 세팅 , Dio << 메모리에 저장
dio.options.headers = {"Authorization": accessToken};
//Logger().d(dio.options.headers);
Navigator.popAndPushNamed(mContext, "/post/list");
}
Share article