23.04.03(Flutter)

MH S·2023년 4월 3일

Flutter

목록 보기
12/17

flutter_http_1

hoop -> provider 로 변경

변경하는 이유

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

디렉토리 나누기

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


기존 파일들을 각각의 폴더에 넣기

main.dart

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,
    );
  }
}

post_table_controller.dart


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

// 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

// 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

// 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 구성
상태 구성
화면 구성

main.dart

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,
    );
  }
}

post_table_controller.dart


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

// 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

// 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;
    }
  }
}

detail_page.dart

// 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

// 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}"),
          ],
        ),
      ),
    );
  }
}

결과 화면




공공데이터 json 형식 통신하기


공공데이터 api 신청하기


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

Tip json데이터에서 가져온 이미지 파일이 꺠질시


--web-renderer html
코드넣기

main.dart

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(),
    );
  }
}

attr_dto.dart


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();
  }
}

attr_item.dart

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),
        ],
      ),
    );
  }
}

attr_page.dart

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(),
        ),
      ),
    );
  }
}

attr_repository.dart

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;
    }
  }
}

실행 결과

0개의 댓글