인프런 커뮤니티 질문&답변

최종환님의 프로필 이미지

작성한 질문수

[Bloc 응용] 실전 앱 만들기 (책 리뷰 앱) : SNS 로그인, Firebase 적용, Bloc 상태 관리, GoRouter

홈 상단 프로필 영역 개발

데이터 저장2

해결된 질문

23.07.30 12:57 작성

·

384

·

수정됨

0

안녕하세요 개남님 개남님이 만들어주신 코드와 제가 가지고 있던 코드랑 같이 활용을 해보려고 했는데 기존에 있던 코드가 작동을 안 하는데 혹시. 개남님이 만드신 Authrepository를 사용안하면 기존에 있던 name이나 Uid를 인식을 못 해서 안 불러지는건가요?

--추가--

신기한건

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: cannot get a field on a DocumentSnapshotPlatform which does not exist

 

이런식으로 에러가 뜨네요... 흠.. 분명 name은 존재한다고 뜨긴하는데.. (개남님이 만드신 방식으로 해야만 뜹니다)

(추가)

이런식으로 해봤더니 기존의 구글 name이 뜨네요.

음 Authrepository의 cubit에서 인식하는 name은 우리가 회원가입에서 입력한 이름이고

제가 만든 코드에서 인식하는 name은 구글 기존의 이름으로 뜹니다. 이유가 뭘까요?

 

감사합니다!

 

  void _nameEvent(NameEvent event, Emitter<HomePageState> emitter) async {
    String uid = FirebaseAuth.instance.currentUser!.uid;
    DocumentSnapshot document =
        await FirebaseFirestore.instance.collection('users').doc(uid).get();
    String userName = document['name'];
    int level = document['level'];

//여기는 기존의 bloc입니다

//이 아래는 이렇게하니까 우리가 추가한 네임이 아니라
기존의 구글 네임이 뜨게 되는 코드입니다.
  void _nameEvent(NameEvent event, Emitter<HomePageState> emitter) async {
    UserModel? userModel;
    final user = await _authenticationRepository.user.first;
    final userName = user?.name ?? 'Unknown'; // 기본값 설정
    print("유저의 이름은 $user");
    print("유저의 이름은 $userName");
    emit(state.copyWith(
      name: userName,
    ));
    // 기존의 Firestore에서 사용자 정보를 가져오는 코드...
    String uid = FirebaseAuth.instance.currentUser!.uid;
    DocumentSnapshot document =
        await FirebaseFirestore.instance.collection('users').doc(uid).get();
    int level = document['level'];
    print(user);

    print(userName);
    emitter(state.copyWith(
      level: level,
      name: userName,
    ));
  }

답변 3

0

최종환님의 프로필 이미지
최종환
질문자

2023. 08. 01. 11:40

앗 잘못썼네요 ㅋㅋ.

네 해결했습니다.

기존에는 그냥 where 절 + isEqual To없이 그냥 doc(uid) 이런식으로 했는데 바로 됐거든요.

 

혹시 그(doc(uid) ) 방법을 사용하시지 않는 이유가 있나요?

제가 기존에 사용하던 코드는 그냥 Where절 없이 썼는데

궁금합니다. 감사합니다!

0

최종환님의 프로필 이미지
최종환
질문자

2023. 07. 31. 09:48

감사합니다. QuerySnapshot queryResult = await FirebaseFirestore.instance .collection('users') .where('uid', isEqualTo: uid) .get(); 아마 이 코드를 몰라서 그랬던 것 같습니다. 감사합니다!

 

QuerySnapshot queryResult = await FirebaseFirestore.instance
    .collection('users')
    .where('uid', isEqualTo: uid)
    .get();

//이것이 일반적으로 사용했던 코드인데
QuerySnapshot queryResult = await FirebaseFirestore.instance
    .collection('users')
    .where('uid', isEqualTo: uid)
    .get();
개발하는남자님의 프로필 이미지
개발하는남자
지식공유자

2023. 07. 31. 11:00

두개의 소스가 같은것 같습니다.

문제가 해결이 되신 걸까요?? ^^ ;;

최종환님의 프로필 이미지
최종환
질문자

2023. 08. 04. 10:02

감사합니다! 해결은 되었습니다. 그런데 질문을 드려도 괜찮을까요?

제가 직접 name을 변경하는 함수를 만들어서 해봤는데 결국 제가 찾은 방법은 where절로 get을 한다음에 업데이트를 해야한다는 점이었습니다. 예를 들어 첫번째 코드는 위에것입니다! 전에는 그저 String uid = FirebaseAuth.instance.currentUser!.uid; 만 해도 바로 불러올 수 있었는데 개남님이 만드신 코드로 하면 where절을 (두번째 코드처럼)해야만 get을 먼저 따로하고 불러야만 가능한 방식인 것 같습니다! 혹시 방법이 이것밖에 없을까요? :)

 

