이제 입력받은 숫자 값을 더해서 다음 화면으로 넘기는 작업을 해볼것이다.
목표는 입력 받은 숫자값을 더한 json을 생성해서 다음 화면인 송금 확인 화면으로 넘기는것!
우선 이를 위해, 먼저 숫자 값을 json에 더하고, 다음 화면에서 데이터들이 잘 도착했는지를 검사해보도록 하자.
이걸 구현하는 과정에서 여러 문제점이 있었어서 그걸 해결했던 과정들 + 뭐가 문제였는지를 정리하고자 한다.
가장 큰 문제는 바로 qr에서 받아온 userdata를 여러 화면에서 사용해야 한다는 것이다.
원래 처음에는, 데이터를 계속 인자로 받아서 넘기는 방식을 사용했는데, 아무리 생각해도 이게 너무 부자연스러운 것이다.
검색 결과, 이렇게 여러 화면에서 한 요소를 사용할 경우, json 데이터를 저장하는 부분을 "user data"라는 클래스로 따로 빼서 사용하는 것이 좋다고 한다. 이 과정에서 이 클래스는 싱글톤으로 구현해서 애플리케이션 전반에 걸쳐 공유될 수 있도록 한다.
싱글톤
플러터(Flutter)에서 싱글톤 클래스는 어플리케이션 전반에 걸쳐 하나의 인스턴스만을 유지하고 이에 대한 접근을 제공하는 클래스를 의미한다. 이는 여러 컴포넌트나 위젯에서 동일한 데이터나 상태를 공유하고자 할 때 사용된다.
싱글톤 클래스를 사용함으로써 전역 상태를 효율적으로 관리할 수 있지만, 남용하면 코드의 복잡도가 증가할 수 있으므로 적절한 상황에서 사용하는 것이 중요하다.
싱글톤으로 구성하는 방법은 다음과 같다.
1. 클래스 내부에 정적 인스턴스 변수 생성: 싱글톤 클래스 내부에 해당 클래스의 인스턴스를 저장할 정적 변수를 만든다.
static MySingleton _instance; //예를 들면, 이런식이다.
프라이빗 생성자(private constructor): 클래스의 생성자를 private으로 지정하여 외부에서 인스턴스를 직접 생성하는 것을 막는다.
MySingleton._(); //예를 들면, 이런식이다.
//dart에서는 어떠한 클래스의 생성자를 _와 같은 밑줄로 시작하게 되면, 해당 생성자는 클래스 외부에서 접근할 수 없게 된다.
인스턴스를 반환하는 정적 메서드(static method): 클래스 내부에서 유일한 인스턴스를 반환하는 정적 메서드를 정의한다. 이 메서드에서는 인스턴스가 없는 경우에만 인스턴스를 생성하고, 이미 생성된 경우에는 기존 인스턴스를 반환하게 된다.
static MySingleton getInstance() {
if (_instance == null) {
_instance = MySingleton._(); // create instance if it doesn't exist
}
return _instance;
} //예를 들면, 이런식이다.
싱글톤을 적용해서 만든 userdata는 다음과 같다.
class UserData {
late String id;
late String email;
late String firstName;
late String lastName;
late String avatar;
// 싱글톤 인스턴스
static final UserData _instance = UserData._internal();
// 프라이빗 생성자
UserData._internal();
// 싱글톤 인스턴스 반환
factory UserData() => _instance;
// 초기화 메서드
void initializeData(Map<String, dynamic> jsonData) {
id = _getStringValue(jsonData, 'id');
email = _getStringValue(jsonData, 'email');
firstName = _getStringValue(jsonData, 'first_name');
lastName = _getStringValue(jsonData, 'last_name');
avatar = _getStringValue(jsonData, 'avatar');
}
String _getStringValue(Map<String, dynamic> jsonData, String key) {
return jsonData[key].toString(); // string으로 타입 캐스팅 후 반환
}
}
이렇게 이 싱글톤 클래스를 통해 json data를 공유하도록 만들어 두었으므로, 기존에 qr 데이터를 화면에서 인자로 받아 설정하던 코드들을 수정해야 한다.
// qr_scanner 클래스
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
setState(() {
result = scanData;
if (result != null) {
controller.pauseCamera();
UserData().initializeData(jsonDecode(result!.code!)); // API 데이터를 UserData에 저장
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyNextScreen()),
).then((_) {
controller.resumeCamera();
});
}
});
});
}
qr 스캔시 userdata에 내용을 저장해서 사용할 수 있도록 수정하였다.
// 새로운 화면으로 이동
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Exchange()),
);
그리고 화면을 넘어갈때 인자로 넘겨주지 않아도 되기 때문에, 인자를 제거하고 바로 화면으로 넘어갈 수 있도록 한다.
또한, 이 다음 화면의 경우도 userdata를 선언해서 사용하기만 하면 된다.
전반적으로 class가 매우 깔끔해진 것을 볼 수 있다.
class Exchange extends StatelessWidget {
final UserData userData;
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Next Screen'),
),
body: Center(
child: Column(
children: [
Text('ID: ${userData.id}', style: TextStyle(fontSize: 20)),
Text('Email: ${userData.email}', style: TextStyle(fontSize: 20)),
Text('First Name: ${userData.firstName}', style: TextStyle(fontSize: 20)),
Text('Last Name: ${userData.lastName}', style: TextStyle(fontSize: 20)),
Text('Avatar: ${userData.avatar}', style: TextStyle(fontSize: 20)),
Text('Amount: $amount', style: TextStyle(fontSize: 20)),
],
),
),
);
}
}
또한, amount 값은 키보드로 입력을 받고, userdata의 json에 추가해야 한다. 이 부분의 경우 userdata에 새로운 값을 추가할 수 있는 메서드를 생성하였다.
import 'dart:convert';
class UserData {
late String id;
late String email;
late String firstName;
late String lastName;
late String avatar;
late int amount;
// 싱글톤 인스턴스 생성
static final UserData _instance = UserData._internal();
factory UserData() => _instance;
// 내부 생성자
UserData._internal() {
id = '';
email = '';
firstName = '';
lastName = '';
avatar = '';
amount = 0;
}
// API 데이터 초기화 메서드
void initializeData(Map<String, dynamic> data) {
id = _getStringValue(data, 'id');
email = _getStringValue(data, 'email');
firstName = _getStringValue(data, 'first_name');
lastName = _getStringValue(data, 'last_name');
avatar = _getStringValue(data, 'avatar');
}
// 새로운 JSON 데이터 추가 메서드
void addNewData(Map<String, dynamic> newData) {
amount = newData['amount'] ?? 0; // amount 값이 없으면 기본값 0으로 설정
}
String _getStringValue(Map<String, dynamic> data, String key) {
return data[key].toString();
}
}
onPressed: () {
// amount 값을 UserData에 추가
userData.amount = amount; // amount 값을 직접 업데이트
// 새로운 화면으로 이동
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Exchange()),
);
},
amount 값의 경우, 새로운 화면으로 이동하기 전에 userdata 안에 있는 값을 update하는 방식으로 값을 넣어주었다.