flutter api fetch json loop null safety

hur-kyuh-leez·2022년 4월 18일
0

누구를 위한 글?

json parsing을 다른 언어에서 잘 하지만 유독 flutter에서만 잘 안되시는 분들을 위해
flutter fetch network cookbook을 봤는데 잘 안되는 경우

full code:

import 'dart:async';
import 'dart:convert';

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



Future<List> fetchAlbum() async {
  final list = <Map>[];
  final response = await http
      .get(Uri.parse('<API_URL>'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.

    var responseBody = jsonDecode(response.body);
    responseBody.forEach( (element) {
      list.add(element);
    });




    return list;

  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}


class Method {
  final int id;
  final int name;
  final String description;
  final DateTime created_at;
  final DateTime updated_at;
  final DateTime deleted_at;

  const Method(
      {
        required this.id,
        required this.name,
        required this.description,
        required this.created_at,
        required this.updated_at,
        required this.deleted_at}
      );

  factory Method.fromJson(Map<String, dynamic> json) {
    return Method(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      created_at: json['created_at'],
      updated_at: json['updated_at'],
      deleted_at: json['deleted_at'],
    );
  }
}



void main() => runApp(const MyApp());

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

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // late Future<Method> futureAlbum;
  late Future<List> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<List>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                // print(snapshot.data?[0]['id']);
                final returnThis = snapshot.data?[0]['name'].toString() ?? 'No data';
                return Text(returnThis);


              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

힌트:

.runtimeType 을 통해 어떤 data type인지 확인 하는 것과
???를 통해 null type이 허용 되는 것과 허용 되지 않는 것을 잘 구분 하면 됩니다.

설명:

가령 저희에게 아래와 같은 json data가 있습니다.

[
    {
        "id": 1,
        "name": "삼성 카드",
        "description": "카드 결제",
        "created_at": "2022-03-27T13:33:13.000000Z",
        "updated_at": "2022-03-27T13:33:40.000000Z",
        "deleted_at": null
    },
    {
        "id": 2,
        "name": "국민 카드",
        "description": "카드 결제",
        "created_at": "2022-03-27T13:33:50.000000Z",
        "updated_at": "2022-03-27T13:33:50.000000Z",
        "deleted_at": null
    },
    {
        "id": 3,
        "name": "현대 카드",
        "description": "카드 결제",
        "created_at": "2022-03-27T13:34:03.000000Z",
        "updated_at": "2022-03-27T13:34:03.000000Z",
        "deleted_at": null
    },
]

flutter 입장에서 본다면,
[] 가 있기 때문에 list 형태 인 것이고
그 안에 <key>:<value>있는 것은 딕셔너리 형태이니 map으로 인식 할 수 있습니다.
그래서 final list = <Map>[];를 준비해 줍니다.
그리고 룹을 돌려 준비한 list에 넣어 줍니다.

var responseBody = jsonDecode(response.body);
    responseBody.forEach( (element) {
      list.add(element);
    });

이렇게 리스트에 넣은 이제 앱 화면상에서 보여 주고 싶습니다.

 child: FutureBuilder<List>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                // print(snapshot.data?[0]['id']);
                final returnThis = snapshot.data?[0]['name'].toString() ?? 'No data';
                return Text(returnThis);


              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),

일반적으로
print(snapshot.data?[0]['id']);가 잘 console창에 print 되지만,

Text() Widget은 String만 받고 null일 수 있는 String?은 받을 수 가 없습니다.
그래서 한번
final returnThis = snapshot.data?[0]['name'].toString() ?? 'No data';
이런 문구를 통해 null이 아니면 snapshot.data?[0]['name'].toString()으로 null 이면 'No data'으로 처리 합니다.

다하시면 아래와 같은 뷰가 나와야 정상 입니다.


ps.
flutter가 어렵게 느껴지는 것은 아무래도 두가지 때문 입니다.

첫번째는 null safety의 의한 !, ?, ??, ??=, ...etc 등 null 관련한 operator가 많아서 입니다. dart팀이 이걸 예상 했는 지 공식 사이트에서 cheatsheet을 제공 하고 있습니다.

두번째는 statically typed 이라고 하지만 typed language에 매우 가까워서 입니다.
특히 typed language 때문에 어떻게든 type을 유지 시키려 하여 여러 Generics가 많이 나옵니다.
generics는 한번 이해하면 flutter가 훨씬 편하게 느껴 지실 태니 한번 꼭 시간을 내서 이해하시는 걸 추천 드립니다.

profile
벨로그에 생각을 임시로 저장합니다. 틀린건 틀렸다고 해주세요 :) 그래야 논리 학습이 강화됩니다.

0개의 댓글