감사합니다.

 

void _onNameChanged(
    NameChangedEvent event,
    Emitter<ProfileState> emitter,
  ) async {
    emitter(state.copyWith(isLoading: true));

    String uid = FirebaseAuth.instance.currentUser!.uid;

    // Update the name in the Firestore database
    await FirebaseFirestore.instance
        .collection('users')
        .doc(uid)
        .update({'name': event.newName});
    emitter(state.copyWith(isLoading: false));

    // Emit the new state
    emitter(
      state.copyWith(name: event.newName),
    );
  }

  void _onNameChanged(
    NameChangedEvent event,
    Emitter<ProfileState> emitter,
  ) async {
    emitter(state.copyWith(isLoading: true));

    String uid = FirebaseAuth.instance.currentUser!.uid;

    try {
      QuerySnapshot queryResult = await FirebaseFirestore.instance
          .collection('users')
          .where('uid', isEqualTo: uid)
          .get();

      // 로그 추가
      print('Documents fetched: ${queryResult.docs.length}');
      DocumentSnapshot snapshot = queryResult.docs.first;
      // Check if any documents are returned
      if (queryResult.docs.isEmpty) {
        // Handle error: No user found with the given uid
        print('No user found with UID: $uid');
        return;
      }

      // Update the name in the Firestore database
      await FirebaseFirestore.instance
          .collection('users')
          .doc(snapshot.id)
          .update({'name': event.newName});
    } catch (error) {
      // 에러 로그 추가
      print('Error in _onNameChanged: $error');
    }

    emitter(state.copyWith(isLoading: false));
    // Emit the new state
    emitter(state.copyWith(name: event.newName));
  }

 

개발하는남자님의 프로필 이미지
개발하는남자
지식공유자

2023. 08. 04. 10:16

최종환님께서 작성하신 코드대로 동작되기 위해서는 users collection에 document id 값을 고객의 uid로 넣어서 데이터를 넣게 된다면 최종환님께서 원하시는 코드처럼 작동은 가능합니다.

하지만 지금 제 강의에서 설계된 부분은 users 에 데이터를 넣을때 documentId를 특정해서 넣지 않고 자동생성하게 만들었기 때문에 documentId가 uid 다르게 됩니다. 그렇기 때문에 uid만으로는 collection에서 doc(uid)로 찾아 낼 수 없는 것입니다.

그렇다면 방법이 아예 없는 것은 아닙니다.

UserModel에 저장된 documentId 값을 저장시켜 doc(uid) 대신 doc(userDocumentId)를 넣어서 바로 업데이트를 시킬 수는 있습니다.

파이어베이스의 서비스중 데이터베이스의 경우 사용은 편리하긴 하지만

단점으로는 쿼리질의를 일반 데이터베이스를 사용하는것과 같이 사용할 수 없어서 불편한 점이 없지는 않습니다.

그리고 또다른 방법으로는 BigQurey를 이용하는 방법이 있을 수 있습니다.

이부분은 또다른 영역으로 학습이 필요한 부분이라 지금으로써는 제가 안내드릴 수 있는 부분이 위에 언급드린 방안만 답변 드릴 수 있겠네요 ^^:;

감사합니다.

최종환님의 프로필 이미지
최종환
질문자

2023. 08. 04. 13:24

답장 감사드립니다!

한 가지만 더 여쭤봐도 괜찮을까요?

