1. 기본 디자인
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:future_app_02/post_page.dart'; void main() { runApp(ProviderScope(child: const MyApp())); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: PostPage(), ); } }
import 'package:flutter/material.dart'; class PostPage extends StatelessWidget { PostPage(); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ AspectRatio( aspectRatio: 1 / 1, child: Image.network("https://picsum.photos/id/50/200/200", fit: BoxFit.cover), ), Text("id : 1, userId: 2, title : 제목1"), ElevatedButton(onPressed: () {}, child: Text("값변경")), ], ), ); } }
2. 의존성 추가하기
flutter_riverpod: ^2.5.1 dio: ^5.4.3+1
3. RestTemplat
스프링 입장에서는 응답받을때까지 스레드가 내려가면 안되고 기다리는 블락이 당함
왜? 스레드로 도니까
메인 스레드는 통신 요청하고 일하러 가고
이런 라이브러리들은 내부에 다 스레드들이 있음
스프링은 이벤트 루프 기반(비동기)가 아니기 때문에 기다려야 함 → 블락당함(대기 상태)
4. 멀티스레드 VS 단일 스레드 기반에 비동기 통신
단일 스레드 기반에 비동기식은 스레드가 하나
코드가 내려가면서 하나하나 실행하다가 통신 요청하면 cpu는 블락이 안당하고 계속 일하다가
스택이 종료되고 큐가 할일이 없고 아무것도 할일이 없어지면 통신요청한 곳으로 돌아가서 다시 실행함 → 이것이 프로미스
cpu가 멍때리는 시간이 아예 없음
스레드 기반은 동작은 똑같이함
코드가 내려가면서 하나하나 실행하다가 통신 요청하면 cpu가 메서드를 콜함
내부적으로 스레드를 가지고 있기 때문에 메인 스레드는 내려가고 새로운 스레드가 블락을 당해 멍때림
이 두개가 컨텍트 스위칭이 됨
0.1초로 rr(라운드로빈)하면 가서 멍때리다 일하고 멍때리다 일하기 때문에 비동기 방식보다 더 비효율적임
- 스레드를 쓰는게 무조건 안좋고 비동기식이 좋지 않은가?
- 비동기식이 속도가 빠른게 아니라 cpu를 안놀게 하는것 → 찾으로 가야 함
- 스레드방식은 cpu를 안멈추게 하는 것 → 리턴 받을 수 있어서 콜백받을 수 있음
EX) 왼쪽에 도화지와 오른쪽 도화지에 색깔을 동시에 칠할 것임
실제로 비동기식 방법은 그렇 수 없음
하지만 스레드 방식은 가능함
대신 진짜 빠르게 타임 슬립을 잡아서 왔다갔다하면 인간의 눈에는 같이 그리는 것으로 보임
끝나야 가는게 좋은것은 IO 방식 → 비동기식이 훨씬 유리함
동시에 그려야 하는 게임 같은 것 → 스레드가 유리함
- 서버는 하는 일이 단순함 : 요청을 받아서 전부 IO를 하는 방식
통신 요청이 들어오면 프로토콜을 가지고 통신 요청을 함
대신 자연어 처리가 아니라 URL로 처리함
대충 디비가서 대충 U로 시작하는 테이블 찾아서 해줘 이런게 아니라 HTTP 프로토콜을 지켜서 URL로 요청함
로그인 파일 줘 → 파일 내용 읽기 io 발생 → 응답
디비한테 가는 것도 io / dbms는 메모리에 있지만 실제 디비 내용은 하드디스크에 디비를 만드는 것
모든 요청이 io라 모두 동기방식으로 돌면 한명이 무조건 io를 가서 기다리고 있음 → 메인 스레드가 멍때리다 응답해줌
두명이 동시에 요청 → 메인 스레드는 받아서 io처리하러 가면서 멍때려서 추가 요청을 못받음
cpu는 엄청나게 빠름 → 프로세스로만 처리하면 순차적으로 들어와도 빨리 처냄
커피 주문받는게 cpu , 커피 만드는 것이 io라고 비유
여러명이 동시에 요청이 들어오면 웹서버는 io 때문에 스레드를 만듦
넌블라킹 io = nio는 멍때리는 시간을 없애겠다는 것 / 비동기 서버
멍때리는 시간을 없애려면 스레드로 안됨 → 자체적으로 멍때림
단일 스레드 비동기방식으로 만들어야 함
스프링은 스레드 기반으로 서버가 돌아서 io가 일어나서 느림
스레드가 만들어지는 것도 오래걸려서 풀링 방식으로 미리 만들어서 공유함 / 속도 올리려고
동시에 서비스 때리고 레파지토리 때려서 동시에 디비 때리면 스레드가 동시에 블락걸림
두개가 wright 요청하고 1개가 들어가고 1개는 트랜잭션으로 블락당해서 기다려야 함 / 데이터가 바뀔 수 있기 때문에 전의 작업이 끝난후 작업이 시작됨
- 비동기식 단일 스레드
스레드 1개에 여러요청은 다 큐에 담고 순차적으로 처리함
서비스 → 레파지토리 → 메모리한테 io 시켜놓고 메모장에 적고 빠져나와서 다음 요청도 다 받을 수 있음
할일이 없다는 것은 잠깐 요청이 없을때 메모장 가서 이벤트 루프가서 확인하고 팬딩이면 다시 일하러가고 팬딩이 끝나면 그 이벤트를 처리함
- 노드제이에스는 자바스크립트 런타임 환경
자바스크립트로 작업한 것을 실행해주는 것
노드제이에스로 서버를 만드는 이유는 단일 스레드 기반이라 퍼포먼스가 좋음 서버에 특화되어있음
- 스프링이 노드를 보면서 내가 느리니까 사람들이 안쓰겠다는 생각을 할 것
그래서 스프링도 단일 스레드로 만듦
스프링 내부에 엔진을 톰켓을 쓰고 있고 톰켓이 10?부터 제티?인가 나옴
선택해서 쓸 수 있음
제티는 단일 스레드도 사용할 수 있음
톰켓도 선택해서 사용할 수 있도록 했음
그래서 소프트웨어도 바뀐것이 플럭스 임
스레드 기반 다음에 새로운 버전이 웹 플럭스로 푸쉬를 해줌
⇒ 자바, 스프링, 소켓통신, 템플릿엔진, 디비쿼리, 운영체제, 네트워크, 자료구조, 알고리즘
4. Dio 사용 이유
- req : header가 들어감
- ResponseDto.class → json데이터를 자바 오브젝트로 받기 위한 클래스 세팅
- 스레드가 돌고 포스트 요청, url, 헤더에 넣을 정보, 응답방식으로 받아주기만하면 됨
- 귀찮으니까 HTTP URL 커넥션을 안쓴 것 - 플러터도 마찬가지 그래서 DIO를 사용
// RestTemplate 생성 RestTemplate restTemplate = new RestTemplate(); // 요청 매개변수 설정 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<RequestDto> request = new HttpEntity<>(requestDto, headers); // HTTP 요청 및 응답 처리 ResponseDto responseDto = restTemplate.exchange(url, HttpMethod.POST, request, ResponseDto.class).getBody();
5. 레파지토리 만들기
- 데이터를 파싱해서 받아줌
- 비어있을 수 있을때 ? 사용하기
- 이정도면 확실하면 !해도 됨
- 내부적 리플렉션을 못함
- 내부적으로 들어가서 해주지는 못함
- 하나의 타입밖에 안되기에 사용하는 것은 비추
- 컬렉션을 받으면 리스트, 단일 데이터는 맵 타입으로 사용
- final을 적으면 자동으로 바꿔줌
- dynamic을 사용하면 모든 타입을 받아줌 → 다운캐스팅만 하면 됨
- res 안에 바디와 헤더가 다 들어가 있음
- res.data: 바디 데이터
- print해서 잘 받아졌는지 통신 여부 확인하기
import 'package:dio/dio.dart'; class PostRepository { Dio dio = Dio(); // 레파지토리가 여러개있을 수 있기에 따로 빼야 함 Future<void> findById(int id) async { final response = await dio.get("https://jsonplaceholder.typicode.com/posts/${id}"); final body = response.data!; print(body); print(body.runtimeType); } }
6. 레파지토리 테스트하기
- findById → 단일 객체 new → await → 빠져나옴 → main 종료 → print 값을 확인할 수 없음
import 'package:future_app_02/post_repository.dart'; void main(){ PostRepository().findById(1); }
- findById → 단일 객체 new → await → 빠져나옴 → 기다림 → Future가 벗겨질때까지 기다림 → main 종료 안됨
import 'package:future_app_02/post_repository.dart'; void main() async{ // Future를 리턴할 때만 사용 await PostRepository().findById(1); }
7. 테스트 결과를 보고 타입 정하기
- 통신해서 그냥 받아서 응답함
import 'package:dio/dio.dart'; class PostRepository { Dio dio = Dio(); // 레파지토리가 여러개있을 수 있기에 따로 빼야 함 Future<Map<String, dynamic>> findById(int id) async { final response = await dio.get("https://jsonplaceholder.typicode.com/posts/${id}"); final body = response.data!; // print(body); // print(body.runtimeType); return body; } }
import 'package:future_app_02/post_repository.dart'; void main() async{ // Future를 리턴할 때만 사용 final body = await PostRepository().findById(1); Map <String, dynamic> body2 = await PostRepository().findById(1); print(body); print(body2); }
8. 문제점
- watch 로 요청해서 데이터를 받아서 화면에 뿌리는데 http headere를 받을 수 없음
- body를 리턴해주니까 header 응답이 안됨
- respone를 통으로 리턴을 해주면 레파지토리에서 파싱이 안됨
- post?에서 받아서 주면 파싱이 가능함
- 뷰는 뷰 모델에 요청 → 레파지토리 때림 → 통신 → map을 리턴 → 뷰모델이 상태값을 갱신 → 뷰에 응답을 해주진 않고 상태값만 받음 / 리턴이 없음 → 컨슈머가 보고 있으니까 다시 그릴 수 있음
- 뷰가 뷰모델한테 의존함
- 뷰가 레파지토리에 의존하면 상태관리가 안되고 통신데이터를 받아서 뷰가 if해서 코드를 뷰에서 짜야함
- 통신이 필요하면 레파지토리를 때리고 필요없으면 ?????
- 레파지토리가 api 요청해서 뷰모델이 받음 → 뷰모델의 책임은 상태 변경, 관리 → 오브젝트로 파싱 역할을 누가할지 정해야함
- 파싱의 책임은 레파지토리
- 전환을 레파지토리에서 해야함
- 프라이베이트 설정 → getter 만들기
class Post{ int _userId; int _id; String _title; String _body; String get Title => _title; }
- 노가다 코드
import 'package:dio/dio.dart'; import 'package:future_app_02/post.dart'; class PostRepository { Dio dio = Dio(); // 레파지토리가 여러개있을 수 있기에 따로 빼야 함 Future<Post> findById(int id) async { final response = await dio.get("https://jsonplaceholder.typicode.com/posts/${id}"); final body = response.data!; return Post( userId: body["userId"], id: body["id"], title: body["title"], body: body["body"], ); } }
- 오버로딩으로 안되있어서 이름이 있는 생성자를 만들어야 함
class Post { int userId; int id; String title; String body; Post({ required this.userId, required this.id, required this.title, required this.body, }); // 이름이 있는 생성자 만들기 Post.fromMap(Map<String, dynamic> body) : userId = body["userId"], id = body["id"], title = body["title"], body = body["body"]; }
import 'package:dio/dio.dart'; import 'package:future_app_02/post.dart'; class PostRepository { Dio dio = Dio(); // 레파지토리가 여러개있을 수 있기에 따로 빼야 함 Future<Post> findById(int id) async { final response = await dio.get("https://jsonplaceholder.typicode.com/posts/${id}"); final body = response.data!; // Post.fromMap 메서드를 사용하여 Map을 Post 객체로 변환 final post = Post.fromMap(body); return post; } }
- factory는 싱글톤 만들 때 사용하는 것 / 없어도 됨
9. 테스트하기
import 'package:future_app_02/post.dart'; import 'package:future_app_02/post_repository.dart'; void main() async{ // Future를 리턴할 때만 사용 Post post = await PostRepository().findById(1); print(post.userId); print(post.id); print(post.title); print(post.body); Map<String, dynamic> changePost = post.toMap(); print(changePost); }
Share article