Skip to content

Commit

Permalink
feat: TT-357 apple id 로그인 구현
Browse files Browse the repository at this point in the history
구현 내용:
- apple id 로그인 구현.
- 일부 기존 코드에 주석을 추가함.

추가 참고 사항 메모:
- android 및 web에서 web view로 뜨는 것 까지는 작동하지만 로그인을 완료했을 때 에러가 뜨면서 더 작동하지 않는 오류가 있음. 따라서 ios에서만 apple id login이 가능하도록 함.
- firebase_auth 의존성 추가시 web이 빌드되지 않는 오류가 있어 주석 처리 함.(없어도 현재 ios에서의 apple id 로그인은 잘 작동함. 현재 코드에서 해당 의존성이 필요한 부분은 없음.)

참고 자료:
- firebase/flutterfire#12088
- firebase/flutterfire#9089
- https://jutole.tistory.com/64
- firebase/flutterfire#13077
- https://stackoverflow.com/questions/70035380/type-listobject-is-not-a-subtype-of-type-list-in-type-cast
- firebase/flutterfire#13077
- aboutyou/dart_packages#408
- firebase/flutterfire#7216
- https://forum.ionicframework.com/t/error-with-firebase-google-authentication/239945
- firebase/firebase-ios-sdk#13084
- firebase/firebase-js-sdk#4256
- https://firebase.google.com/docs/auth/web/redirect-best-practices?hl=ko
- https://woongs.tistory.com/66
- firebase/flutterfire#10909

#feature/TT-357-SigninWithAppleId #149
  • Loading branch information
Seungman-dev committed Sep 20, 2024
1 parent 48e385e commit ed7b36c
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 40 deletions.
13 changes: 9 additions & 4 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

