내배캠 32일차_Flutter 실무숙련 (책 검색 앱 만들기2)
1. Flutter 실무숙련
WebView란?
플러터에서 웹 페이지 보여줄 때 사용하는 위젯
flutter_inappwebview :
flutter 공식팀이 만든건 아니지만 쿠키 관리, 자바스크립트 상호작용 등의 기능을 간편하게 할 수 있음
패키지 추가 : flutter pub add flutter_inappwebview
Open API (Open Appilcation Programming Interface)
특정 서비스나 소프트웨어 기능을 외부 개발자가 사용할 수 있도록 정의한 인터페이스
우리가 사용할 네이버 책 검색 API는 우리가 검색어를 네이버에 요청하면
네이버에서 그에 대한 결과를 JSON형태로 반환해주는 서비스
OPEN API 키 발급 : 네이버 개발자 센터
Thunder Client 설치
HTTP 요청, VSCode 확장 프로그램, API요청을 해볼 수 있는 도구.
Header 수정. GET메서드 이외의 요청 등.
데이터테스트
HEADER :
X-Naver-Client-Id, X-Naver-Client-SecretModel 클래스 생성 및 테스트
응답받은 JSON 중 item에서 가장 짧은내용 복사
주석처리 후 Book Class 만들기 (fromJson, toJson)
test 함수 사용
test 작성 및 테스팅 가능.
book_model_test.dart
import 'dart:convert';
import 'package:flutter_book_search_app/data/model/book.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Book model test', (){
String dummyData = """
{
"title": "Harry Potter (Welcome to Hogwarts Planner Notebook Collection (Set of 3): (Harry Potter School Planner School, Harry Potter Gift, Harry Potter Station)",
"link": "https://search.shopping.naver.com/book/catalog/43805245634",
"image": "https://shopping-phinf.pstatic.net/main_4380524/43805245634.20240616071135.jpg",
"author": "Insight Editions",
"discount": "0",
"publisher": "Insight Editions",
"pubdate": "20230606",
"isbn": "9798886631418",
"description": ""
}
""";
// 1. map으로 변환
Map<String, dynamic> map = jsonDecode(dummyData);
// 2. 객체로 변환
Book book = Book.fromJson(map);
expect(book.discount, "0");
print(book.toJson());
});
}
Repository 구현
flutter pub add http
터미널에 입력
http 패키지란? : 앱 내에서 http 요청을 하기 위한 dart 패키지
book_repository_test.dart
import 'package:flutter_book_search_app/data/repository/book_repository.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('BookRepository test', () async {
BookRepository bookRepository = BookRepository();
final books = await bookRepository.searchBooks('harry');
expect(books.isEmpty, false);
for (var book in books) {
print(book.toJson());
}
});
}
book_repository.dart
import 'dart:convert';
import 'package:flutter_book_search_app/data/model/book.dart';
import 'package:http/http.dart';
class BookRepository {
Future<List<Book>> searchBooks(String query) async {
final client = Client();
final response = await client.get(
Uri.parse('https://openapi.naver.com/v1/search/book.json?query=$query'),
headers: {
'X-Naver-Client-Id': 'i7OQwpawURkbtcLbA0_t',
'X-Naver-Client-Secret': 'hh1_pDPDiD',
},
);
// Get 요청 시 성공 => 200
// 응답 코드가 200일 때!
// body 데이터를 jsonDecode 함수 사용해서, map으로 바꾼 후 List<Book>으로 반환
if (response.statusCode == 200) {
Map<String, dynamic> map = jsonDecode(response.body);
final items = List.from(map['items']);
// MappedIterable 이라는 클래스에 함수를 넘겨줄테니
// 나중에 요청하면, 그때 List(items)들을 하나씩 반복문을 돌면서
// 내가 넘겨준 함수를 실행시켜서 새로운 리스트로 돌려줘!
final iterable = items.map((e) {
return Book.fromJson(e);
});
final list = iterable.toList();
return list;
}
// 아닐 때, 빈 리스트 반환
return [];
}
}
HomeViewModel 구현
// 1. 상태 클래스 만들기
import 'package:flutter_book_search_app/data/model/book.dart';
import 'package:flutter_book_search_app/data/repository/book_repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomeState {
List<Book> books;
HomeState(this.books);
}
// 2. 뷰모델 만들기 - Notifier 상속
class HomeViewModel extends Notifier<HomeState> {
@override
HomeState build() {
return HomeState([]);
}
Future<void> searchBooks(String query) async {
final bookRepository = BookRepository();
final books = await bookRepository.searchBooks(query);
state = HomeState(books);
}
}
// 3. 뷰모델 관리자 만들기 - NotifierProvider 객체
final homeViewModelProvider = NotifierProvider<HomeViewModel, HomeState>(() {
//
return HomeViewModel();
});
HomeViewModel 테스트
import 'package:flutter_book_search_app/ui/home/home_view_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('HomeViewModel test', () async {
//
// 앱 내에서는 ProviderScope 안에서 뷰모델을 관리
// 테스트 환경에서는 Widget을 생성하지 않고 테스트 할 수 있게 ProviderContainer 제공
final providerContainer = ProviderContainer();
addTearDown(providerContainer.dispose);
final homeVm = providerContainer.read(homeViewModelProvider.notifier);
// 처음 homeViewModel의 상태 => 빈 리스트 인걸 테스트
final firstState = providerContainer.read(homeViewModelProvider);
expect(firstState.books.isEmpty, true);
// HomeViewModel에서 searchBooks 메서드 호출 후 상태가 변경이 정상적으로 되는지 테스트
await homeVm.searchBooks('harry');
final afterState = providerContainer.read(homeViewModelProvider);
expect(afterState.books.isEmpty, false);
afterState.books.forEach((element) {
print(element.toJson());
});
});
}
HomeViewModel 데이터 바인딩
Homepage 수정 : ConsumerStateful 위젯 사용
HomeBottomSheet 수정
Homepage에서 HomeBottomSheet 생성자에 Book 전달
DetailPage에서 Book 전달받을 수 있게 수정
HomeBottomSheet 에서 DetailPage로 Book 전달
*DetailPage는 뷰모델이 필요하지 않음
2. 인사이트 클럽
책 <미니멀리스트 창업가> 사힐 라빈지아 지음, 박재영 옮김, CAPITALEDGE
Chapter 8. 어디를 향해 갈 것인가
이기적일 만큼 자기에게 가장 적합한 사업을 만들어야 한다.
그러면서도 가장 이타적인 커뮤니티를 함께 만들어나가야 한다.
그리고 그 과정에서 자신의 행복을 우선해야 한다.
...
원하는 것이 무엇인지 알게 되면, 그것을 이루는 방법도 찾을 수 있다
(p.295~296)
원하는 것이 무엇인지 알기 위해
어떤 것을 고민해보면 좋을까.
우리 팀이 가진 각자의
강점과 역량
각 팀원들이 달성하기
원하는 목표
추구하는 가치와 의미,
라이프스타일, 인생의 우선순위 등
닮고 싶은, 참고가 될 만한 다른 브랜드
혹은 인물이 있는지?
고객의 문제에 대한 깊은 공감
(고객의 입장을 직접/간접 체험 필요)
고객의 삶이 더 나아지길 바라는 진심
(진심의 온도는 몇 도 인가?)
고객층의 다양한 유형 파악 및 카테고리화
적합한 표현방식과 커뮤니케이션 스타일은
어떻게 할 것인가?
...
종합해본다면 힌트를 얻을 수 있지 않을까?
댓글
댓글 쓰기