728x90

서버 샘플 소스는 Android 앱 샘플 개발시 사용했던 코드를 가지고 Android Retrofit 라이브러리를 사용할 때와 비교하면서 시도를 해보는 중이다.

factory 생성자를 수동으로 생성하고 내 마음대로 코드를 이렇게 하면 되겠다 싶어 처리했더니 죽어도 해결이 안되었다.

Flutter 에서 JSON 데이터 가져오는 것이 왜 이리 힘들어 ㅠㅠㅠ 하면서 맨붕에 빠졌다가 차분하게 유투브 동영상에 나온 예제를 그대로 따라하면서 살펴보고 문제가 뭔지 비교 분석을 했다.

 

직집 구현한 factory 생성자

class ContactResult {
  final String status;
  final String message;
  final List<Contact_Item> addrinfo;
 
  const ContactResult({
    required this.status,
    required this.message,
    required this.addrinfo,
  });
 
  factory ContactResult.fromJson(Map<String, dynamic> map) {
    return ContactResult(
      status: map['status'] as String,
      message: map['message'] as String,
      addrinfo: List<Contact_Item>.from(map['addrinfo']),
    );
  }
}

final List<Contact_Item>? addrinfo; 라고 해야 하는데 강제로 null 값을 제거하고 동작 결과를 확인하기 위해 시도했으나 해결되지 않았다.

에러메시지를 구글링 해보니 addrinfo: List<Contact_Item>.from(map['addrinfo']) 로 하면 해결될 것처럼 써있는 답변이 있어서 시도해봤다. 하지만 여전히 에러가 발생한다.

 

자동 생성한 factory 생성자

Json Serializable 을 이용하여 자동 생성한 factory 생성자 결과

ContactResult _$ContactResultFromJson(Map<String, dynamic> json) =>
    ContactResult(
      status: json['status'] as String,
      message: json['message'] as String,
      addrinfo: (json['addrinfo'] as List<dynamic>?)
          ?.map((e) => Contact_Item.fromJson(e as Map<String, dynamic>))
          .toList(),
    );
 

 

위 코드와 아래 자동 생성된 코드를 비교해보면 addrinfo 변수를 처리한 결과가 확연하게 다른 것을 확인할 수 있다.

 

정상적으로 처리되고 나서 Contact_Item 변수에 오류가 있다는 것도 알게 되었다.

정교하게 변수 처리를 해주지 않으면 에러를 뱉어내는 걸 확인했고, 화면 출력된 결과를 보면서 제대로 수정했다.

 

 

import 'package:json_annotation/json_annotation.dart';
 
part 'contact_item.g.dart';
 
@JsonSerializable()
class Contact_Item {
  final int idx; // String 으로 변수를 선언했는데, 출력 결과에서 int 라고 변경 요청
  final String userNM;
  final String mobileNO;
  final String? telNO;
  final String? photo; // 출력 결과에 null 값이 존재하는 걸 확인하고 변경
  final bool checkBoxState;
 
  const Contact_Item({
    required this.idx,
    required this.userNM,
    required this.mobileNO,
    this.telNO,
    this.photo,
    required this.checkBoxState,
  });
 
  factory Contact_Item.fromJson(Map<String, dynamic> json) =>
      _$Contact_ItemFromJson(json);
 
  Map<String, dynamic> toJson() => _$Contact_ItemToJson(this);
}

위와 같이 모델 class 를 선언하고 터미널 창에서 dart run build_runner build 를 하면 자동으로 contact_item.g.dart 파일이 생성된다. 코드를 수정시에는 항상 dart run build_runner build 를 해서 업데이트 해줘야 한다.

 

import 'package:json_annotation/json_annotation.dart';
import 'package:login_ex/contact/model/contact_item.dart';
 
part 'contact_result.g.dart';
 
@JsonSerializable()
class ContactResult {
  final String status;
  final String message;
  final List<Contact_Item>? addrinfo;
 
  const ContactResult({
    required this.status,
    required this.message,
    this.addrinfo,
  });
 
  factory ContactResult.fromJson(Map<String, dynamic> json) 
=> _$ContactResultFromJson(json);
 
  Map<String, dynamic> toJson() => _$ContactResultToJson(this);
}

 

 

 

json_serializable 는 https://pub.dev/packages/json_serializable/install 를 보면서 라이브러리를 설치해주면 된다.

https://pub.dev/packages/json_annotation/install 를 참조하여 설치한다.

https://pub.dev/packages/build_runner/install 를 참조하여 설치한다.

 

위 3개의 라이브러를 터미털 창에서 아래 코드를 한줄씩 실행하면 최신버전으로 설치된다.

기존에 설치된 라이브러리 버전이 낮다면 해당 줄을 삭제하고 다시 실행하면 최신버전으로 설치된다.

flutter pub add json_annotation
 
flutter pub add dev:json_serializable
 
flutter pub add dev:build_runner
 

 

 

그러면 실제 샘플로 구현한 코드를 살펴보자.

import 'package:dio/dio.dart';
import 'package:login_ex/common/repository/retrofit_url.dart';
import 'package:login_ex/contact/model/contact_request.dart';
import 'package:login_ex/contact/model/contact_result.dart';
import 'package:login_ex/common/repository/logging.dart';
 
abstract class ContactRepo {
  Future<ContactResult> getContactList(ContactRequest req);
}
 
class ContactService extends ContactRepo {
 
  Future<ContactResult> getContactList(ContactRequest req) async {
    BaseOptions options = BaseOptions(
      baseUrl: RetrofitURL.baseUrl,
    );
    Dio dio = Dio(options);
    dio.interceptors.add(LoggingInterceptor());
 
    FormData formData = FormData.fromMap({
      "keyword": req.keyword,
      "search": req.search,
    });
 
    final response = await dio.post(RetrofitURL.contactData, data: formData);
    print(response);
    // print(response.data.runtimeType);
    //print(response.headers);
    if (response.statusCode == 200) {
      // final Map<String, dynamic> body = jsonDecode(response.data);
      ContactResult result = ContactResult.fromJson(response.data);
      return result;
    } else {
      return ContactResult(status: "fail", message: "fail", addrinfo: []);
    }
  }
}
 

 

로그 화면에서 결과를 확인하기 위한 코드 예시이다.

class MainScreen extends StatefulWidget {
  const MainScreen({Key? key}) : super(key: key);
 
  @override
  State<MainScreen> createState() => _MainScreenState();
}
 
class _MainScreenState extends State<MainScreen> {
  final ContactRepo repo = ContactService();
 
  @override
  void initState() {
    super.initState();
    getContactData();
  }
 
  Future<void> getContactData() async {
 
    ContactRequest dataPost = ContactRequest(
      keyword: Crypto.AES_encrypt(Crypto.URLkey()),
      search: '',
    );
 
    ContactResult response = await repo.getContactList(dataPost);
 
    for(var item in response.addrinfo as List<Contact_Item>){
      print('${item.idx} | ${item.userNM} | ${item.mobileNO} | ${item.photo}');
 
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return DefaultLayout(
      child: Center(
        child: Text('Main Page'),
      ),
    );
  }
}
 

 

keyword는 서버에서 key값을 비교하여 일치하는 경우에만 JSON 결과 데이터를 반환하도록 처리하기 위해서 추가한 것이다.

블로그 이미지

Link2Me

,