[Flutter] Flutter 앱에서 완독한 책 목록 화면 구현하기 : FinishedBookListScreen 클래스

StudipU·2024년 3월 9일
0

FinishedBookListScreen 클래스 소개 ✨

FinishedBookListScreenFlutter에서는 사용자가 완독한 책 목록을 확인하고 감상평을 작성할 수 있는 화면을 제공할 수 있습니다. 이를 위해 FirebaseCloud Firestore를 사용하여 데이터를 관리합니다.

주요 기능과 코드 분석 🎭

1. 필요한 라이브러리 및 위젯 import 하기

먼저, 앱 개발에 필요한 라이브러리와 위젯을 import합니다. Firebase와 Cloud Firestore를 사용하기 위해 cloud_firestore 패키지를, Firebase Authentication을 사용하기 위해 firebase_auth 패키지를 import합니다. 또한, 화면 구성을 위해 필요한 Flutter 위젯들을 import합니다.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:military_bookstore/database/get_database_Info.dart';
import 'package:military_bookstore/screen/review_screen.dart';
import 'package:military_bookstore/screen/search_bookTitle_screen.dart';
import 'package:military_bookstore/widget/appbar_widget.dart';
import 'package:military_bookstore/widget/drewer_widget.dart';
import 'package:military_bookstore/widget/nav_widget.dart';

2. build 메서드 구현하기

FinishedBookListScreen 클래스는 StatelessWidget을 상속받아야 하며, 사용자의 완독한 책 목록을 보여주는 화면을 구현합니다. 코드에서는 build 메서드를 사용하여 화면을 구성합니다.

class FinishedBookListScreen extends StatelessWidget {
  const FinishedBookListScreen({super.key});

  
  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;

