Don't think! Just do it!

종합 IT 기술 정체성 카오스 블로그! 이... 이곳은 어디지?

Flutter/Flutter Study

[Flutter] JSON, 직렬화???, Model, Firestore withConverter

방피터 2022. 10. 15. 11:49

2022.10.12 - [Flutter/Flutter Study] - [Flutter] - Firebase firestore

 

[Flutter] - Firebase firestore

React native에서는 React Query가 있어서 디따 편하게 했는데;;; 찾아보니까 Flutter에는 대표적으로 사용되는 React Query처럼 대표적으로 많이 사용되는 패키지는 없는 듯해... 음 뭐 그렇다고 Firestore 테

engschool.tistory.com

이전에 firestore 테스트할 때 읽는 방식이 매우 요상했어! 한마디로 등신처럼 스트림 설정하고 등신처럼 읽었지. 뭐 나도 공부해가면서 하는 거니까 ㅋㅋㅋ

//이렇게 등신처럼 스트림 설정하고
  final Stream<QuerySnapshot> userTextStream = FirebaseFirestore.instance
      .collection(FirebaseAuth.instance.currentUser!.email.toString())
      .snapshots();

//요딴식으로 썼지.
           children: snapshot.data!.docs.map((DocumentSnapshot document) {
              //예는 또 뭐임?
              Map<String, dynamic> data =
                  document.data() as Map<String, dynamic>;
              return ListTile(title: Text(data['userText']));//이렇게 읽어야함????
            }).toList(),

 

 

 

data['userText'] <-- 이딴식으로 접근하는 것도 등신같고 말이지. JSON 형식을 조금 보기 좋고 편하게 컨트롤하려면 몇 가지 알아야 할 것들이 있어.

//Javscript
const json = {"name": "홍길동","email": "gildong@example.com"};
console.log(json.name);
console.log(json.email);

//결과
> "홍길동"
> "gildong@example.com"

위 예를 보면 JS에서는 저런 json 형식의 object에 접근이 굉장히 편해. 그냥 일반 Object 다루듯 접근하면 끝인데, 다트는 안그래 👇

//dart
final json = {"name": "홍길동","email": "gildong@example.com"};
print(json["name"]);
print(json["email"]);

//결과
홍길동
gildong@example.com

다트 입장에서 JSON 포맷의 무언가는 Map<String,dynamic> 형식의 하나일 뿐이야. 그냥 String key과 dynamic value로 이루어진 세트 정도인거지. 그래서 String key로만 JSON 내부 요소에 접근할 수 있어. 그래서 json을 object로 변환시키는 작업을 따로 해야 해. 이걸 가지고 "직렬화"라고 병신처럼 부르는데;;; (무식한 소리인지 모르지만)그냥 변환 정도로 하면 안되나 싶어;;

암튼 난 JSON String을 Map<String,dynamic>으로 변환하는 "직렬화"는 관심이 없고 JSON을 일단 오브젝트로 변환하는 "직렬화"를 Firestore에 함께 적용해보려고 해. 우선 Fireastore는 문서 데이터를 json 포맷으로 전달해. 위에서 설명했듯이 javascript에서는 객체 접근하듯이 하면 되고 Dart에서는 String key로 접근해야 해.👇

//dart
FirebaseFirestore.instance
      .collection(FirebaseAuth.instance.currentUser!.email.toString())
      .orderBy('createdAt')
      .limit(1)
      .get()
      .then((value) =>
            print('userText is ${value.docs[0].data()!['userText']}'));
            //'userText'라는 String key로 접근

 

 

그래서 이걸 클래스 모델을 만들어서 Object로 변환해야 해. 아래는 userText와 createdAt을 가지고 있는 UserData라는 firestore 데이터 모델을 하나 만들고 fromJson 이라는 클래스 함수와 toJson이라는 인스턴스 함수를 하나 만들었어.

class UserData {
  UserData({this.userText, this.createdAt});

  final String? userText;
  final Timestamp? createdAt;

  UserData.fromJson(Map<String, Object?> json)
      : this(
          userText: json['userText']! as String,
          createdAt: json['createdAt']! as Timestamp,
        );
  Map<String, Object?> toJson() {
    return {
      'userText': userText,
      'createdAt': createdAt,
    };
  }
}

