내배캠 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-Secret


Model 클래스 생성 및 테스트

응답받은 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)



원하는 것이 무엇인지 알기 위해 
어떤 것을 고민해보면 좋을까.




우리 팀이 가진 각자의 
강점과 역량


각 팀원들이 달성하기 
원하는 목표


추구하는 가치와 의미, 
라이프스타일, 인생의 우선순위 등


닮고 싶은, 참고가 될 만한 다른 브랜드 
혹은 인물이 있는지?



고객의 문제에 대한 깊은 공감
(고객의 입장을 직접/간접 체험 필요)


고객의 삶이 더 나아지길 바라는 진심
(진심의 온도는 몇 도 인가?)


고객층의 다양한 유형 파악 및 카테고리화


적합한 표현방식과 커뮤니케이션 스타일은
어떻게 할 것인가?


...




종합해본다면 힌트를 얻을 수 있지 않을까?








댓글

이 블로그의 인기 게시물

내배캠15일차_map() reduce() fold()

내배캠16일차_Flutter_스토어 앱 만들기

내배캠 [3주차 WIL]