23/04/03 flutter

서지우·2023년 4월 3일
0

Flutter

목록 보기
5/7

폴더구조

구분하는 이유는 파일이 많아지기 때문에 구조화하기 위해서 directory폴더를 만들어 준다.

저번 시간에 실습한 파일들을 폴더로 구분해 주기 위해 아래와 같이 폴더 구조를 만든다.

ex) post - 게시글 데이터를 받아오는 directory


파일들을 폴더에 이동시켜준다.

그럼 아래 같은 창이 뜨는데 Refactorf를 해주면 된다.
(이렇게 하면 일일히 수정을 하지 않고 import가 변경된다.)


Provider

Provider를 사용할 때에는, 위젯 트리와 상관없이 상태(데이터)를 저장할 클래스를 생성하고, 해당 상태를 공유하는 공통 부모 위젯에 Provider를 제공(Provide)하고, 상태를 사용하는 곳에는 Provider의 데이터를 읽어서 사용하게 됩니다.

Hook -> Provider 변경

변경하는 이유
1. 화면에 로직이 들어가면 관리가 어렵다
(화면은 화면을 구성하는 데에만 집중)
2. 한 종류의 통신을 여러 위젯에서 쓸 경우
3. Provider 패턴을 쓰면 데이터 공유를 쉽게 할 수 있다

multiProvider

여러 개의 Proivder를 사용하는 경우 MultiProvider를 이용할 수 있다.

post_table_controller.dart

multiProvider의 예시를 위해 controller폴더 안에 만들어준다.

import 'package:flutter/foundation.dart';
import 'package:flutter_http_1/post/model/repository/post_repository.dart';
//foundation은 flutter를 구성하는 기초문법들
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();
    });
  }
}

list_page.dart(추가)

저번 시간에 했던 list_page dart파일에 다음과 같이 변경한다.

class ListPage extends HookWidget {
  const ListPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final controller = context.watch<PostTableController>();

    //useEffect(작동함수, 관찰할 상태 리스트);
    //빌드가 완료되면 작동한다
    //관찰하는 상태가 변경되면 작동한다
    //관찰하는 상태가 없으면 빌드 시 1번 작동한다
    useEffect((){
      controller.setPostDTOTableList();
    },[]);

    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: controller.postDTOTableList?.map((e) => ListItem(postDTOTable: e)).toList() ?? [],
            // Text(jsonState.value ?? "값이 없습니다"),//null이면 값이 없다고
        ),
      ),
    );
  }
}

main.dart(추가)

main문에서 추가해 준다.
(주석으로 쓰는 이유 적어놓음)

void main(){
  //controller는 사용하기 전에 주입을 해줘야 한다
  //프로젝트가 커지면 하나의 controller보다
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (BuildContext context) => PostTableController()),
      ],
      //여러가지를 동시에 넣어줄려면 multiprovider를 사용한다 
      //아님 일일히 하나씩 다 적어야하는 불편함이 있음
      child: MyApp(),
    ),
  );
}

MultiProvider를 통해 여러 개의 ChangeNotifierProvider를 사용하게 된다.


goRouter

go Route는 flutter에서 페이지 간 이동 시 URL기반의 API를 이용해서 쉽게 이동할 수 있도록 도와주는 패키지이다.

이용하기 위해서는 먼저 MaterialApp위젯을 MaterialApp.route로 변경해 주고, GoRouter 오브젝트 타입의 _router를 정의하여 routerConfig에 추가한다.

'/'route에 접근 시 예를 들어, '/userId'route에 접근 시 userId로 연결된다.

goRouter 참고

Detailpage 만들기

페이지가 넘어가는 걸 보기 위해 pages 폴더 안에 Detailpage dart파일을 만든다.

import 'package:flutter/material.dart';

class DetailPage extends StatelessWidget {
  const DetailPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const Text("디테일 페이지");
  }
}

routes 설정하기(goRouter)

lib폴더 안에 roures dart파일을 만들어 준다.

import 'dart:js';

import 'package:flutter_http_1/post/view/pages/detail_page.dart';
import 'package:flutter_http_1/post/view/pages/list_page.dart';
import 'package:go_router/go_router.dart';

class Routes {
  static const String table = 'table';
  static const String detail = 'detail';

  static final GoRouter goRouter = GoRouter(
    initialLocation: '/table',//가장 처음 페이지 이동할 곳
    routes: [
      GoRoute(
        name: Routes.table,
        path: '/table',
        builder: (context, state) => ListPage(),
      ),
      GoRoute(
        name: Routes.detail,
        path: '/detail',
        builder: (context, state) => DetailPage(),
      ),
    ],
  );
}

main.dart(추가)

main dart파일에서 materialApp.router설정해 준다.

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: Routes.goRouter,
    );
  }
}

list_page.dart(추가)

아이템리스트를 클릭했을 때 detail_page.dart파일로 넘어가게 하기 위해 아이템에 ontab이벤트를 넣어 준다.

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

