Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- printf
- java 출력
- JQ
- npm
- react
- Flutter
- Clean Architecture
- 배포
- println
- ListView
- 단축키
- 자바 출력 방식
- JS
- riverpod
- 자바스크립트
- 엡
- java 콘솔 출력 차이
- DART
- scss
- 앱심사
- firebase
- UI/UX
- java
- abap
- develop
- unity
- nodejs
- LLM
- 자바 포맷 출력
- lifecycle
Archives
- Today
- Total
guricode
[flutter-sns-project - 7] 트러블슈팅 - 이메일/패스워드 인자 순서 뒤바뀐 로그인/회원가입 실패 본문
앱/Flutter&Dart
[flutter-sns-project - 7] 트러블슈팅 - 이메일/패스워드 인자 순서 뒤바뀐 로그인/회원가입 실패
agentrakugaki 2025. 9. 3. 23:28로그인 기능을 수행하는데 email형태가 올바르지 않아 로그인이 되지 않는 상황이 있었다.
UI에서 컨트롤러로 email과 password를 받아 usecase를 물고있는 view모델에 login 메서드를 호출했는데
firebase Auth에서 email 형태 오류가 났다.
이것저것 살펴봤는데 문제는 data_source에서 파라미터를 네임드파라미터로 만들지 않아, 입력한 값이 email과 password가 반대로 되어 발생한 현상이었다.
간단한 문제였는데....코딩할때 이런점을 생각하지 않았던게 조금 창피하기도하다...
상황 인지
- UI → ViewModel → UseCase → Repository → DataSource 흐름에서 Firebase Auth email 형식 오류 발생했다.
- 원인 탐색 결과 Repository와 DataSource 사이에서 positional parameter 순서가 바뀌어 전달되고 있었다.
원인 분석
- user_data_source_impl.emailSignup(String email, String password, ...)는 (email, password) 순서다.
- user_repository_impl.emailSignup(String password, String email, ...)는 반대 순서다.
- Repository 호출 시 userDataSource.emailSignup(password, email, ...)로 전달되어 Firebase에 password가 email로 올라갔다.
- Dart는 동일 타입의 positional 인자 스왑을 컴파일 타임에 잡아내지 못한다.
해결 전략
- 전면 네임드 파라미터화로 순서 의존 제거.
- 타입 더 구체화 및 required로 누락 방지.
- 레이어 경계에 입력 검증 추가.
- 회귀 테스트로 재발 방지.
- Lint 규칙 강화로 positional 사용 금지에 가깝게 운영.
수정 코드
DataSource 인터페이스/구현
// user_data_source.dart
abstract class UserDataSource {
Future<UserDto> emailSignup({
required String email,
required String password,
required String imgUrl,
required String userNickname,
});
}
// user_data_source_impl.dart
class UserDataSourceImpl implements UserDataSource {
@override
Future<UserDto> emailSignup({
required String email,
required String password,
required String imgUrl,
required String userNickname,
}) async {
final cred = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
final uid = FirebaseAuth.instance.currentUser?.uid;
if (uid == null) throw StateError('uid is null after signup');
await FirebaseFirestore.instance.collection('user').add({
'uid': uid,
'email': email,
'userNickname': userNickname,
'profileImgUrl': imgUrl,
'createdAt': FieldValue.serverTimestamp(),
});
final snap = await FirebaseFirestore.instance
.collection('user')
.where('uid', isEqualTo: uid)
.limit(1)
.get();
return UserDto.fromFirestore(snap.docs.first);
}
}
Repository 인터페이스/구현
// user_repository.dart
abstract class UserRepository {
Future<User?> emailSignup({
required String email,
required String password,
required String imgUrl,
required String userNickname,
});
}
// user_repository_impl.dart
class UserRepositoryImpl implements UserRepository {
UserRepositoryImpl(this.userDataSource);
final UserDataSource userDataSource;
@override
Future<User?> emailSignup({
required String email,
required String password,
required String imgUrl,
required String userNickname,
}) async {
final dto = await userDataSource.emailSignup(
email: email,
password: password,
imgUrl: imgUrl,
userNickname: userNickname,
);
return User(
uid: dto.uid,
email: dto.email,
userNickname: dto.userNickname,
profileImgUrl: dto.profileImgUrl,
);
}
}
ViewModel/UseCase 호출부 예시
// usecase
class EmailSignupUseCase {
EmailSignupUseCase(this.repo);
final UserRepository repo;
Future<User?> call({
required String email,
required String password,
required String imgUrl,
required String userNickname,
}) {
return repo.emailSignup(
email: email,
password: password,
imgUrl: imgUrl,
userNickname: userNickname,
);
}
}
// viewmodel
Future<void> signup() async {
// controller에서 값 획득 후
final user = await emailSignupUseCase(
email: emailController.text.trim(),
password: passwordController.text,
imgUrl: state.profileImgUrl ?? '',
userNickname: nicknameController.text.trim(),
);
// ...
}
추가 안전장치
1) 입력 검증
bool _isValidEmail(String s) =>
RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$').hasMatch(s);
void _guardSignupArgs({
required String email,
required String password,
}) {
if (!_isValidEmail(email)) throw ArgumentError('Invalid email format');
if (password.length < 6) throw ArgumentError('Password too short');
}
2) 회귀 테스트
test('repository passes named args to datasource correctly', () async {
final mock = MockUserDataSource();
when(() => mock.emailSignup(
email: any(named: 'email'),
password: any(named: 'password'),
imgUrl: any(named: 'imgUrl'),
userNickname: any(named: 'userNickname'),
)).thenAnswer((_) async => fakeUserDto);
final repo = UserRepositoryImpl(mock);
await repo.emailSignup(
email: 'a@b.com',
password: 'pass123',
imgUrl: 'url',
userNickname: 'nick',
);
verify(() => mock.emailSignup(
email: 'a@b.com',
password: 'pass123',
imgUrl: 'url',
userNickname: 'nick',
)).called(1);
});
3) Lint 권장
- analysis_options.yaml에 규칙 추가:
linter:
rules:
prefer_named_parameters: true
always_put_required_named_parameters_first: true
- 팀 규칙: **레이어 경계(public API)**는 무조건 named + required.
결과
- 인자 순서 뒤바뀜으로 인한 Firebase Auth 이메일 형식 오류 해소.
- 레이어 간 계약이 명시적(named)으로 바뀌어 재발 가능성 낮아짐.
- 테스트와 린트로 회귀 방지 체계 확보.
후속 고려사항
- DTO ↔ Entity 매핑에 fromJson/toJson 일원화.
- 실패 케이스(중복 이메일, 네트워크 오류) 에러 모델 표준화.
- 로그인/회원가입 공통 입력 검증 유틸로 분리하여 재사용.
'앱 > Flutter&Dart' 카테고리의 다른 글
| [flutter-sns-project - 9] 트러블슈팅 - 구글로그인 중간에 화면 나갈시 그대로 홈 화면으로 진입되는 문제 (0) | 2025.09.04 |
|---|---|
| [flutter-sns-project - 8] Flutter 앱에 Sentry 연동하기: 설치부터 초기화까지 (0) | 2025.09.04 |
| [flutter-sns-project - 6] 트러블슈팅 - 로그인 수행시 Null오류 (0) | 2025.09.02 |
| [flutter-sns-project - 5] cloude_firestore ios 빌드 오류, 로그인 UI구현 (1) | 2025.09.01 |
| [flutter-sns-project - 4] 패키지설치 (0) | 2025.09.01 |