이번에는 Flutter를 사용하여 다음 분기에 읽을 책을 투표하는 화면에 대해 설명드리겠습니다. 사전에 Firestore의 votingBooks collection에 다음 분기 책 후보 목록을 저장하고, 앱 사용자들로 하여금 마음에 드는 책 컨테이너를 클릭하여 각 도서의 투표수에 반영하는 기능입니다.
이 화면은 사용자가 도서 컨테이너 클릭을 통해 특정 도서에 투표하거나 투표를 취소할 수 있는 기능을 제공합니다. 코드를 살펴보면서 각 부분에 대해 자세히 알아보겠습니다.
우선 예시로 다음 분기 책 투표 대상을 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);
/...
아래 코드는 화면의 레이아웃을 구성하는 build
메서드입니다. 여기서는 FutureBuilder
를 사용하여 다음 분기 투표할 도서 목록을 비동기적으로 가져와서 표시합니다. 각 도서 항목을 누를 때마다 voteForBook
함수를 호출하여 투표를 처리하고, toggleItemSelecition
함수를 사용하여 항목의 토글 상태를 변경합니다.
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); // 항목의 토글 상태를 변경합니다.
},
);
},
);
}
},
),
),
],
),
),
),
);
}
Widget
아래 코드는 사용자가 도서에 투표하거나 투표를 취소할 때 호출되는 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;
}
}
}