실행하면 다음 화면과 같이 출력되면서

리스트 중 아무거나 선택을 하면 다음과 같은 페이지로 넘어가는 것을 볼 수 있다.


디테일 페이지로 통신하기

detail_page.dart(추가 및 변경)

list page에서 리스트들 중 어떤 것을 클릭하면 그 리스트의 아이템 정보들이 상세페이지로 넘어가 출력될 수 있도록 페이지를 만든다.

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

    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("값이 없습니다."),
      ),
    );
  }
}

post_dto.dart

상세페이지에 들어갈 dto들을 작성해준다.

//상세 페이지 용
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

통신을 하기 위해 repository 파일을 만들어준다.

//class PostRepository안에 넣어준다.
  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;
    }
  }
}

routes.dart(변경)

path를 detail page의 postId로 받게 설정해준다.

import 'dart:js';

import 'package:flutter_http_1/post/view/pages/detail_page.dart';
import 'package:flutter_http_1/post/view/pages/list_page.dart';
import 'package:go_router/go_router.dart';

class Routes {
  static const String table = 'table';
  static const String detail = 'detail';

  static final GoRouter goRouter = GoRouter(
    initialLocation: '/table',//가장 처음 페이지 이동할 곳
    routes: [
      GoRoute(
        name: Routes.table,
        path: '/table',
        builder: (context, state) => ListPage(),
      ),
      //path를 postId로 해준다.
      GoRoute(
        name: Routes.detail,
        path: '/:postId',
        builder: (context, state) => DetailPage(postId: int.parse(state.params["postId"]!)),
      ),
    ],
  );
}

실행하면 아래와 같이 나오고 클릭을 하게 되면

통신을 통해 받은 데이터들이 상세페이지에 출력된다.


공공데이터 활용해서 프론트 엔드 입장에서 구성하기

공공데이터 사용법

공공데이터포털에 로그인을 하여 부산에 대해 검색을 해 오픈 API를 선택해준다.

부산광역시_부산명소정보 서비스 데이터를 클릭한다.

들어가서 활용신청을 눌려주면 동의하라는 페이지가 나오는데 사용할 목적을 입력해주고 '동의'를 한 후 신청한다.
(목적은 웹개발 목적이라고 간단히 적으면 된다.)

마이페이지에 들어가 API신청을 눌려 자신의 신청했던 데이이터를 클릭하여 들어가면 상세보기 페이지가 나온다.

상세기능정보에서 미리보기 확인버튼을 클릭하면 아래와 같은 빨간박스 테두리의 내용이 나오는데 resultType에 json이라고 입력해주고 미리보기를 하면

json페이지가 나온다.
(아래를 데이터를 통신해 사용할 예정이다)

여기서는 아래의 데이이터들만 불러와서 사용한다.

MAIN_TITLE
CNTCT_TEL
MAIN_IMG_THUMB

화면(더미) 구성

아래와 같은 설정으로 new project를 만들어 준다.

pubspec.yaml

yaml파일에 들어가 사용할 패키지들을 아래의 그림과 같이 줄을 맞추어 적어주고 Pub get을 해준다.

  http:
  flutter_hooks:


main.dart

화면이 실행이 되도록 하기 위해 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_page.dart

리스트들이 나올 수 있도록 page dart 파일을 만든다.

import 'package:flutter/material.dart';
import 'package:flutter_attr_busan/attr_item.dart';

class AttrPage extends StatelessWidget{
  const AttrPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: [
            AttrItem(),
            AttrItem(),
            AttrItem(),
            AttrItem(),
          ],
        ),
      ),
    );
  }
}

attr_item.dart

page dart파일에서 리트스로 나올 item들을 불러올 수 있는 페이지를 만든다.
(아래는 임시로 더미데이터를 입력한 것)

import 'package:flutter/material.dart';

class AttrItem extends StatelessWidget {
  const AttrItem({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10),
      child: Column(
        children: [
          Image.network("https://www.visitbusan.net/uploadImgs/files/cntnts/20191222164810529_thumbL"),
          Text("흰여울문화마을"),
          Text("051-419-4067"),
        ],
      ),
    );
  }
}


실행하면 위의 결과와 같이 출력되는데 app으로 출력하는 거랑 web으로 출력할 때 이미지가 불러오는 게 달라 아래와 같이 설정해준다.

실행하는 초록색 삼각형 옆에 main.dart의 아랫방향 삼각형을 눌려서 해당 그림과 같이 클릭한다.

아래와 같은 창이 뜨는데 빨간 박스 안에 --web-renderer html 넣어준다.

그러면 그림이 제대로 불러와지는 것을 볼 수 있다.


모델(DTO 구성)

attr_dto.dart

