본문 바로가기

Flutter/03 Design Pattern

[Flutter] Design Pattern(1) - Model

SNS login 이후, 사용자의 정보를 관리하는 사용자 정보에 대한 Model 패턴.

일번적으로 Model 패턴이라고 부르는진 모르지만, 개인적으로 개발할때 이렇게 표현 합니다.

 

login_user.dart

 

import 'package:cloud_firestore/cloud_firestore.dart';

class LoginUser {
  final String? appVersion;
  final String? datetime;
  final String? email;
  final String? expDate;
  final String? uid;
  final String? language;
  final String? lastLogin;
  final int? loginCnt;
  final String? photoURL;
  final String? providerId;
  final String? sUid;
  final String? userType;
  final bool? validation;

  LoginUser(
      {
        this.appVersion,
        this.datetime,
        this.email,
        this.expDate,
        this.uid,
        this.language,
        this.lastLogin,
        this.loginCnt,
        this.photoURL,
        this.providerId,
        this.sUid,
        this.userType,
        this.validation,
      }
  );

  // 1. StreamBuilder 또는 FutureBuilder 사용하는 경우
  factory LoginUser.fromDoc(QueryDocumentSnapshot data) {
    Map<String, dynamic> info = data.data() as Map<String, dynamic>;
    return LoginUser(
      datetime: info['datetime'],
      email: info['email'],
      expDate: info['expDate'],
      uid: info['uid'],
      language: info['language'],
      photoURL: info['photoURL'],
      providerId: info['providerId'],
      sUid: info['sUid'],
      userType: info['userType'],
      validation: info['validation'],
      appVersion: info['appVersion'],
      lastLogin: info['lastLogin'],
      loginCnt: info['loginCnt'],
    );
  }

  // 2. _JsonQueryDocumentSnapshot 또는 일반적인 json 타입인 경우
  factory LoginUser.fromJson(QueryDocumentSnapshot<Map<String, dynamic>> json) {
    return LoginUser(
      datetime: json['datetime'],
      email: json['email'],
      expDate: json['expDate'],
      uid: json['uid'],
      language: json['language'],
      photoURL: json['photoURL'],
      providerId: json['providerId'],
      sUid: json['sUid'],
      userType: json['userType'],
      validation: json['validation'],
      appVersion: json['appVersion'],
      lastLogin: json['lastLogin'],
      loginCnt: json['loginCnt'],
    );
  }

// 3. 기존 사용자의 로그인 정보 업데이트
  Map<String, dynamic> loginToMap() {
    return {
      'appVersion': appVersion,
      'lastLogin': lastLogin,
      'loginCnt': loginCnt,
    };
  }
  
// 4. 신규 사용자의 로그인 정보 생성
  Map<String, dynamic> initToMap() {
    return {
      'datetime': datetime,
      'email': email,
      'expDate': expDate,
      'uid': uid,
      'language': language,
      'photoURL': photoURL,
      'providerId': providerId,
      'sUid': sUid,
      'userType': userType,
      'validation': validation,
      'appVersion': appVersion,
      'lastLogin': lastLogin,
      'loginCnt': loginCnt,
    };
  }

}

 

1. factory LoginUser.fromDoc(QueryDocumentSnapshot data) 에 대한 개인적인 사용 예시.

데이터가 snapshot 인 경우, 사용하면 된다.

 

      child: StreamBuilder<QuerySnapshot>(
        stream: _firebaseFirestore
            .collection('sample')
            .orderBy('datetime')
            .snapshots(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (!snapshot.hasData) {
            return Center(child: CircularProgressIndicator());
          }

          if (snapshot.data != null && !snapshot.hasError) {
            _curUser = snapshot.data!.docs.map((user) {
              return LoginUser.fromDoc(user);
            }).toList();

or

    return FutureBuilder(
      future: _firebaseFirestore
          .collection('sample')
          .orderBy('datetime')
          .get(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }
        if (snapshot.data != null && !snapshot.hasError) {
          _curUser = snapshot.data!.docs.map((user) {
            return LoginUser.fromDoc(user);
          }).toList();

 

2. factory LoginUser.fromJson(QueryDocumentSnapshot<Map<String, dynamic>> json) 에 대한 개인적인 사용 예시.

일반적으로 collection 하위의 doc 까지 지정해서 사용하는데, map 기능을 사용하려다 보니, 약간 변형해서 where 조건을 사용하는 방식으로 처리함, 원래방식으로 처리가능한지 추가 테스트 예정입니다.

factory 패턴으로 처리하면, 변수 접근을 xxx['uid'] 로 하지 않고 xxx.uid 로 접근 가능함

 

  Future getUserInfo(String uid) async {
    var result = await FirebaseFirestore.instance
        .collection('user_info')
        .get();

    final userList = result.docs.map((user) {
      return LoginUser.fromJson(user);
    }).where((user) {
      return (user.uid == uid);
    }).toList();

    if (userList.isNotEmpty) {
      _curUser(userList.first); // Getx 및 controller 패턴에서 추가 설명 예정
    }

    return userList.length.toInt();
  }

 

A/S code - collection.doc.get 테스트 코드(정상 동작함)

어제는 여러가지 오류들이 있어서 변경해볼 엄두를 못했는데, 블로그 정리하면서 보니 될것 같아서 변경해보니 잘 동작합니다. 이래서 졸릴땐 코딩하면 안됩니다.

 

  Future getUserInfo(String uid) async {
    var result = await FirebaseFirestore.instance
        .collection('user_info')
        .doc(uid)
        .get();

    if(result.exists) { // 결과가 있는 경우
      final userList = LoginUser.fromJson2(result);
      _curUser(userList);
      print('>>>>' + curUser.email.toString());
      return 1;
    }
    return 0; // 결과가 없는 경우, 신규 고객정보를 생성

  }
  ============================ print 결과
  >>>>xxxxx@gmail.com

 

 

개인적으로 자주 사용하는 아래 샘플처럼 코딩하면 결과 값이 이미 Map 타입으로 코딩하는데 문제는 없지만 factory 패턴을 사용해보고 싶어서 상기 샘플로 처리하였음.

 

    FirebaseFirestore.instance
        .collection('user_info')
        .doc(uid)
        .get()
        .then((user) async {
      if (user.exists) {
        var _user = user.data();
          print(_user['uid']);

 

3, 4 같은 경우는 디비 저장시, 직접 처리해도 상관없지만, 여러명이 협업할 경우, 디비 구조가 제대로 전달되지 않아서 발생하는 오류를 막을수 있음. 모델 설계자가 코딩을 변경하면 다른 개발자들은 추가 또는 삭제된 필드를 제대로 처리하지 않은 경우, 오류가 발생하므로 신규 변경사항을 전달받지 못했더라도 컴파일시, 오류로 인해서 알게된다.

물론 1,2 번도 동일한 장점을 가지고 있다.