[Flutter] 비동기 프로그래밍 Stream - 2

푸드테크·2022년 2월 25일
0

Flutter

목록 보기
3/6
post-custom-banner

서론

이번 글은 FireStore Database를 활용해서 숫자를 증감시키고, 변경된 값을 바로 확인해볼 수 있는 간단한 예제입니다. Future와 Stream의 비동기 처리에 대해 집중했고, FireStore Database의 연동 방법은 생략했습니다.

사용 라이브러리

  firebase_core: 1.10.3
  cloud_firestore: ^3.1.7

구조

  1. StreamBuilder를 이용해 FD(FireStore Database)의 데이터를 받아오고, 리스트뷰를 사용해서 화면 출력합니다.
  2. 증가 버튼을 클릭하면 FD의 해당 document의 num 값을 1 증가시킵니다.
  3. 초기화 버튼을 클릭하면 FD의 해당 document 데이터에서 num 값 0으로 초기화합니다.

RoomModel

채팅방의 이름, 숫자, document 값을 가진 데이터 모델입니다. factory형식으로 json을 파싱할 수 있도록 해놨고, 예제에서 사용하지는 않지만 toString과 equal operator를 override 했습니다.

class RoomModel {
  String? name;
  int? num;
  String? doc;

  RoomModel({required this.name, required this.num, required this.doc});

  factory RoomModel.fromJson(Map<String, dynamic> json) {
    return RoomModel(name: json["name"], num: json["num"], doc : json["doc"]);
  }


  
  String toString() {
    return "RoomModel(name : $name, num : $num, doc : $doc)";
  }

  
  bool operator ==(Object other) {
    if (other is RoomModel) {
      return name == other.name && num == other.num && doc == other.doc;
    } else {
      return false;
    }
  }

  
  int get hashCode => super.hashCode;
}

FSGet

FD의 데이터를 호출하는 클래스입니다. readData 함수는 파이어베이스 클래스의 함수를 사용해서 Stream 타입을 리턴하고 있습니다.

class FSGet {
  ///파이어베이스의 room collection에 있는 모든 데이터 불러오기
  static Stream<List<Map<String, dynamic>>> readData({required String collection}) =>
      FirebaseFirestore.instance
          .collection(collection)
          .snapshots()
          .map((snapshot) => snapshot.docs.map((doc) => doc.data()).toList());
}

FSSet

FD의 데이터를 변경할 때 사용합니다.

class FSSet {
  ///FireStore Database의 room collection 업데이트
  static Future set({
    required String collection,
    required String doc,
    required Map<String, dynamic> json,
  }) async {
    await FirebaseFirestore.instance.collection(collection).doc(doc).set(json);
  }
}

RoomScreen


///Firebase와 Future, Stream을 활용한 채팅방 스크린
class RoomScreen extends StatefulWidget {
  const RoomScreen({Key? key}) : super(key: key);

  
  _RoomScreenState createState() => _RoomScreenState();
}

class _RoomScreenState extends State<RoomScreen> {
  final String collectionName = "room";

  
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text("Room")), body: _body());
  }

  Widget _body() {
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: FSGet.readData(collection: collectionName),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          print(snapshot.data);
          List<RoomModel> data = snapshot.data!
              .map<RoomModel>((json) => RoomModel.fromJson(json))
              .toList();
          return _listView(data: data);
        } else {
          return const SizedBox();
        }
      },
    );
  }

  ///리스트 위젯
  Widget _listView({required List<RoomModel> data}) {
    return ListView.separated(
      itemCount: data.length,
      shrinkWrap: true,
      itemBuilder: (_, index) {
        var model = data[index];
        return _listItem(
          name: model.name!,
          num: model.num!,
          plus: () async => await _plusData(roomModel: model),
          reset: () async => await _resetData(roomModel: model),
        );
      },
      separatorBuilder: (_, __) => _divider(),
    );
  }

  ///구분선
  Widget _divider() =>
      Divider(height: 2, thickness: 2, color: Colors.grey.shade300);

  ///리스트 아이템 위젯
  Widget _listItem({
    required String name,
    required int num,
    required VoidCallback plus,
    required VoidCallback reset,
  }) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Row(
        children: [
          ///숫자 증가 버튼
          ElevatedButton(onPressed: plus, child: const Text("증가")),
          const SizedBox(width: 10),

          ///숫자 초기화 버튼
          ElevatedButton(onPressed: reset, child: const Text("초기화")),

          ///대화방 이름과 숫자
          Expanded(
            child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
              Text("$name : $num", style: const TextStyle(fontSize: 20))
            ]),
          )
        ],
      ),
    );
  }

  ///숫자 플러스 api 호출
  Future _plusData({required RoomModel roomModel}) async {
    int num = roomModel.num!;
    num++;
    await FSSet.set(
        collection: "room",
        doc: roomModel.doc!,
        json: {"doc": roomModel.doc!, "name": roomModel.name!, "num": num});
  }

  ///숫자 초기화 api 호출
  Future _resetData({required RoomModel roomModel}) async {
    await FSSet.set(
        collection: collectionName,
        doc: roomModel.doc!,
        json: {"doc": roomModel.doc!, "name": roomModel.name!, "num": 0});
  }
}
profile
푸드 테크 기술 블로그
post-custom-banner

0개의 댓글