24일차에는 데이터를 직렬화하는 Serialization에 대해 학습했다. 이번주는 클래스를 활용해 데이터를 처리하는 방식에 대해 학습할 예정이다.
학습한 내용
- 데이터 직렬화 (Serialization)
- Public API, Private API
- factory 생성자
데이터 직렬화(Serialization)는 메모리를 디스크에 저장하거나 네트워크 통신에 사용하기 위한 형식으로 변환하는 것을 의미한다.
데이터 직렬화 언어로는 yaml
, xml
, json
등이 있다.
역직렬화(Deserialization)는 반대로 디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 다시 변환하는 것이다.
API는 Application Programming Interface의 약자로 여러 프로그램들과 데이터베이스, 그리고 기능들의 상호 통신 방법을 규정하고 도와주는 매개체이다.
API는 아래와 같은 3가지 역할을 수행한다.
API의 역할
1. API는 서버와 데이터베이스에 대한 출입구 역할을 한다.
2. API느 애플리케이션과 기기가 원활하게 통신할 수 있도록 한다.
3. API는 기기와 운영체제에 상관없이 누구나 동일한 액세스를 얻을 수 있도록 모든 접속을 표준화한다.
이러한 API는 접근 방식에 따라 3가지의 유형으로 구분될 수 있다.
API의 유형
1. Private API
Private API는 내부 API로, 기업이나 연구 단체 등에서 자체 제품과 운영 개선을 위해 단체 내부에서만 사용한다. 따라서 제 3자에게 노출되지 않는다.
- Public API
Public API는 개방형 API로, 모두에게 공개된다. Public API 중에서도 접속하는 대상에 대한 제약이 없는 경우에는 Open API라고 한다.
- Partner API
Partner API는 특정 비즈니스 파트너 간의 데이터 공유를 위해 사용된다. 따라서 동의하는 특정인들만 사용할 수 있다.
또한 아키텍처 스타일에 따라 API의 종류를 구분할 수도 있는데 SOAP, RPC, REST API, GraphQL 등으로 구분된다.
(해당 포스팅에서 이 내용은 깊게 다루지 않음)
API는 아래와 같은 장점들이 있다.
API 장점
1. 개발자들이 애플리케이션 코드를 작성하는 방법을 표준화함으로써, 간소화되고 빠른 프로세스 처리를 가능하게 한다.
2. 소프트웨어를 통합할 때 개발자들 간의 협업을 용이하게 한다.
[Flutter] 스나이퍼팩토리 22일차에서 팩토리 생성자에 대해 간단히 정리했었다.
해당 포스팅에서 좀 더 자세한 내용을 작성하고자 한다.
Dart에서 factory
는 새로운 인스턴스를 생성하지 않는 생성자를 구현할 때 사용한다고 설명되어 있다. 이는 싱글톤 패턴을 구현하는 것과 같다.
싱글톤 패턴(singleton-pattern)
소프트웨어 디지인 패턴에서 싱글톤 패턴을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이러한 디자인 유형을 싱글톤 패턴이라고 한다.
factory
는 아래와 같은 특징을 가진다.
factory 특징
- 기존에 이미 생성된 인스턴스가 있다면 return하여 재사용한다.
- 하나의 클래스에서 하나의 인스턴스만 사용한다.
- 서브 클래스 인스턴스를 리턴할 때 사용할 수 있다.
- 팩토리 생성자에서는 this에 접근할 수 없다.
아래 예시 코드에서 Logger()
가 호출될 때마다 Logger factory
생성자가 호출된다. 처음 호출된다면 인스턴스를 새로 생성하여 반환하고, 한 번 더 호출하면 기존 인스턴스를 반환한다.
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache =
<String, Logger>{};
// 1번
factory Logger(String name) {
return _cache.putIfAbsent( // 2번
name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
또한 아래의 예시처럼 서브 클래스의 인스턴스를 반환할 때에도 사용할 수 있다.
class Book {
// named generative
Book.comic(String title) : this(title, "Champ");
// named factory
factory Book.comic(String title) {
return ComicBook(title);
}
}
class ComicBook extends Book {
ComicBook(String title) : super(title, "Champ");
}
- 강아지 사진 앱 만들기
- 추가 내용 정리
공개된 API를 분석하고, 클래스를 활용하여 강아지 사진을 보여주는 앱을 만들고자 한다.
- 아래의 API를 사용해 데이터를 요청한다.
- 반드시 네트워크의 데이터를 받아와서 제작한 클래스에 적용해야 한다.
- 3개의 강아지 사진이 갤러리 형태로 나오도록 만든다.
- 강아지 사진을 클릭하면 강아지 사진이 Dialog로 나오게 한다.
import 'package:flutter/material.dart';
import 'package:my_app/page/main_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// root Widget
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: MainPage(), // 홈 페이지 호출
);
}
}
main.dart
에서는 MainPage
를 호출한다.
class Dog {
String message;
String status;
Dog({
required this.message,
required this.status,
});
factory Dog.fromJson(Map<String, dynamic> json) {
return Dog(
message: json['message'],
status: json['status'],
);
}
}
강아지의 사진 링크와 상태를 가지고 있는 모델 클래스를 Dog
로 생성했다. 기본 생성자를 작성하고 팩토리 생성자를 json 값을 받아 멤버 변수에 매핑하여 직렬화하도록 작성했다.
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:my_app/model/Dog.dart';
class MainPage extends StatelessWidget {
MainPage({super.key});
final Dio dio = Dio(); //dio 객체
final String url = 'https://dog.ceo/api/breeds/image/random'; //데이터 요청 url
//데이터 가져오기
Future<Dog> getData() async {
var response = await dio.get(url); //데이터 요청
//데이터를 정상적으로 받아온 경우
if (response.statusCode == 200) {
//데이터를 직렬화하여 리턴
return Dog.fromJson(response.data);
} else {
throw Exception('Could not get data');
}
}
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //그리드뷰 한 줄에 3개의 요소
),
itemCount: 3, //총 아이템 개수
itemBuilder: (context, index) {
return FutureBuilder(
future: getData(), //데이터 요청 바인딩
builder: (context, snapshot) {
//데이터 로딩 중
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
//데이터가 없는 경우
if (!snapshot.hasData) return const Text('데이터 없음!');
//데이터를 받아온 경우
return GestureDetector(
//사진 다이얼로그
onTap: () => showDialog(
context: context,
builder: (context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.all(0),
actionsAlignment: MainAxisAlignment.center,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), //테두리 곡선
),
//이미지 출력
content: Image.network(
fit: BoxFit.contain,
snapshot.data!.message,
),
actions: [
//닫기 버튼
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
)
],
);
},
),
//그리드뷰 아이템 이미지
child: Image.network(
fit: BoxFit.cover,
snapshot.data!.message,
loadingBuilder: ((context, child, loadingProgress) {
//이미지 로딩 완료
if (loadingProgress == null) {
return child;
}
//이미지 로딩 중
return const Center(child: LinearProgressIndicator());
}),
),
);
},
);
},
),
);
}
}
getData()
는 네트워크에 데이터를 요청하여 가져와 Dog
클래스에 직렬화를 수행하여 리턴하는 함수이다.
본문에서는 그리드 뷰를 사용해 갤러리를 구성한다. 한 줄에 3개의 아이템이 출력되도록 설정했고, 총 3개의 아이템을 가져오도록 itemCount
를 3으로 설정했다.
그리드 뷰의 요소로는 FutureBuilder
를 사용해 앞에서 작성한 getData()
로 강아지 사진에 대한 데이터를 가져왔다.
데이터를 성공적으로 받아온 경우 GestureDetector
의 onTap
이벤트로 다이얼로그를 띄워주었다. 다이얼로그에서 이미지를 출력했으며 텍스트버튼을 눌렀을 때, 다이얼로그를 닫는 기능을 추가했다.
마지막으로 본문에서는 Image.network
를 사용해 이미지를 출력했는데 여기서 loadingBuilder
를 사용해 이미지 로딩중에는 LinearProgressIndicator
를 보여주도록 했다.
코드에서 GridView.builder
의 itemCount
의 값을 변경하면 더 많은 사진을 불러올 수 있다. 아래는 itemCount
를 30으로 변경한 결과이다. 더욱 갤러리 같은 화면을 만들 수 있다.
과제로 강아지 사진 앱을 제작하고 강의를 수강한 뒤 아쉬웠던 점을 정리했다.
getData()
에서 데이터를 받아왔을 때 올바르지 않은 null 값이 전달 될 수도 있다. 따라서 getData()
아래와 같이 nullable로 작성하면 더 좋을 것 같다.
Future<Dog?> getData() async {
var response = await dio.get(url); //데이터 요청
//데이터를 정상적으로 받아온 경우
if (response.statusCode == 200) {
//데이터를 직렬화하여 리턴
return Dog.fromJson(response.data);
}
return null
}
Image.network
에 들어가는 image
String이 null인 경우를 확인하여 아래와 같이 값을 다르게 리턴하면 더 안전하고 좋은 코드가 될 수 있다.
return snapshot.data?.message == null
? SizedBox()
: GestureDetector(
...
);
"3개의 강아지 사진이 갤러리 형태로 나오도록 만드시오."라는 요구사항을 보고 3개의 요소를 넣었는데 한 줄에 3개의 요소를 출력한다는 의미였던 것 같다. ㅠㅠ
그리드뷰에서 아이템이 무한으로 생성되도록 하려면 itemCount
를 설정하지 않으면 된다.
추가로 showDialog()
에서 리턴한 AlertDialog
는 따로 widget
폴더에 dog_detail_dialog.dart
와 같은 파일로 생성해서 만들었으면 코드가 간결해지고 좋았을 것 같다.
스나이퍼팩토리 6주차가 시작되었다. 이번주에는 클래스를 활용하여 데이터를 직렬화하는 것을 반복적으로 할 예정이라고 한다. 아주 중요한 내용이니까 열심히 따라가야겠다!! 어제 도전과제가 어려워서 그걸 하고 오늘 과제를 하니 좀 쉽게 느껴진다. ㅋㅋㅋㅋ 6주차 첫 번째 날은 여기서 마무리