본문 바로가기
스파르타/Flutter

[Flutter] 좋아요 구현 - ( week 4 )

by bakcoding_sparta 2023. 4. 27.

좋아요를 누른 책들은 좋아요 탭에서 모아서 볼 수 있도록 만든다.

이때 좋아요 페이지에서는 책들의 목록을 볼 수 있어야 하는데 이미 만들어 놓은 기능이 있기 때문에 이걸 별도의 기능으로 빼서 재사용할 수 있도록 한다.

 

Book List

main.dart > SearchPage에서 만들어놓은 ListTile 위젯을 추출한다.

 

추출한 위젯의 이름은 BookTile로 명명한다.

 

ListView 내부의 ListTile 위젯이 외부로 분리되었다.

              itemBuilder: (context, index) {
                if (bookService.bookList.isEmpty) return SizedBox();
                Book book = bookService.bookList.elementAt(index);
                return BookTile(book: book);
              },

 

분리된 BookTile은 클래스 형태로 아래에서 확인할 수 있다.

class BookTile extends StatelessWidget {
  const BookTile({
    super.key,
    required this.book,
  });

  final Book book;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {},
      leading: Image.network(
        book.thumbnail,
        fit: BoxFit.fitHeight,
      ),
      title: Text(
        book.title,
        style: TextStyle(fontSize: 16),
      ),
      subtitle: Text(
        book.subtitle,
        style: TextStyle(color: Colors.grey),
      ),
      trailing: IconButton(
        onPressed: () {},
        icon: Icon(Icons.star_border),
      ),
    );
  }
}

 

이제 BookList에서 좋아요를 선택한 책들은 별도로 관리가 필요하다. 이 정보를 담기 위한 리스트 변수를 선언한다.

class BookService extends ChangeNotifier {
  List<Book> bookList = []; // 책 목록
  List<Book> likedBookList = [];

 

좋아요 페이지에서는 likedBookList를 순회하면서 그려주어야한다.

이 리스트에 책들이 들어가는 조건은 검색에서 나온 책들이 좋아요가 안되어있다면 해당 책들을 좋아요 책 리스트에 담고 좋아요가 이미 되어있다면 좋아요를 취소하는 것이므로 리스트에서 빼주어야 한다.

 

위 기능을 토글형태의 함수로 구현한다.

 

  void toggleLikeBook({required Book book}) {
    String bookId = book.id;
    if (likedBookList.map((book) => book.id).contains(bookId)) {
      likedBookList.removeWhere((book) => book.id == bookId);
    } else {
      likedBookList.add(book);
    }
    notifyListeners();
  }

 

db에 데이터를 저장하고 가져올 때 보이지 않는 정보인 bookId가 있었다. bookId를 여기서 사용해서 리스트 안에 해당 Id가 있는지 비교하여 좋아요 여부를 판단한다. 

 

하지만 이렇게 하지 않고 book 그대로 비교해서 판단할 수 있지 않나 생각했지만 클래스를 통해 생성된 두 객체는 포함된 데이터가 같더라도 별도의 객체이기 때문에 다르다고 판단한다. 따라서 이를 구분하기 위해서는 고유한 id 값이 필요한 것이다.

 

이 함수를 좋아요가 클릭될 때 호출되도록 한다.

그런데 좋아요를 누를 때 아이콘이 변경되어야 하기 때문에 새로고침이 필요하다. 하지만 BookTile은 이미 SearchPage의 Cosumer로 감싸진 곳에서 호출이 되기 때문에 추가로 새로고침을 위해서 BookTile을 Consumer로 해줄 필요는 없다. 

 

굳이 한다면 기능은 문제없지만 불필요한 새로고침은 성능을 저하시키기 때문에 여기서는 데이터만 가져다 쓰기 위해서 context.read를 적용시킨다.

 

context.read를 사용하기 위해서 BookTile에 변수를 선언한다.

  @override
  Widget build(BuildContext context) {
    BookService bookService = context.read<BookService>();

좋아요 버튼은 bookTile의 trailing 속성으로 만들어졌다. 이 부분의 기능을 구현한다.

      trailing: IconButton(
        onPressed: () {
          bookService.toggleLikeBook(book: book);
        },
        icon: bookService.likedBookList.map((book) => book.id).contains(book.id)
            ? Icon(
                Icons.star,
                color: Colors.amber,
              )
            : Icon(Icons.star_border),
      ),

선택된 book의 정보를 넘겨주고 두 리스트를 비교해서 아이콘이 변경되도록 한다.

 

Like Page

이제 저장된 좋아요 책 리스트를 가지고 좋아요 페이지에서 모아서 볼 수 있도록 만든다.

좋아요 페이지에서도 리스트가 수정될 때마다 화면이 새로 그려져야 하기 때문에 Scaffold를 Consumer로 만들어 준다.

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

  @override
  Widget build(BuildContext context) {
    return Consumer<BookService>(
      builder: (context, bookService, child) {
        return Scaffold(
          body: Center(
            child: Text("좋아요"),
          ),
        );
      }
    );
  }

 

그리고 body 부분을 수정해 준다.

          body: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: ListView.separated(
              itemCount: bookService.likedBookList.length,
              separatorBuilder: (context, index) {
                return Divider();
              },
              itemBuilder: (context, index) {
                if (bookService.likedBookList.isEmpty) return SizedBox();
                Book book = bookService.likedBookList.elementAt(index);
                return BookTile(book: book);
              },
            ),
          ),

코드는 SearchPage에서 사용했던걸 그대로 붙여 넣어주고 전달할 리스트만 변경해 준다. bookList -> likedBookList

이때 동일한 내용을 한 번에 변경할 수 있는데 변경할 코드를 클릭하고 ctrl + D를 누르면 동일한 코드를 찾아서 선택해 준다.

 

이제 좋아요를 누른 책들만 저장되는 걸 확인할 수 있다.

그리고 book id를 통해서 책들의 정보를 비교하기 때문에 다른 것을 검색하고 다시 동일한 단어를 검색해 보아도 좋아요를 체크한 내용이 유지된다.