[Flutter] Flutter를 활용한 다음 분기 투표 화면 구현: VoteNextBookScreen 클래스

StudipU·2024년 3월 13일
0

이번에는 Flutter를 사용하여 다음 분기에 읽을 책을 투표하는 화면에 대해 설명드리겠습니다. 사전에 Firestore의 votingBooks collection에 다음 분기 책 후보 목록을 저장하고, 앱 사용자들로 하여금 마음에 드는 책 컨테이너를 클릭하여 각 도서의 투표수에 반영하는 기능입니다.

VoteNextBookScreen 클래스 소개 ✨

이 화면은 사용자가 도서 컨테이너 클릭을 통해 특정 도서에 투표하거나 투표를 취소할 수 있는 기능을 제공합니다. 코드를 살펴보면서 각 부분에 대해 자세히 알아보겠습니다.

주요 기능과 코드 분석 🎭

1. 도서 투표 상태변수

우선 예시로 다음 분기 책 투표 대상을 3개로 설정을 했고, 어떤 책이 클릭되었는지를 사용자마다의 정보로 기억을 해놓기 위해 toggleStates 변수를 생성했습니다. 특정 책에 투표를 했다면 다른 화면으로 전환했다가 다시 돌아오거나, 로그아웃 후에 재로그인 해도 투표상태 변수값을 참조하여 투표한 책들에 대해 클릭된 상태를 유지할 수 있습니다.

class VoteNextBookScreen extends StatefulWidget {
  const VoteNextBookScreen({super.key});

  
  State<VoteNextBookScreen> createState() => _VoteNextBookScreenState();
}

class _VoteNextBookScreenState extends State<VoteNextBookScreen> {
  /// 책 권수는 매번 동일하게 설정
  List<bool> toggleStates = List.generate(3, (index) => false);
  
  /...
  

2. 투표하기 프로세스

아래 코드는 화면의 레이아웃을 구성하는 build 메서드입니다. 여기서는 FutureBuilder를 사용하여 다음 분기 투표할 도서 목록을 비동기적으로 가져와서 표시합니다. 각 도서 항목을 누를 때마다 voteForBook 함수를 호출하여 투표를 처리하고, toggleItemSelecition 함수를 사용하여 항목의 토글 상태를 변경합니다.


Widget build(BuildContext context) {
  // 화면 크기 계산
  double deviceWidth = MediaQuery.of(context).size.width;
  double deviceHeight = MediaQuery.of(context).size.height;
  double widthRatio = deviceWidth / 375;
  double heightRatio = deviceHeight / 812;

  return Scaffold(
    appBar: appbar_widget(context),
    drawer: drewer_widget(context),
    body: Center(
      child: Container(
        // 배경색상 설정
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment(-0.00, -1.00),
            end: Alignment(0, 1),
            colors: [Color(0xA545B0C5), Color(0xFF4580C5), Color(0xFF4580C5)],
          ),
        ),
        child: Column(
          children: [
            nav_widget(context),
            // 투표 목록
            Container(
              width: widthRatio * 350,
              height: heightRatio * 600,
              child: FutureBuilder(
                future: getVotingBooks(), // 다음 분기 투표할 도서 목록을 가져옵니다.
                builder: (context, snapshot) {
                  if (!snapshot.hasData) {
                    return CircularProgressIndicator();
                  } else {
                    return ListView.builder(
                      itemCount: snapshot.data!.docs.length,
                      itemBuilder: (context, index) {
                        DocumentSnapshot book = snapshot.data!.docs[index];
                        return GestureDetector(
                          child: Container(
                            // 도서 항목을 나타내는 컨테이너
                            ...
                          ),
                          onTap: () async {
                            await voteForBook(book); // 도서에 투표하거나 투표를 취소합니다.
                            toggleItemSelecition(index); // 항목의 토글 상태를 변경합니다.
                          },
                        );
                      },
                    );
                  }
                },
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

아래 코드는 사용자가 도서에 투표하거나 투표를 취소할 때 호출되는 voteForBook 함수입니다. 사용자가 이미 투표한 도서인지 여부를 확인한 후, 해당 도서의 투표 수를 증가 또는 감소시킵니다.

뿐만 아니라, 해당 사용자가 투표를 한 책들에 대하여 사용자의 투표 목록을 업데이트합니다. 투표한 책들은 users collection에서 각자의 이메일 아이디 문서에 votedBookList 안에 문서로 저장됩니다.

Future<void> voteForBook(DocumentSnapshot book) async {
  User? user = FirebaseAuth.instance.currentUser;
  if (user != null) {
    // 사용자가 투표한 책 목록을 가져옵니다.
    QuerySnapshot<Map<String, dynamic>> querySnapshot =
        await FirebaseFirestore.instance
            .collection('users')
            .doc(user.email)
            .collection('votedBookList')
            .get();

    List<dynamic> votedBooks =
        querySnapshot.docs.map((doc) => doc.id).toList() ?? [];

    // 사용자가 이미 이 책에 투표한 경우 투표를 취소합니다.
    if (votedBooks.contains(book.id)) {
      await FirebaseFirestore.instance
          .collection('votingBookList')
          .doc(book.id)
          .update({
        'votes': FieldValue.increment(-1),
      });

      votedBooks.remove(book.id);
      await FirebaseFirestore.instance
          .collection('users')
          .doc(user.email)
          .collection('votedBookList')
          .doc(book.id)
          .delete();
      return;
    } else {
      // 사용자가 이 책에 투표하지 않은 경우 투표를 처리합니다.
      await FirebaseFirestore.instance
          .collection('votingBookList')
          .doc(book.id)
          .update({
        'votes': FieldValue.increment(1),
      });

      votedBooks.add(book.id);
      await FirebaseFirestore.instance
          .collection('users')
          .doc(user.email)
          .collection('votedBookList')
          .doc(book.id)
          .set({});
      return;
    }
  }
}
profile
컴공 대학생이 군대에서 작성하는 앱 개발 블로그

0개의 댓글