firestore에서 받아온 json 데이터를 가지고 userData를 만들면 일반 객체로 접근이 가능해. 타입도 안전하게 지켜지고, String key로 접근하다 실수하는 일도 없겠지. 

...
           children: snapshot.data!.docs.map((document) {
           	  //userData 인스턴스 생성.
              final userData = UserData.fromJson(document.data());
              return Column(children: [
                Divider(
                  thickness: 2,
                ),
                ListTile(title: Text(userData.userText))//객체로 접근.
              ]); //Listtile 생성!
            }).toList(),
...

그런데 이것도 모델이 많아지면 일일이 받아오는 것도 쉽지 않은 일이야. 그래서 아예 firestore 레퍼런스를 가져올 때 withConverter 함수를 사용해서 JSON이 아닌 객체를 주고 받고 그 데이터 타입도 정해줄 수 있어. 👇

  final userTextColRef = FirebaseFirestore.instance
      .collection(FirebaseAuth.instance.currentUser!.email.toString())
      .withConverter(
          //firestore에서 JSON이 아닌 객체로 데이터를 넘겨줌.
          fromFirestore: (snapshot, _) => UserData.fromJson(snapshot.data()!),
          //firestore에 저장할 때 객체를 Json으로 변경해서 넘겨줌.
          toFirestore: (movie, _) => movie.toJson());

 

 

이렇게 하면 userTextColRef 컬렉션을 통해 얻어온 데이터 타입은 JSON(Map<String,dynamic>이 아닌 UserData 객체야. 이제 별도의 변환없이 firestore 문서에 바로 객체처럼 접근할 수 있지.

객체로 접근, 당연히 자동 완성까지!

아래는 Firebase Stream 테스트할 때 사용했던 코드니 참고하시고!

class _FirestoreReadState extends State<FirestoreRead> {
  final userTextColRef = FirebaseFirestore.instance
      .collection(FirebaseAuth.instance.currentUser!.email.toString())
      .withConverter(
          fromFirestore: (snapshot, _) => UserData.fromJson(snapshot.data()!),
          toFirestore: (movie, _) => movie.toJson());

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: userTextColRef.orderBy('createdAt').snapshots(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Text("There is no data!");
        }
        if (snapshot.hasError) {
          return Text("Failed to read the snapshot");
        }

        return Expanded(
          child: ListView(
            //리스트뷰 써보자! 왜냐면 데이터가 많을 거니까!
            shrinkWrap: true, //이거 없으면 hasSize에서 에러발생!!
            //snapshot을 map으로 돌려버림!
            children: snapshot.data!.docs.map((document) {
              return Column(children: [
                Divider(
                  thickness: 2,
                ),
                ListTile(title: Text(document.data().userText!))
              ]); //Listtile 생성!
            }).toList(), //map을 list로 만들어서 반환!
          ),
        );
      },
    );
  }
}

 

Firestore에 데이터를 쓸때도 마찬가지👇

//기존
FirebaseFirestore.instance
    .collection(FirebaseAuth.instance.currentUser!.email.toString())
    .add({'userText': inputController.text, 'createdAt': Timestamp.now()})
    .then((value) => print("document added"))
    .catchError((error) => print("Fail to add doc ${error}"));
    
//withConverter
FirebaseFirestore.instance
    .collection(FirebaseAuth.instance.currentUser!.email.toString())
    .withConverter(
      fromFirestore: (snapshot, options) =>
          UserData.fromJson(snapshot.data()!),
      toFirestore: (value, options) => value.toJson(),
    )
    //JSON이 아닌 객체로 입력, 모델에 선언된 것 이외는 사용을 못하므로 한층 안정성 업!
    .add(UserData(
        userText: inputController.text, createdAt: Timestamp.now()))
    .then((value) => print("document added"))
    .catchError((error) => print("Fail to add doc ${error}"));

 

아래는 깃헙(정리안되어 있음 주의!)👇

https://github.com/peter-bang/studyFlutter

 

GitHub - peter-bang/studyFlutter: flutter project for studying

flutter project for studying. Contribute to peter-bang/studyFlutter development by creating an account on GitHub.

github.com

 

반응형