변경하는 이유
1. 화면에 로직이 들어가면 관리가 어렵다.
(화면은 화면을 구성하는데에만 쓴다)
2. 한 종류의 통신을 여러 위젯에서 쓸 경우

controller, model(dto, repository), view(pages, widgets) 폴더로 나누기

기존 파일들을 각각의 폴더에 넣기
import 'package:flutter/material.dart';
import 'package:flutter_http_1/post/controller/post_table_controller.dart';
import 'package:flutter_http_1/post/view/pages/list_page.dart';
import 'package:flutter_http_1/routes.dart';
import 'package:provider/provider.dart';
void main(){
// controller 는 사용하기 전에 주입을 해줘야 한다
// 프로젝트가 켜지면 하나의 controller 로만 사용하기 어렵다
// 주입을 해주는 ChangeNotifierProvider를 여러개 사용하고 싶다
// MultiProvider를 사용한다
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (BuildContext context) => PostTableController()),
],
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Routes.goRouter,
);
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
import '../model/dto/post_dto.dart';
class PostTableController extends ChangeNotifier {
List<PostDTOTable>? _postDTOTableList;
List<PostDTOTable>? get postDTOTableList => _postDTOTableList;
void setPostDTOTableList(){
PostRepository.instance.getDTOList().then((value){
_postDTOTableList = value;
notifyListeners();
});
}
}
// post_dto.dart
// 테이블 용
class PostDTOTable {
int userId; // 유저 번호
int id; // 글 번호
String title; // 제목
// 생성자
PostDTOTable({
required this.userId,
required this.id,
required this.title,
});
// 생성자를 이용한 팩토리 함수
factory PostDTOTable.fromJson(dynamic json) => PostDTOTable(
userId: json["userId"],
id: json["id"],
title: json["title"],
);
// 팩토리를 이용한 함수
static List<PostDTOTable> fromJsonList(List jsonList) {
return jsonList.map((json) => PostDTOTable.fromJson(json)).toList();
}
}
// 상세 페이지 용
class PostDTODetail {
int userId; // 유저 번호
int id; // 글 번호
String title; // 제목
String body;
PostDTODetail(
{required this.userId,
required this.id,
required this.title,
required this.body}); // 내용
}
// post_repository.dart
import 'dart:convert';
import 'package:flutter_http_1/post/model/dto/post_dto.dart';
import 'package:http/http.dart' as http;
class PostRepository{
// 싱글톤 - 해당 타입의 객체가 프로그램에서 단 1개
// 스태틱 변수 선언
static PostRepository? _instance;
// 퍼블릭 생성자 제거
// dart에서 private은 맨 앞에 언더바를 붙인다
PostRepository._();
// 싱글톤 객체 getter
static PostRepository get instance => _instance ??= PostRepository._();
// 통신은 실패할 수 있다 == nullable
Future<List<PostDTOTable>?> getDTOList() async {
String url = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return PostDTOTable.fromJsonList(jsonDecode(response.body));
} else {
return null;
}
}
}
// void main() {
// PostRepository.instance;
// PostRepository.instance;
// PostRepository.instance;
// }
// list_page.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_http_1/post/controller/post_table_controller.dart';
import 'package:flutter_http_1/post/model/dto/post_dto.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
import 'package:flutter_http_1/routes.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
class ListPage extends HookWidget {
const ListPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// final listState = useState<List<PostDTOTable>?>(null);
final controller = context.watch<PostTableController>();
useEffect(() {
controller.setPostDTOTableList();
}, []);
return Scaffold(
body: SafeArea(
child: ListView(
children: controller.postDTOTableList?.map((e) => ListItem(postDTOTable: e)).toList() ?? [],
),
),
);
}
}
class ListItem extends StatelessWidget {
PostDTOTable postDTOTable;
ListItem({Key? key, required this.postDTOTable}) : super(key: key);
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.pushNamed(Routes.detail);
},
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Column(
children: [
Text("유저번호 : ${postDTOTable.userId}"),
Divider(),
Text("글 번호 : ${postDTOTable.id}"),
Divider(),
Text("글 제목 : ${postDTOTable.title}"),
],
),
),
);
}
}


