창업 일지/부트 캠프
책 정보 api를 활용한 앱
jjs815
2022. 6. 22. 21:18
지난주 보단 provider를 어떻게 써야 할지 감이 조금 생겼다
하지만 아직 api 데이터를 가져와서 이걸 출력하는 단계의 로직이 생소해서 그런지 막히는 부분이 생긴다
논리적인 사고력이 부족한것 같은 느낌이 든다
팁이라면 한번에 완벽한 코딩을 하기보단 조금씩 작성하다가 내가 원하는 값이 나오는지 print를 자주 하는 게 답인 것 같다
[ 메인 ]
import 'package:book_store/book_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'book.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => BookService()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
HomePage({Key? key}) : super(key: key);
/// 검색어를 가져올 수 있도록 TextField와 연결해 줍니다.
final TextEditingController searchController = TextEditingController();
/// 검색 함수
/// 엔터를 누르거나 돋보기 아이콘을 누를 때 호출
void search(BookService bookService) {
String keyword = searchController.text;
if (keyword.isNotEmpty) {
bookService.getBookList(keyword);
}
}
@override
Widget build(BuildContext context) {
return Consumer<BookService>(
builder: (context, bookService, child) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: false,
title: Text(
"Book Store",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
actions: [
Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(right: 12),
child: Text(
"total ${bookService.bookList.length}",
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
],
/// AppBar의 Bottom은 항상 PreferredSize 위젯으로 시작해야합니다.
bottom: PreferredSize(
preferredSize: Size(double.infinity, 72),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "원하시는 책을 검색해주세요.",
// 테두리
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
/// 돋보기 아이콘
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: () {
// 돋보기 아이콘 클릭
search(bookService);
},
),
),
onSubmitted: (v) {
// 엔터를 누르는 경우
search(bookService);
},
),
),
),
),
body: bookService.bookList.isEmpty
// bookList가 비어있는 경우
? Center(
child: Text(
"검색어를 입력해 주세요",
style: TextStyle(
fontSize: 21,
color: Colors.grey,
),
),
)
// bookList 보여주기
: ListView.builder(
itemCount: bookService.bookList.length,
itemBuilder: (context, index) {
Book book = bookService.bookList[index];
return ListTile(
leading: Image.network(
book.thumbnail,
width: 80,
height: 80,
fit: BoxFit.cover,
),
title: Text(book.title),
subtitle: Text(book.subtitle),
onTap: () {
// 클릭시 previewLink 띄우기
launch(book.previewLink);
},
);
},
),
);
},
);
}
}
[ book 클래스 ]
class Book {
String title;
String subtitle;
String thumbnail;
String previewLink;
Book({
required this.title,
required this.subtitle,
required this.thumbnail,
required this.previewLink,
});
// Map<String, dynamic>을 전달받아 Book 클래스 인스턴스를 반환하는 함수
// factory 키워드를 붙여서 생성자로 사용
factory Book.fromJson(Map<String, dynamic> volumeInfo) {
return Book(
// title이 없는 경우 빈 문자열 할당
title: volumeInfo["title"] ?? "",
// subtitle이 없는 경우 빈 문자열 할당
subtitle: volumeInfo["subtitle"] ?? "",
// imageLisks 또는 thumbnail이 없을 때 빈 이미지 추가
thumbnail: volumeInfo["imageLinks"]?["thumbnail"] ?? "https://i.ibb.co/2ypYwdr/no-photo.png",
// previewLink가 없는 경우 빈 문자열 할당
previewLink: volumeInfo["previewLink"] ?? "",
);
}
}
[ book service 클래스 ]
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'book.dart';
class BookService extends ChangeNotifier {
// 책 목록
List<Book> bookList = [];
/// 검색어로 책 정보 불러오기
void getBookList(String q) async {
bookList.clear(); // 기존에 들어있는 데이터 초기화
// API 호출
Response res = await Dio().get(
"https://www.googleapis.com/books/v1/volumes?q=$q&startIndex=0&maxResults=40",
);
List items = res.data["items"]; // items 접근
for (Map<String, dynamic> item in items) {
Map<String, dynamic> volumeInfo = item["volumeInfo"]; // volumeInfo 접근
Book book = Book.fromJson(volumeInfo); // Map -> Book
bookList.add(book); // Book 추가
}
// 화면 갱신
notifyListeners();
}
}
반응형