/* Begin PBXBuildFile section */
10031D7D2C58FDC500FDAEA2 /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 10031D7C2C58FDC500FDAEA2 /* Secrets.xcconfig */; };
10FE74F62C91479F000A8125 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 10FE74F52C91479F000A8125 /* GoogleService-Info.plist */; };
11829EC5A239B9F847C462A0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2253D20DCAC2C0EB3D063BC4 /* Pods_RunnerTests.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3A8F365CEBA94997C35B696E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 673D58D2B4877F3915CC070B /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
71F70B27FE57D7C71CD32EE2 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DC1D8C4E60DCC7DF7DB5E45F /* GoogleService-Info.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
Expand Down Expand Up @@ -46,6 +46,8 @@
/* Begin PBXFileReference section */
099080D7AE08621C05F88895 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
10031D7C2C58FDC500FDAEA2 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Secrets.xcconfig; path = assets/config/Secrets.xcconfig; sourceTree = "<group>"; };
10FE74F52C91479F000A8125 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../Downloads/GoogleService-Info.plist"; sourceTree = "<group>"; };
10FE74F72C9169FC000A8125 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2253D20DCAC2C0EB3D063BC4 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -66,7 +68,6 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C1D06FC6053F6629D079746B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
DC1D8C4E60DCC7DF7DB5E45F /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
F2DD688C9371B1C65F7E11F1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
F962AC41FA69BD32645556D4 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -122,14 +123,14 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
10FE74F52C91479F000A8125 /* GoogleService-Info.plist */,
10031D7C2C58FDC500FDAEA2 /* Secrets.xcconfig */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
D4BDACCB3BCF43729A6BBF57 /* Pods */,
6F39B1FD65A6E5E9D965AA96 /* Frameworks */,
DC1D8C4E60DCC7DF7DB5E45F /* GoogleService-Info.plist */,
);
sourceTree = "<group>";
};
Expand All @@ -145,6 +146,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
10FE74F72C9169FC000A8125 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
Expand Down Expand Up @@ -271,7 +273,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
71F70B27FE57D7C71CD32EE2 /* GoogleService-Info.plist in Resources */,
10FE74F62C91479F000A8125 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -496,6 +498,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2Y3R4N58K2;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -681,6 +684,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2Y3R4N58K2;
ENABLE_BITCODE = NO;
Expand All @@ -704,6 +708,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2Y3R4N58K2;
ENABLE_BITCODE = NO;
Expand Down
10 changes: 10 additions & 0 deletions ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
98 changes: 94 additions & 4 deletions lib/features/common/controllers/social_login_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:kakao_flutter_sdk/kakao_flutter_sdk.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:swm_peech_flutter/features/common/controllers/user_info_controller.dart';
import 'package:swm_peech_flutter/features/common/data_source/local/local_auth_token_storage.dart';
import 'package:swm_peech_flutter/features/common/data_source/remote/remote_social_login_data_souce.dart';
Expand Down Expand Up @@ -40,28 +41,38 @@ class SocialLoginCtr extends GetxController {
loginChoiceViewState.value = SocialLoginChoiceViewState.loading;
if (await isKakaoTalkInstalled()) {
try {
//카카오톡으로 로그인
OAuthToken token = await UserApi.instance.loginWithKakaoTalk();
print("KAKAO AccessToken: ${token.accessToken}");

//서버로 전송
SocialLoginInfo kakaoLoginInfo = SocialLoginInfo(socialToken: token.accessToken, authorizationServer: 'KAKAO');
AuthTokenResponseModel authTokenResponseModel = await postSocialToken(kakaoLoginInfo);

//화면 상태 변경
loginChoiceViewState.value = SocialLoginChoiceViewState.success;
loginChoiceViewLoginFailed.value = false;
print('카카오톡으로 로그인 성공');

//추가 정보 입력 분기
await Future.delayed(const Duration(milliseconds: 500));
if (authTokenResponseModel.statusCode == 411) {
// 추가 정보 입력 화면으로 이동
AppEventBus.instance.fire(SocialLoginBottomSheetOpenEvent(
socialLoginBottomSheetState: SocialLoginBottomSheetState.gettingAdditionalDataView, fromWhere: 'postSocialToken'));
} else {
// 로그인 성공
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("카카오톡 로그인 성공!"),
));
// 바텀 시트 닫기
if (context.mounted) {
Navigator.pop(context);
}
userInfoController.getUserAudioTimeInfo();
userInfoController.getUserAudioTimeInfo(); //홈 화면 오디오 시간 받아오기
}
} on DioException catch (error) {
// 서버 에러
loginChoiceViewState.value = SocialLoginChoiceViewState.waitingToLogin;
loginChoiceViewLoginFailed.value = true;
print('카카오톡으로 로그인 실패(dio exception) $error');
Expand All @@ -70,8 +81,9 @@ class SocialLoginCtr extends GetxController {
));
rethrow;
} catch (error) {
loginChoiceViewState.value = SocialLoginChoiceViewState.waitingToLogin;
loginChoiceViewLoginFailed.value = true;
// 클라이언트 에러
loginChoiceViewState.value = SocialLoginChoiceViewState.waitingToLogin; // 로그인 선택 뷰로 상태 변경
loginChoiceViewLoginFailed.value = true; // 로그인 실패 표시 보이기
print('카카오톡으로 로그인 실패(exception) $error');
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("카카오톡 로그인 실패: 클라이언트 에러"),
Expand All @@ -80,30 +92,108 @@ class SocialLoginCtr extends GetxController {
}
} else {
print('카카오톡이 깔려있지 않습니다.');

//카카오 계정으로 로그인
OAuthToken token = await UserApi.instance.loginWithKakaoAccount();
print("KAKAO AccessToken: ${token.accessToken}");

//서버로 전송
SocialLoginInfo kakaoLoginInfo = SocialLoginInfo(socialToken: token.accessToken, authorizationServer: 'KAKAO');
AuthTokenResponseModel authTokenResponseModel = await postSocialToken(kakaoLoginInfo);

//화면 상태 변경
loginChoiceViewState.value = SocialLoginChoiceViewState.success;
loginChoiceViewLoginFailed.value = false;
print('카카오톡으로 로그인 성공');

//추가 정보 입력 분기
await Future.delayed(const Duration(milliseconds: 500));
if (authTokenResponseModel.statusCode == 411) {
// 추가 입력 화면으로 이동
AppEventBus.instance.fire(
SocialLoginBottomSheetOpenEvent(socialLoginBottomSheetState: SocialLoginBottomSheetState.gettingAdditionalDataView, fromWhere: 'postSocialToken'));
} else {
// 로그인 성공
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("카카오톡 로그인 성공!"),
));
// 바텀 시트 닫기
if (context.mounted) {
Navigator.pop(context);
}
userInfoController.getUserAudioTimeInfo();
userInfoController.getUserAudioTimeInfo(); // 홈 화면 오디오 시간 받아오기
}
}
}