구성순서
모델(DTO구성)
Repository 구성
상태 구성
화면 구성
import 'package:flutter/material.dart';
import 'package:flutter_http_1/post/controller/post_table_controller.dart';
import 'package:flutter_http_1/post/view/pages/list_page.dart';
import 'package:flutter_http_1/routes.dart';
import 'package:provider/provider.dart';
void main(){
// controller 는 사용하기 전에 주입을 해줘야 한다
// 프로젝트가 켜지면 하나의 controller 로만 사용하기 어렵다
// 주입을 해주는 ChangeNotifierProvider를 여러개 사용하고 싶다
// MultiProvider를 사용한다
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (BuildContext context) => PostTableController()),
],
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Routes.goRouter,
);
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
import '../model/dto/post_dto.dart';
class PostTableController extends ChangeNotifier {
List<PostDTOTable>? _postDTOTableList;
List<PostDTOTable>? get postDTOTableList => _postDTOTableList;
void setPostDTOTableList(){
PostRepository.instance.getDTOList().then((value){
_postDTOTableList = value;
notifyListeners();
});
}
}
// post_dto.dart
// 테이블 용
class PostDTOTable {
int userId; // 유저 번호
int id; // 글 번호
String title; // 제목
// 생성자
PostDTOTable({
required this.userId,
required this.id,
required this.title,
});
// 생성자를 이용한 팩토리 함수
factory PostDTOTable.fromJson(dynamic json) => PostDTOTable(
userId: json["userId"],
id: json["id"],
title: json["title"],
);
// 팩토리를 이용한 함수
static List<PostDTOTable> fromJsonList(List jsonList) {
return jsonList.map((json) => PostDTOTable.fromJson(json)).toList();
}
}
// 상세 페이지 용
class PostDTODetail {
int userId; // 유저 번호
int id; // 글 번호
String title; // 제목
String body;
PostDTODetail(
{required this.userId,
required this.id,
required this.title,
required this.body}); // 내용
factory PostDTODetail.fromJson(dynamic json) => PostDTODetail(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"]
);
}
// post_repository.dart
import 'dart:convert';
import 'package:flutter_http_1/post/model/dto/post_dto.dart';
import 'package:http/http.dart' as http;
class PostRepository{
// 싱글톤 - 해당 타입의 객체가 프로그램에서 단 1개
// 스태틱 변수 선언
static PostRepository? _instance;
// 퍼블릭 생성자 제거
// dart에서 private은 맨 앞에 언더바를 붙인다
PostRepository._();
// 싱글톤 객체 getter
static PostRepository get instance => _instance ??= PostRepository._();
// 통신은 실패할 수 있다 == nullable
Future<List<PostDTOTable>?> getDTOList() async {
String url = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return PostDTOTable.fromJsonList(jsonDecode(response.body));
} else {
return null;
}
}
Future<PostDTODetail?> getDTO(int postId) async{
String url = "https://jsonplaceholder.typicode.com/posts/$postId";
http.Response response = await http.get(Uri.parse(url));
if(response.statusCode == 200){
return PostDTODetail.fromJson(jsonDecode(response.body));
} else {
return null;
}
}
}
// datail_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_http_1/post/model/dto/post_dto.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
class DetailPage extends HookWidget {
final int postId;
const DetailPage({Key? key, required this.postId}) : super(key: key);
Widget build(BuildContext context) {
final detailState = useState<PostDTODetail?>(null);
useEffect((){
PostRepository.instance.getDTO(postId).then((value){
detailState.value = value;
});
},[]);
// 3항 연산자 = state가 null 이 아니면 / null 이면
// data !=null ? a : b
return Scaffold(
body: SafeArea(
child: detailState.value != null ? Column(
children: [
Text("유저번호: ${detailState.value!.userId}"),
Text("글번호: ${detailState.value!.id}"),
Text("제목: ${detailState.value!.title}"),
Text("내용: ${detailState.value!.body}"),
],
) : Text("값이 없습니다."),
),
);
}
}
// list_page.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_http_1/post/controller/post_table_controller.dart';
import 'package:flutter_http_1/post/model/dto/post_dto.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
import 'package:flutter_http_1/routes.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
class ListPage extends HookWidget {
const ListPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// final listState = useState<List<PostDTOTable>?>(null);
final controller = context.watch<PostTableController>();
useEffect(() {
controller.setPostDTOTableList();
}, []);
return Scaffold(
body: SafeArea(
child: ListView(
children: controller.postDTOTableList?.map((e) => ListItem(postDTOTable: e)).toList() ?? [],
),
),
);
}
}
class ListItem extends StatelessWidget {
PostDTOTable postDTOTable;
ListItem({Key? key, required this.postDTOTable}) : super(key: key);
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.pushNamed(Routes.detail, params: {"postId" : postDTOTable.id.toString()});
},
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Column(
children: [
Text("유저번호 : ${postDTOTable.userId}"),
Divider(),
Text("글 번호 : ${postDTOTable.id}"),
Divider(),
Text("글 제목 : ${postDTOTable.title}"),
],
),
),
);
}
}



