Jin's Dev Story

[Flutter] http 패키지로 GET 요청 본문

Web & Android/Flutter

[Flutter] http 패키지로 GET 요청

woojin._. 2023. 9. 22. 22:33

http 패키지 설치

  1. pubspec.yaml 에 http: ^0.13.4 추가 후 pub get 누르기
dependencies:
  http: ^0.13.4
  1. main.dart 파일 들어가서 코드 추가
import 'package:http/http.dart' as http;
import 'dart:convert';
  • dart:convert 이건 JSON -> 일반자료형 변환을 도와주는 함수모음집
  1. android/app/src/main/AndroidManifest.xml
  • <application ~ 전에 추가
<uses-permission android:name="android.permission.INTERNET" />

 

서버에게 GET 요청 날리기

  • await http.get( Uri.parse('요청할url') ) 사용하여 GET 요청 날리기

→ 변수에 저장해서 사용

getData() async {
  var result = await http.get( Uri.parse('https://codingapple1.github.io/app/data.json') );
  print( jsonDecode(result.body) )
}

⇒ 가져온 데이터는 JSON 형식

  • 서버랑 주고 받는 데이터는 문자만 가능
    • JSON을 그대로 사용해도 되나 조작하기 십도록 list, map 자료로 변환 → json.Decode()
    • reuslt = [{”name”:”jhon”, “age”:20}, {”name”:”hong”, “age”:15}]에서 20을 가져오고 싶다면 result[0][”age”] 하면 20이 출력됨
  • 에러 체크하고 싶으면 result.statusCode를 출력하여 성공 여부 확인
getData() async {
  var result = await http.get(
      Uri.parse('https://codingapple1.github.io/app/data.json')
  );
  if (result.statusCode == 200) {
    print( jsonDecode(result.body) );
  } else {
    throw Exception('실패함');
  }
}

 

페이지 첫 로드 시 특정 코드를 실행하고 싶은 경우

  • initState() 사용
@override
void initState() {
  super.initState();
  getData();
}

 

가져온 데이터 화면에 보여주기

  • 자주 바뀌는 데이터인 경우 state에 저장해서 사용
class _MyAppState extends State<MyApp> {
  var tab = 0;
  var data = [];

  getData() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/data.json'));
    var result2 = jsonDecode(result.body);
    setState((){
      data = result2;
    })
  }

  (생략)
  1. MyApp 안에있던 Home 에 전송하고
Home(data : data)
  1. Home 은 등록하고
class Home extends StatelessWidget{
  const Home ({Key? key, this.data}) 
  final data;
  1. Home 안에서 사용
Text(data[0]['content'])

 문제점 : 데이터 도착 전에 먼저 쓰면 에러 발생

  • if문 사용하여 null check
  • .inNotEmpty 사용하면 됨
class Home extends StatelessWidget{
  const Home ({Key? key, this.data}) 
  final data;
  Widget build(BuildContext context){

    if (data.isNotEmpty){
      return ListView.builder(생략);
    } else {
      return Text('로딩중');
    }

  }
}

→ CircularProgressIndicator() 위젯 사용하면 로딩 중인 표시로 표현됨

→ 아니면 Dio 패키지 설치하여 사용해도 됨

→ 아니면 FutureBuilder 사용

  • 입력한 Future 변수가 실제 데이터로 변할 때 내부 코드 1회 실행해주는 위젯
  1. future: 안에는 Future를 담은 state 이름을 적으면 됨
    • http.get() 이런거 직접 적어도 되긴 하지만 state에 저장했다가 쓰는게 좋음
  2. builder: (){return 어쩌구} 안의 코드는 입력한 state 데이터가 도착할 때 실행
  3. 그리고 snapshot 이라는 파라미터가 변화된 state 데이터를 의미
  4. ⇒ 도착 시 위젯을 보여줘야할 경우 FutureBuilder가 유용하지만 데이터가 자주 추가되는 경우엔 불편
FutureBuilder (
  future: http.get(어쩌구),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('post에 데이터 있으면 보여줄 위젯')
    } 
    return Text('post에 데이터 없으면 보여줄 위젯')
  },
)

전체 코드

import 'package:flutter/material.dart';
import './style.dart' as style;
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(
      MaterialApp(
      theme: style.theme,
      home: MyApp()
  ));
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var tab = 0;  // 1. 현재 상태 저장
  var list = [1, 2, 3];
  var map = {'name':'john', 'age':20};
  var data = [];

  getData() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/data.json'));
    var result2 = jsonDecode(result.body);
    setState(() {
        data = result2;
    });

    print(result2[0]['likes']);
  }

  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text('Instagram'),
          actions: [
            IconButton(
              icon: Icon(Icons.add_box_outlined),
              onPressed: () {},
              iconSize: 30,
            )
          ]),
      body: [Home(data : data), Text('샵페이지')][tab],  // 2. state에 따라 tab 보이는 상태 변경
      bottomNavigationBar: BottomNavigationBar(
        showSelectedLabels: false,
        showUnselectedLabels: false,
        onTap: (i){
          setState(() {
            tab = i;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home_outlined), label: '홈'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_bag_outlined), label: '샵')
        ],
      ),
    );
  }
}

class Home extends StatelessWidget {
  const Home({Key? key, this.data}) : super(key: key);
  final data;

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
        itemCount: 3,
        itemBuilder: (c, i) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Image.network(data[i]['image']),
              Container(
                constraints: BoxConstraints(maxWidth: 600),
                padding: EdgeInsets.all(20),
                width: double.infinity,
                child: Column(
                  children: [
                    Text(data[i]['likes'].toString()),
                    Text(data[i]['user']),
                    Text(data[i]['content'])
                  ],
                ),
              )
            ],
          );
        }
    );
  }
}