void loginWithApple(BuildContext context) async {
loginChoiceViewState.value = SocialLoginChoiceViewState.loading;
try {
//apple id login
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
webAuthenticationOptions: WebAuthenticationOptions(
clientId: 'swm-peech-flutter.twenty-three.com',
redirectUri: Uri.parse('https://twenty-three-4d6f6.firebaseapp.com/__/auth/handler'),
),
);

print('[credential]: $credential');
print('credential.state = $credential');
print('credential.email = ${credential.email}');
print('credential.userIdentifier = ${credential.userIdentifier}');
print('credential.identityToken = ${credential.identityToken}');

//서버로 전송
SocialLoginInfo appleLoginInfo = SocialLoginInfo(socialToken: credential.identityToken, authorizationServer: 'APPLE');
AuthTokenResponseModel authTokenResponseModel = await postSocialToken(appleLoginInfo);
loginChoiceViewState.value = SocialLoginChoiceViewState.success;
loginChoiceViewLoginFailed.value = false;
print('apple id로 로그인 성공');

// 추가정보 입력 분기
await Future.delayed(const Duration(milliseconds: 500));
if (authTokenResponseModel.statusCode == 411) {
// 추가정보 입력 화면으로 이동
AppEventBus.instance.fire(
SocialLoginBottomSheetOpenEvent(socialLoginBottomSheetState: SocialLoginBottomSheetState.gettingAdditionalDataView, fromWhere: 'postSocialToken'));
} else {
// 로그인 성공
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("애플 로그인 성공!"),
));
// 바텀 시트 닫기
if (context.mounted) {
Navigator.pop(context);
}
userInfoController.getUserAudioTimeInfo(); // 홈 화면 오디오 시간 받아오기
}
} on DioException catch (error) {
loginChoiceViewState.value = SocialLoginChoiceViewState.waitingToLogin;
loginChoiceViewLoginFailed.value = true;
print('Apple Id로 로그인 실패(dio exception): $error');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("애플 로그인 실패: 서버 에러"),
),
);
rethrow;
} catch (error) {
loginChoiceViewState.value = SocialLoginChoiceViewState.waitingToLogin;
loginChoiceViewLoginFailed.value = true;
print('Apple Id로 로그인 실패(exception) $error');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("애플 로그인 실패: 클라이언트 에러"),
),
);
rethrow;
}
}

Future<AuthTokenResponseModel> postSocialToken(SocialLoginInfo kakaoLoginInfo) async {
RemoteSocialLoginDataSource remoteSocialLoginDataSource = RemoteSocialLoginDataSource(AuthDioFactory().dio);
String funnel = platform_funnel.getFunnel();
Expand Down
48 changes: 37 additions & 11 deletions lib/features/common/widgets/social_login_choice_view.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:swm_peech_flutter/features/common/controllers/social_login_controller.dart';
import 'package:swm_peech_flutter/features/common/models/social_login_choice_view_state.dart';
import 'package:swm_peech_flutter/features/common/platform/is_mobile_on_mobile.dart'
if (dart.library.html) 'package:swm_peech_flutter/features/common/platform/is_mobile_on_web.dart' as platform_client;

Widget socialLoginChoiceView(BuildContext context, SocialLoginCtr controller) {
return SizedBox(
Expand All @@ -18,7 +21,7 @@ Widget socialLoginChoiceView(BuildContext context, SocialLoginCtr controller) {
),
),
const SizedBox(height: 20),
if(controller.loginChoiceViewState.value == SocialLoginChoiceViewState.waitingToLogin)
if (controller.loginChoiceViewState.value == SocialLoginChoiceViewState.waitingToLogin)
Column(
children: [
const Text(
Expand All @@ -28,7 +31,7 @@ Widget socialLoginChoiceView(BuildContext context, SocialLoginCtr controller) {
),
),
const SizedBox(height: 20),
if(controller.loginChoiceViewLoginFailed.value == true)
if (controller.loginChoiceViewLoginFailed.value == true)
const Column(
children: [
Text(
Expand All @@ -47,21 +50,44 @@ Widget socialLoginChoiceView(BuildContext context, SocialLoginCtr controller) {
onTap: () async {
controller.loginWithKakao(context);
},
child: Image.asset('assets/images/kakao_login_medium_wide.png'),
child: Image.asset(
'assets/images/kakao_login_medium_wide.png',
),
),
if (platform_client.isIphone())
Column(
children: [
SizedBox(height: 10),
SizedBox(
width: 300,
child: SignInWithAppleButton(
onPressed: () {
controller.loginWithApple(context);
},
),
),
],
),
// SignInWithAppleButton(
// onPressed: () async {
// final AppleAuthProvider provider = AppleAuthProvider();
// provider.addScope('email');
// provider.addScope('name');
// final UserCredential userCredential = await FirebaseAuth.instance.signInWithProvider(provider);
// print('userCredential: ${userCredential.user?.getIdToken()}');
// },
// ),
],
)
else if(controller.loginChoiceViewState.value == SocialLoginChoiceViewState.success)
const Text(
'로그인 성공!',
else if (controller.loginChoiceViewState.value == SocialLoginChoiceViewState.success)
const Text('로그인 성공!',
style: TextStyle(
fontSize: 15,
)
)
else if(controller.loginChoiceViewState.value == SocialLoginChoiceViewState.loading)
const CircularProgressIndicator(),
))
else if (controller.loginChoiceViewState.value == SocialLoginChoiceViewState.loading)
const CircularProgressIndicator(),
],
),
),
);
}
}
Loading

0 comments on commit ed7b36c

Please sign in to comment.