[Flutter&약간의 Django] Future... 내 미래는 어둡지(API post방식)

리미·2020년 4월 24일
1

Flutter

목록 보기
3/6
post-thumbnail

아니 이게 무슨 개소리야?

Future에는 여러 의미가 있지...
앞이 시꺼먼 나의 미래와 내 친구 이미래와 쌉 레게노 가수 윤미래가 있지...(쌉소리)
Flutter에는 Future class 가 있다. 저번 get 방식에서 http 연결할때 쓰였고 DB에 연결할 때도 쓰인다. 하지만 이번에는 post방식을 할거라서 저번이랑 비슷한데 bloc은 안쓰고 Future만 써서 넘겨보겠다.

1. 일단 뭘 넘길건데?

우리의 어플쨩은 초기등록할때 사용자에겐 이름과 핸드폰번호, 차량번호를 입력받고 뒤에선 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;
      },
    );
}

2. submit버튼을 누를때 API를 타보자

저장버튼은 알아서 만들면되고(절대 귀찮은거 아님) 저장버튼을 눌렀을때 이벤트는 이렇게 적어준다.
_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()));
        // 다 했으면 다음 페이지로!
    }
},

3. 응답 json 받기!

이건 전 단계에서 하는 법을 알려줬으니 그대로 따라한다.
물론 여기서 class 이름은 InitPost로 사용하겠다.
참고 : https://velog.io/@rimi/Flutter-특정-값마다-초기화면-다르게-만들어줘엉
("3. 응답 json을 dart로 바꾸자" 참고)

4. 미래를 만들어볼까?!

자 이제 우리가 미래를 만들 차례다...

// 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이 아닐경우 예외처리
        }
}

!!!여기서 중요한 문제가 터졌다(뇌피셜주의)🤯

문제 1) API 응답 상태코드 값

어떤 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 반환한다는 것이다.
(따로 지정하지않았으니 당연한듯?)

문제 2) http부터 접속하지 못한다

이것도 마찬가지로 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

profile
Python이 하고싶은데 자꾸 Flutter 시켜서 빡쳐서 만든 블로그

0개의 댓글