공공데이터 api 신청하기


미리보기 클릭시 다음과 같은 json 형식으로 데이터를 볼 수 있음.


--web-renderer html
코드넣기
import 'package:flutter/material.dart';
import 'package:flutter_attr_busan/attr_page.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
home: AttrPage(),
);
}
}
class AttrDTO {
// MAIN_TITLE
// CNTCT_TEL
// MAIN_IMG_THUMB
String mainTitle;
String cntctTel;
String mainImgThumb;
AttrDTO({
required this.mainTitle,
required this.cntctTel,
required this.mainImgThumb
}) ;
factory AttrDTO.fromJson(dynamic json) => AttrDTO(
mainTitle: json["MAIN_TITLE"],
cntctTel: json["CNTCT_TEL"],
mainImgThumb: json["MAIN_IMG_THUMB"]
);
static List<AttrDTO> fromJsonList(List jsonList){
return jsonList.map((e) => AttrDTO.fromJson(e)).toList();
}
}
import 'package:flutter/material.dart';
import 'package:flutter_attr_busan/attr_dto.dart';
class AttrItem extends StatelessWidget {
final AttrDTO attrDTO;
const AttrItem({Key? key,required this.attrDTO}) : super(key: key);
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
Image.network(attrDTO.mainImgThumb),
Text(attrDTO.mainTitle),
Text(attrDTO.cntctTel),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_attr_busan/attr_dto.dart';
import 'package:flutter_attr_busan/attr_item.dart';
import 'package:flutter_attr_busan/attr_repository.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class AttrPage extends HookWidget {
const AttrPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final listState = useState<List<AttrDTO>>([]);
useEffect((){
AttrRepository.getDTO().then((value) {
listState.value = value ?? [];
});
},[]);
return Scaffold(
body: SafeArea(
child: ListView(
children: listState.value.map((e) => AttrItem(attrDTO: e)).toList(),
),
),
);
}
}
import 'dart:convert';
import 'package:flutter_attr_busan/attr_dto.dart';
import 'package:http/http.dart' as http;
class AttrRepository {
static Future<List<AttrDTO>?> getDTO() async {
final String url = "https://apis.data.go.kr/6260000/AttractionService/getAttractionKr?serviceKey=KmPzcGQ5PCcraF4sRF2nw3lzJFd%2FO32LKptEAZtAFZXe8y8cBaNU8xo5HXLMQhmQvo0hbUPQawobLltt%2FX25YQ%3D%3D&pageNo=1&numOfRows=10&resultType=json";
http.Response response = await http.get(Uri.parse(url));
if(response.statusCode == 200){
dynamic json = jsonDecode(response.body);
return AttrDTO.fromJsonList(json["getAttractionKr"]["item"]);
} else {
return null;
}
}
}