혹시 디폴트로 제공되는 uid를 제공 안 하고 왜 따로 Uid를 생성해서 하셨는지 여쭙고 싶습니다!

일반적인 Uid를 사용한다면 where절을 사용하지 않고도 가능하지 않나 싶은데

혹시 제가 모르는 다른 것이 있는지 여쭙고 싶습니다

 

개발하는남자님의 프로필 이미지
개발하는남자
지식공유자

2023. 08. 04. 13:39

좋은 질문 감사합니다.

개발에 있어서 설계에는 다양하게 설계할 수 있습니다. 제가 이 강의에서 설계한 것이 정답은 아니며 얼마든지 더 좋은 방향이있을 수 있다는 점 참고 해주시면 좋을 것 같습니다.

단지 제가 uid를 별도로 관리하고 있는 이유에대해서 설명을 드리자면

firebase Sns 로그인시 각 플렛폼(google/apple/kakao/naver 등등) 에서 해당 고객에게 제공되는 unique key가 존재하는데 그 값이 이번 강의에서 사용되는 uid 입니다. 그렇기 때문에 별도로 uid를 생성했다 라고 보기보다는 각 플렛폼의 uid를 기준으로 앱을 개발했기 때문에 firebase collection에서 자동으로 생성된 documentId를 uid로 사용하지 않은 것입니다. 해당 uid는 로그인시에도 활용이 될 뿐아니라 회원 탈퇴후에 재 가입시 기존 사용계정을 연동을 해주기 위한 방안에서도 필요한 부분이라 uid를 각 플렛폼에서 제공해준 key를 사용한 것입니다.

질문 주신 것 처럼 documentId를 서비스 내부의 uid로 사용할수는 있습니다. 그렇게 된다면 update시 documentId를 바탕으로 바로 update도 할 수 있을 것입니다. 하지만 그렇다고 하더라도 플렛폼별 제공되는 uid는 별도로 관리가 필요합니다.

감사합니다.

최종환님의 프로필 이미지
최종환
질문자

2023. 08. 04. 16:06

답변 감사합니다!

0

개발하는남자님의 프로필 이미지
개발하는남자
지식공유자

2023. 07. 30. 21:38

안녕하세요 답변이 늦어 죄송합니다.

우선 질문해주신 내용을 정확히 이해가 되지 않았지만

강의에서 사용된 소스를 종환님께서 작업중이신 소스에 적용을 해보려 했다는 것인가요?

우선 SNS 로그인을 하게 되면 기본적으로 SNS에서 사용중인 Displayname을 name 값으로 넣어주고 있습니다.

AuthenticationRepository.dart 파일에 Stream<UserModel?> get user 소스를 확인해보면

  Stream<UserModel?> get user {
    return _firebaseAuth.authStateChanges().map<UserModel?>((user) {
      return user == null
          ? null
          : UserModel(
              name: user.displayName,
              uid: user.uid,
              email: user.email,
            );
    });
  }

_firebaseAuth.authStateChanges 에서 넘어오는 user 객체에서 displayName을 name으로 넣어주고있습니다. 이후 회원가입시 입력 받은 name을 저장하지 않고 다른 field이름으로 저장했다면 name을 출력시 sns에서 사용중인 닉네임이 나올 수 있습니다.

정확한 원인 확인을 하기 위해서는 데이터 저장이 어떻게 되어있는지도 확인이 필요해 보입니다. 또한

보내주신 소스 코드에서

    print("유저의 이름은 $user");
    print("유저의 이름은 $userName");

이 print를 찍는 시점은 파이어베이스에서 조회하기 전 user 정보이기때문에 sns 네임으로 나올 것입니다.

파이어베이스 조회 이후에는 조회된 데이터로부터 name을 추출해야 합니다.

하지만 소스 코드를 보게 되면

print(userName);
    emitter(state.copyWith(
      level: level,
      name: userName,
    ));

파이어베이스 데이터를 조회하기 전인 SNS에서 사용중인 namedmf userName으로 이미 할당한 뒤에

emit 처리를 해주고 있어서 문제 될 것입니다.