json에서 가져오는 데이터들을 list타입으로 구성하기 위해 만들어준다.

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){
  //list타입이기 때문에 factory못씀
   return jsonList.map((e) => AttrDTO.fromJson(e)).toList();
   //e가 요소들, 그것들을 fromjson에 넣어준다는 것
  }
}

적을 때 맘대로 하는 것이 아니라 빨간 박스와 같이 이름을 똑같이 해준다.


리파지토리 구성

attr_repository.dart

통신을 하고 url을 파싱하기 위해 repository 파일을 만들어 준다.
(싱글톤을 사용하지 않고 비동기식으로 함)

import 'dart:convert';

import 'package:flutter_attr_busan/attr_dto.dart';
import 'package:http/http.dart' as http;

//싱글톤 안만들고(차이는 static)
class AttrRepository {
  static Future<List<AttrDTO>?> getDTO() async {
    final String url = "https://apis.data.go.kr/6260000/AttractionService/getAttractionKr?serviceKey=L4O6Jd5locofQV0Sa674EwMQ4GyHi380DNlzkWVMQLw8O2LvzNMvBKe1RxTj4jssgmQKPrDvinJFtSOIs9KmbA%3D%3D&pageNo=1&numOfRows=10&resultType=json";
    http.Response response = await http.get(Uri.parse(url));//url을 파싱함.
    if(response.statusCode == 200){
      dynamic json = jsonDecode(response.body);
      return AttrDTO.fromJsonList(json["getAttractionKr"]["item"]);
    }else{
      return null;
    }
  }
}

상태 구성

attr_page.dart(변경)

statelessWidget에서 hookWidget으로 변경해 준다.
(주석으로 설명함)

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

//상태구성할때 hook으로 변경
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){
        // if(value != null){//null처리를 안하면 오류남
        //   listState.value = value;
        // }
        // 또 다른 방법
        listState.value = value ?? [];
      });
    },[]);
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: listState.value.map((e) => AttrItem(attrDTO: e)).toList(),
        ),
      ),
    );
  }
}

화면 구성

attr_item.dart(변경)

AttrItem을 final로 선언해주고 children에 dto를 불러와 적어준다.

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

아래와 같이 불러온 데이터들이 나온다.


공공데이터에서 영화 데이터를 불러와서 만들기(실습)

새로운 프로젝트를 만들어준다.

yaml파일에서 아래와 같이 설정해준 뒤 pub get을 해준다.


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

http_movie_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:http_movie/http_movie_dto.dart';
import 'package:http_movie/http_movie_item.dart';
import 'package:http_movie/http_movie_repository.dart';

class HttpMoviePage extends HookWidget {
  const HttpMoviePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final listState = useState<List<HttpMovieDTO>>([]);
    useEffect((){
      HttpMovieRepository.getDTO().then((value){
        listState.value = value ?? [];
      });
    },[]);
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: listState.value.map((e) => HttpMovieItem(httpMovieDTO: e)).toList(),
        ),
      ),
    );
  }
}

http_movie_item.dart

import 'package:flutter/material.dart';
import 'package:http_movie/http_movie_dto.dart';

class HttpMovieItem extends StatelessWidget {
  final HttpMovieDTO httpMovieDTO;
  const HttpMovieItem({Key? key, required this.httpMovieDTO}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10),
      child: Column(
        children: [
          Text(httpMovieDTO.rank),
          Text(httpMovieDTO.auiCnt),
          Text(httpMovieDTO.movieNm),
          Text(httpMovieDTO.openDt),
        ],
      ),
    );
  }
}

http_movie_dto.dart

class HttpMovieDTO{
  // rank (랭킹)
  // auiCnt (관객수)
  // movieNm (영화이름)
  // openDt (개봉일)

  String rank;
  String auiCnt;
  String movieNm;
  String openDt;

  HttpMovieDTO({
    required this.rank,
    required this.auiCnt,
    required this.movieNm,
    required this.openDt,
});

  factory HttpMovieDTO.fromJson(dynamic json) => HttpMovieDTO(
    rank: json["rank"],
    auiCnt: json["audiCnt"],
    movieNm: json["movieNm"],
    openDt: json["openDt"],
  );

  static List<HttpMovieDTO> fromJsonList(List jsonList){
    return jsonList.map((e) => HttpMovieDTO.fromJson(e)).toList();
  }
}

http_movie_repository.dart

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:http_movie/http_movie_dto.dart';

class HttpMovieRepository{
  static Future<List<HttpMovieDTO>?> getDTO() async{
    final String url = "http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt=20120101";
    http.Response response = await http.get(Uri.parse(url));
    if(response.statusCode == 200){
      dynamic json = jsonDecode(response.body);
      return HttpMovieDTO.fromJsonList(json["boxOfficeResult"]["dailyBoxOfficeList"]);
    }else{
      return null;
    }
  }
}


성공!!!!!!


git

오늘 수업 내용
부산공공데이터 활용
영화데이터로 실습

0개의 댓글