    // Scaffold를 통해 화면 구성
    return Scaffold(
      appBar: appbar_widget(context), // 상단 앱 바
      drawer: drewer_widget(context), // 좌측 드로어 메뉴
      body: Center(
        child: Container(
          clipBehavior: Clip.antiAlias,
          // 배경색상 및 그래디언트 설정
          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 * 515,
                child: FutureBuilder(
                  future: getFinishedBookInfo(), // 완독한 책 정보 가져오기
                  builder: (BuildContext context,
                      AsyncSnapshot<Map<dynamic, dynamic>> snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting) {
                      // 데이터 로딩 중일 때 로딩 스피너 표시
                      return CircularProgressIndicator();
                    } else if (snapshot.hasError) {
                      // 에러 발생 시 에러 메시지 표시
                      return Text('Error: ${snapshot.error}');
                    } else {
                      // 완독한 책 목록을 보여주는 리스트뷰 생성
                      return SingleChildScrollView(
                        child: Column(
                          children: List.generate(
                              snapshot.data!['length'],
                              (index) => lstItem(
                                  context,
                                  snapshot.data!["Ids"],
                                  index,
                                  widthRatio,
                                  heightRatio)),
                        ),
                      );
                    }
                  },
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

3. lstItem 메서드 구현하기

// 각 완독한 책 항목을 표시하는 위젯
Widget lstItem(BuildContext context, Map<dynamic, dynamic> bookMap,
      int index, widthRatio, heightRatio) {
    List<dynamic> bookList = bookMap.keys.toList();

    return GestureDetector(
      onTap: () {
        // 각 책을 클릭하면 해당 책의 감상평을 보여주는 화면으로 이동
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) =>
                ReviewScreen(
                  review: bookMap[bookList[index]],
                  bookTitle: bookList[index],
                ),
          ),
        );
      },
      child: Container(
        margin: EdgeInsets.only(bottom: heightRatio * 20),
        padding: EdgeInsets.symmetric(
            vertical: heightRatio * 10, horizontal: widthRatio * 10),
        decoration: ShapeDecoration(
          color: Colors.white.withOpacity(0),
          shape: RoundedRectangleBorder(
            side: BorderSide(width: 2, color: Color(0xBFFFFFFF)),
            borderRadius: BorderRadius.circular(20),
          ),
        ),
        child: Container(
          margin: EdgeInsets.only(left: widthRatio * 5),
          child: Row(
            children: [
              // 책 이미지 표시
              FutureBuilder(
                future: getBookImage(bookList[index]),
                builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    // 이미지 로딩 중일 때 로딩 스피너 표시
                    return CircularProgressIndicator();
                  } else if (snapshot.hasError) {
                    // 에러 발생 시 에러 메시지 표시
                    return Text('Error: ${snapshot.error}');
                  } else {
                    // 이미지 로딩이 완료되면 이미지 표시
                    return Container(
                      child: Image(
                        image: snapshot.data!.image,
                        width: widthRatio * 105,
                        height: widthRatio * 105 * 1.48,
                        fit: BoxFit.fill,
                      ),
                    );
                  }
                },
              ),
              // 책 제목 및 감상평 표시
              Container(
                margin: EdgeInsets.only(left: 20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 책 제목
                    Container(
                      constraints: BoxConstraints(
                        maxWidth: 200.0,
                      ),
                      child: FittedBox(
                        fit: BoxFit.fitWidth,
                        child: Text(
                          bookList[index],
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: Color(0xE5001F3F),
                            fontSize: 18,
                            fontFamily: 'GowunBatang',
                            fontWeight: FontWeight.bold,
                            height: 0,
                            letterSpacing: -0.40,
                          ),
                        ),
                      ),
                    ),
                    // 감상평 요약
                    Container(
                      width: widthRatio * 190,
                      height: heightRatio * 101,
                      child: Text(
                        bookMap[bookList[index]]!,
                        maxLines: 4,
                        overflow: TextOverflow.ellipsis,
                        style: TextStyle(
                          color: Color(0xE5001F3F),
                          fontFamily: 'GowunBatang',
                          fontWeight: FontWeight.bold,
                          height: 0,
                          letterSpacing: -0.40,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

4. Firebase와 상호작용하는 함수 구현하기

위에서 사용된 getFinishedBookInfo(), getBookImage(), getTopRankers(), getUserRank() 등의 함수들은 Firebase와 상호작용하여 데이터를 가져오거나 업데이트하는 기능을 담당합니다. 이들 함수는 각각 사용자의 완독한 책 정보를 가져오는 함수, 책의 이미지를 가져오는 함수, 상위 랭커 정보를 가져와 랭킹을 업데이트하는 함수, 사용자의 랭킹을 가져오는 함수입니다.

Future<Map<String, dynamic>> getFinishedBookInfo() async {
    Map<String, dynamic> collectionInfo = {};
    final User? user = FirebaseAuth.instance.currentUser;
    FirebaseFirestore _firestore = FirebaseFirestore.instance;

    ///쿼리 실행결과는 QuerySnapshot, 실행 결과의 각 문서 객체는 QueryDocumentSnapshot
    QuerySnapshot<Map<String, dynamic>> documentSnapshot = await _firestore
        .collection('users')
        .doc(user!.email)
        .collection('finishedBookList')
        .get();
    collectionInfo['length'] = documentSnapshot.docs.length;
    Map<String, dynamic> documentIds = Map.fromIterable(
      documentSnapshot.docs,
      key: (doc) => doc.id,
      value: (doc) => doc["review"],
    );
    collectionInfo['Ids'] = documentIds;
    return collectionInfo;
  }

  Future<int> getFinishedBookCount() async {
    Map<String, dynamic> finishedBookInfo = await getFinishedBookInfo();
    Map<String, dynamic> finishedBookList = finishedBookInfo['Ids'];

    List<dynamic> reviews = finishedBookList.values.toList();

    int count = 0;

    for (var review in reviews) {
      if (review.length > 199) {
        count++;
      }
    }

    return count;
  }

  // Future<List<Map<String, dynamic>>>
  Future<void> getTopRankers() async {
    /// Firestore에서 'users' 컬렉션의 문서들을 'bookCount' 필드를
    /// 기준으로 내림차순으로 정렬하여 상위 10개의 문서 가져오기
    QuerySnapshot<Map<String, dynamic>> querysnapshot = await FirebaseFirestore
        .instance
        .collection('users')
        .orderBy('bookCount', descending: true)
        .get();
    final CollectionReference usersCollectionRef =
    await FirebaseFirestore.instance
        .collection('users');
    // 상위 10개의 문서를 Map의 List로 변환
    List<Map<String, dynamic>> topRankers = querysnapshot.docs.map((doc) =>
        doc.data()).toList();

    // 상위 10개의 문서의 순위 부여
    for (int i = 0; i < topRankers.length; i++) {
      await usersCollectionRef.doc(topRankers[i]["email"]).update({
        "rank": i + 1
      });
    }
  }

  Future<int> getUserRank(context) async {
    int userRank = -1;
    final User? user =
        FirebaseAuth.instance.currentUser;
    if (user == null) {
      Navigator.pushNamed(context, '/login');
      return userRank;
    }
    else {
      final DocumentSnapshot<Map<String, dynamic>> documentRef =
      await FirebaseFirestore.instance
          .collection('users')
          .doc(user.email)
          .get();
      int userRank = documentRef.data()!['rank'];
      return userRank;
    }
  }

위와 같이 FinishedBookListScreen 클래스를 구현하면 사용자는 완독한 책 목록을 확인하고, 각 책에 대한 감상평을 작성하거나 확인할 수 있습니다.

이와 같이 FlutterFirebase를 이용하여 완독한 책 목록을 효과적으로 관리하고 화면에 표시할 수 있습니다.

profile
컴공 대학생이 군대에서 작성하는 앱 개발 블로그

0개의 댓글