Future에는 여러 의미가 있지...
앞이 시꺼먼 나의 미래와 내 친구 이미래와 쌉 레게노 가수 윤미래가 있지...(쌉소리)
Flutter에는 Future class 가 있다. 저번 get 방식에서 http 연결할때 쓰였고 DB에 연결할 때도 쓰인다. 하지만 이번에는 post방식을 할거라서 저번이랑 비슷한데 bloc은 안쓰고 Future만 써서 넘겨보겠다.
우리의 어플쨩은 초기등록할때 사용자에겐 이름과 핸드폰번호, 차량번호를 입력받고 뒤에선 Device token을 같이 api에 넘겨준다. 그 뒤는 백엔드의 몫이지..
일단 입력받은 데이터들을 저장해놓아주는 class를 상단에 만들어준다.
class _initData {
String userName;
String userPhoneNumber;
String userCarNumber;
}
그리고 Form에 TextFormField 등 필요한 정보를 입력받을 Field들을 추가해준다.
Form key를 최상단에 넣는거 잊지말공
_initData _data = new _initData();
// 저장할 때 여기에 사용자가 입력한 데이터를 넣을거임
final _formKey = GlobalKey<FormState>();
// formkey를 생성하고 폼에 넣어준다
그치만 Form안에 들어간 순간 값이 들어갔는지 체크하기 위해선 Field 말고 FormField 또는 TextFormField를 추가해야한다. 나머진 customizing 해야댐..ㅇㅇ....
일단 폼내부는 대충 이렇게 꾸며준다
Form(
key: _formKey,
// 상단에서 생성해준 formkey를 넣어준다.
child: Column(
// 키보드때문에 bottom overflow가 발생된다면
// scrollView나 해결할 수 있는 것들을 집어넣는다
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(20),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return '공백은 허용되지 않습니다.';
}
},
decoration: InputDecoration(
border: OutlineInputBorder(), labelText: '이름'),
onSaved: (String value) {
this._data.userName = value;
},
),
),
Padding(
padding: const EdgeInsets.all(20),
child: phoneNumberField(),
),
...생략...
InitSubmitBtn(context), // 저장버튼임 ㅇㅇ
]
),
나는 핸드폰 번호 입력 필드를 만들기 위해 따로 Widget으로 만들어 customizing을 했다
그 전에 입력 필드 mask 처리해주는 패키지 다운로드 하는거 잊지말긔...
https://pub.dev/packages/mask_text_input_formatter
Widget phoneNumberField() {
// MaskTextInputFormatter 이건 패키지 다운받아야댐..
var phoneFormatter = new MaskTextInputFormatter(
mask: '###-####-####', filter: {'#': RegExp(r'[0-9]')});
// 대충 쓰는 방법은 android였나 bootstrap이였나
// 하튼 그런 input mask처리와 비슷하다 모두 RGRG?
return TextFormField(
inputFormatters: [phoneFormatter],
keyboardType: TextInputType.phone,
// 이건 키보드 모양, 난 전화번호만 입력받는 필드라 phone 타입으로 지정해주었다
decoration:
InputDecoration(border: OutlineInputBorder(), labelText: '핸드폰번호'),
validator: (value) {
if (value.isEmpty) {
// 만약에 아무것도 입력하지않고 저장을 했을 때의 보여질 텍스트
return '공백은 허용되지 않습니다.';
}
},
onSaved: (String value) {
// 저장을 눌렀을때의 행위
this._data.userPhoneNumber = value;
},
);
}
저장버튼은 알아서 만들면되고(절대 귀찮은거 아님) 저장버튼을 눌렀을때 이벤트는 이렇게 적어준다.
_data는 위에서 적어준대로 저장을 누르면 클래스안에 저장이 되어있을테니 값을 이때 써먹어준다.
onPressed: () async {
// 당근 안에 await를 쓰니까 async의 함수로 만들어야 되게찌!
if (_formKey.currentState.validate()) {
// 저장버튼을 누르고 값이 모두 입력되었을 때
_formKey.currentState.save();
InitPost init = await getOrCreateInitAPIData(_data.userPhoneNumber, _data.userName, _data.userCarNumber);
// 무사히 다 실행될때까지 넘어가면안돼니까 await로 좀 기다려달라고 부탁한다
await InitDBHelper().insertData(InitModel(id: 1, device: init.deviceIdId, car: init.carId));
// 이것두... 부탁한다 ㅠㅠ 기다려줭... 내부 디비에 저장해야되니깡...안적으면 그냥 넘어감...
Navigator.push(context, MaterialPageRoute(builder: (context) => RegisterWorkLog()));
// 다 했으면 다음 페이지로!
}
},
이건 전 단계에서 하는 법을 알려줬으니 그대로 따라한다.
물론 여기서 class 이름은 InitPost로 사용하겠다.
참고 : https://velog.io/@rimi/Flutter-특정-값마다-초기화면-다르게-만들어줘엉
("3. 응답 json을 dart로 바꾸자" 참고)
자 이제 우리가 미래를 만들 차례다...
// json to dart로 저장한 InitPost 타입을 반환하는 Future함수를 만들것이다.
Future<InitPost> getOrCreateInitAPIData(deviceNumber, deviceName,
carNumber) async {
var deviceToken;
// *** 이건 FireBase를 연결해서 Device Token을 가져오는 것이다
await _firebaseMessaging.getToken().then((token) {
deviceToken = token;
});
// *** 이건 나중에 하는걸로~(넘나 복잡;;)
String url = '여기는 url을 쓰는 곳이지';
var response = await http.post(url, headers: {
'Accept': 'application/json'
}, body: {
'carNumber': carNumber,
'deviceNumber': deviceNumber,
'deviceToken': deviceToken,
'deviceName': deviceName, // device token이 null이여도 보내버림
});
if (response.statusCode == 200) {
// statusCode 200일때, 정상신호일때
var data = InitPost.fromJson(
jsonDecode(utf8.decode(response.bodyBytes))[0]);
// 현재 api 응답값은 리스트로 묶어서 전송하므로
// 0번째만 가져와서 저장하는걸로 하였다. 어차피 0번째 밖에 없다..
return data;
} else {
throw Exception('failed to update carNumber');
// 응답이 200이 아닐경우 예외처리
}
}
어떤 API는 정상을 200으로 받고 다른건 201로 받기 시작한거다 ㅡㅡ
장고로 넘어가서 한번 보자
# [views.py]
class CarViewSet(viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
class WorkStartAPIView(APIView):
def post(self, request):
... 생략 ...
serializer = WorkLogSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
... 생략 ...
response.update(serializer.data)
return Response(response, status=status.HTTP_200_OK)
이 두가지에서 문제가 발생하는거다.
하나는 viewset으로 Serializer를 사용하였고
다른 한개는 APIView로 정상상태값을 200으로 지정해서 넘긴다.
그런데 왜 ViewSet이 Serializer를 사용한게 뭐?
API View도 사용하고 있는걸?
그럼 serializers.py에서 한번 보자
# [serializers.py]
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = '__all__'
def create(self, validated_data):
carNumber = validated_data.get('carNumber')
car, created = Car.objects.get_or_create(carNumber=carNumber)
return car
class WorkLogSerializer(serializers.ModelSerializer):
class Meta:
model = Worklog
fields = '__all__'
Car는 serializers의 create 함수를 이용해서 처리하였고
WorkLogSerializer는 view단에서 처리하였다
ViewSet을 이용한 api가 serializers의 create함수를 타게되면 statue code를 HTTP_201_CREATED 반환한다는 것이다.
(따로 지정하지않았으니 당연한듯?)
이것도 마찬가지로 ViewSet과 APIView의 차이인것같은데
(이쯤되면 자동이냐 가라냐의 문제인듯)
ViewSet Post(Create)의 경우 위 4번처럼 http를 접속하면 안됀다는 것이다.
그럼 어떻게 접속해야되나?
Future<DevicePost> getOrCreateInitAPIData(deviceNumber, deviceName,
carNumber) async {
String deviceUrl = '여긴url이지롱';
// print를 찍어볼때 여기 밑에서부터(http에서) 접속을 못하고있었다;
// 그래서 아래와 같이 변경했더니 됨;;;
var deviceResponse = await http.post(deviceUrl,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'deviceNumber': deviceNumber.toString(),
'deviceName': deviceName.toString(),
})
);
// 여긴 코드값만 200에서 201만 변경해준거 빼곤 동일
if (deviceResponse.statusCode == 201) {
var deviceData = DevicePost.fromJson(json.decode(deviceResponse.body));
return deviceData
} else {
throw Exception('failed to create device');
}
}
다 따지고 보면 어차피 위에 소스랑 비슷한데 왜 위엔 돌아가고 밑엔 안돌아가는 지 아직도 의문이다.
하튼, get방식은 그래도 할만했는데 ㅡㅡ post에서 이것저것 문제가 터지니...
ViewSet을 사용하고자 하면 일단 그걸 사용할때 상태값과 전달할때 헤더값 등 정도는 미리 공부를하고 쓰는게 좋을것같다.
Rest Framework 할때 몇줄 안적고도 자동으로 다 되는게 신기해서 다 ViewSet으로 만들었는데 ㅡㅡ;;; (결국 이 문제는 과거의 내 잘못이였던것이다)
무조건 자동으로 된다고 좋은게 아닌가봉가...
출처 :
1. https://api.flutter.dev/flutter/dart-async/Future-class.html
2. https://flutter.dev/docs/cookbook/networking/fetch-data