---
title: Flutter를 사용한 Parse 데이터 핸들링 가이드
slug: docs-kr/flutter/parse-sdk/data-objects/flutter-data-types
description: Flutter로 Parse 데이터 유형을 읽고 저장하는 방법을 배웁니다.
image: https://www.back4app.com/images/logo-social.png
createdAt: 2024-03-15T14:18:16.681Z
---

이 가이드는 Flutter에서 Parse 데이터 유형을 소개하며, `main.dart` 파일의 예제를 사용합니다. Parse 데이터 저장소는 ParseObject를 중심으로 하며, 이는 JSON 호환 데이터의 키-값 쌍을 포함하고 있어 스키마가 없습니다. 이는 사전 정의된 스키마 없이도 데이터를 저장할 수 있음을 의미합니다.

원하는 키-값 쌍을 설정할 수 있으며, 우리의 백엔드는 이를 저장합니다.

예를 들어, 게임의 최고 점수를 추적한다고 가정해 보겠습니다. 단일 Parse 객체는 다음을 포함할 수 있습니다:

:::BlockQuote
점수: 1337, 플레이어 이름: "Sean Plott", 치트 모드: false
:::

:::hint{type="info"}
코드 가독성과 유지 관리를 향상시키기 위해 `NameYourClassesLikeThis` 및 `nameYourKeysLikeThis`를 사용하는 것을 권장합니다.
:::

## 우리 앱 이해하기

Back4app을 더 잘 이해하기 위해, 주요 지원 데이터 유형과 함께 Flutter 애플리케이션에서 Parse 작업의 코드 예제를 살펴보겠습니다. 이 가이드는 Parse와 Flutter를 사용하는 데 초점을 맞추기 때문에 Flutter 앱 코드에 대한 설명은 포함되지 않습니다.

## 전제 조건

이 튜토리얼을 완료하려면 다음이 필요합니다:

:::hint{type="info"}
- [안드로이드 스튜디오 ](https://developer.android.com/studio) 또는 [VS 코드 설치](https://code.visualstudio.com/) (와 [플러그인](https://docs.flutter.dev/get-started/editor) 다트와 플러터)
- 백4앱에서 [생성된](https://www.back4app.com/docs/get-started/new-parse-app) 앱:
  - **참고:&#x20;**&#x20;[새 파스 앱 튜토리얼](https://www.back4app.com/docs/get-started/new-parse-app)을 따라 백4앱에서 파스 앱을 만드는 방법을 배우세요.
- 백4앱에 연결된 플러터 앱.
  - **참고:&#x20;**&#x20;[플러터 프로젝트에 파스 SDK 설치](https://www.back4app.com/docs/flutter/parse-sdk/parse-flutter-sdk)를 따라 백4앱에 연결된 플러터 프로젝트를 만드세요.
- 안드로이드 또는 iOS를 실행하는 장치(또는 가상 장치).
:::

## 1 - 파스 객체 작업하기

각 ParseObject는 서로 다른 데이터 유형을 구분하는 데 사용되는 클래스 이름(예: GamePoint)을 가지고 있습니다. 다음은 데이터 유형으로 새 Parse 객체를 생성하고 저장하는 방법입니다 `문자열`, `정수`, & `부울`:

```dart
final gamePoint = ParseObject('GamePoint')
  ..set('score', 1337)
  ..set('playerName', 'Sean Plott')
  ..set('cheatMode', false);
await gamePoint.save();
```

**새로운 Parse 객체를 쿼리하고 데이터 유형을 검색하려면:**

```dart
  final QueryBuilder<ParseObject> queryGamePoints = QueryBuilder<ParseObject>(ParseObject('GamePoint'));

  final ParseResponse response = await queryGamePoints.query();

  if (response.success && response.results != null) {
    for (var gamePoint in response.results!) {
      final score = gamePoint.get<int>('score');
      final playerName = gamePoint.get<String>('playerName');
      final cheatMode = gamePoint.get<bool>('cheatMode');
      
    }
```

## 2 - 카운터

ParseObject에서 정수 필드를 증가시키거나 감소시키려면 `set()` 메서드를 사용하세요.

하지만, 이것은 효과적이지 않으며 여러 클라이언트가 동일한 카운터를 업데이트하려고 할 경우 문제를 일으킬 수 있습니다. Parse는 카운터 유형 데이터를 저장하기 위해 자동으로 모든 숫자 필드를 증가 및 감소시키는 두 가지 방법을 제공합니다.

`setIncrement()`

`setDecrement()`

카운터 `int` 값을 증가시키는 업데이트는 다음과 같이 작성됩니다:

```dart
 final gamePoint = ParseObject('GamePoint')
  ..objectId = 'yourObjectId'
  ..setIncrement('intField', 1);
 await gamePoint.save();
```

감소시키려면:

```dart
 final gamePoint = ParseObject('GamePoint')
  ..objectId = 'yourObjectId'
  ..setDecrement('intField', 1);
 await gamePoint.save();
```

&#x20;`setIncrement()` 및 `setDecrement()`를 `save()` 호출과 함께 사용하면 여러 필드를 수정할 수 있는 더 큰 저장 작업의 일환으로 값을 업데이트할 수 있습니다. 이는 추가 네트워크 요청을 피하는 데 더 좋습니다.

## 3 - 목록

Parse는 `setAdd`, `setAddUnique`, `setRemove`, 및 해당 `모두` 버전을 포함하여 목록 데이터 작업을 위한 메서드를 제공합니다.

목록: `["a","b","c"]`

### 3.1 - setAdd

```dart
final gamePoint = ParseObject('GamePoint')
..objectId = 'yourObjectId'
..setAdd('listStringField', 'd');
await gamePoint.save();
```

결과: `["a","b","c","d"]`

### 3.2 - setAddAll

```dart
final gamePoint = ParseObject('GamePoint')
..objectId = 'yourObjectId'
..setAddAll('listStringField', ['e','f']);
await gamePoint.save();
```

결과: `["a","b","c","d","e","f"]`

`setAddAll`는 목록에 이미 포함된 중복 요소를 추가하지 않습니다.

### 3.3 - setAddUnique

```dart
final gamePoint = ParseObject('GamePoint')
..objectId = 'yourObjectId'
..setAddUnique('listStringField', ['a', 'e', 'g']);
await gamePoint.save();
```

결과: `["a","b","c","d","e","f","g"]`

### 3.4 - setRemove

```dart
final gamePoint = ParseObject('GamePoint')
 ..objectId = 'yourObjectId'
 ..setRemove('listStringField', 'd');
 await gamePoint.save();
```

결과: `["a","b","c","e","f","g"]`

## 4 - ParseObject에서 필드 제거

객체에서 단일 필드를 삭제하려면 unset 작업을 사용하십시오:

```dart
final gamePoint = ParseObject('GamePoint')
..objectId = 'yourObjectId'
..unset("listStringField");
```

## 5 - 파일

ParseFile을 사용하면 클라우드에서 애플리케이션 파일을 저장하고 검색할 수 있습니다.
아래는 로컬 이미지 파일을 Back4App에 업로드하는 기본 예제입니다:

```dart
// Create ParseFile object
 final parseFile = ParseFile(File(image.path));

 final response = await parseFile.save();

 if (response.success) {
   print('File uploaded successfully: ${parseFile.url}');

   // Save this file in an object 
   final gamePoint = ParseObject('GamePoint')
     ..set('title', 'Post with an Image')
     ..set('imageFile', parseFile); // Saving the file as a field

   final postResponse = await gamePoint.save();
   }
```

이 코드 조각은 이미지 파일을 검색하는 방법을 보여줍니다:

```dart
final query = QueryBuilder<ParseObject>(ParseObject('GamePoint'));

  final response = await query.query();

  if (response.success && response.results != null) {
    for (var gamePoint in response.results!) {
      ParseFileBase? varFile = gamePoint.get<ParseFileBase>('file');
      if (varFile != null) {
        final fileUrl = varFile.url;
        print('Game Title: Image URL: $fileUrl');
      } else {
        print('No file found for this object.');
      }
    }
  }
```

Parse Server 5.2.3부터는 파일 업로드 시 오류를 유발할 수 있는 변경 사항이 있습니다. [이 가이드](https://www.back4app.com/docs/platform/parse-server-version#O-CiL)를 따라 업로드 문제를 해결하십시오.

나중에 가이드에서 [Parse로 파일을 저장하고 표시하는 방법](https://www.back4app.com/docs/flutter/parse-sdk/flutter-save-file)에 대해 더 논의하고 템플릿을 보여줄 것입니다.

## 6 - GeoPoint

Parse는 객체와 함께 실제 세계의 위도 및 경도 좌표를 GeoPoint 데이터 유형으로 연결할 수 있도록 합니다. ParseObject에 ParseGeoPoint를 추가하면 객체가 기준점에 얼마나 가까운지를 고려하는 쿼리를 가능하게 합니다.

예:

```dart
 // Request permission here and get the current location: the `position` object.
 
 // Create GeoPoint object
  final geoPoint = ParseGeoPoint(latitude: position.latitude, longitude: position.longitude);

 // Create an object with the GeoPoint
  final gamePoint = ParseObject('GamePoint')
    ..set('name', 'Current Location')
    ..set('location', geoPoint); // Save the GeoPoint in a field

  final response = await gamePoint.save();
```

Geopoint 위치를 저장하는 독립 실행형 예제를 이 [main.dart](https://github.com/templates-back4app/Flutter_DataTypes/blob/18c4c372eb49c1cd950eeaac355564134a5e70c4/lib/src/main.dart) 파일에서 찾을 수 있습니다.

## 전체 앱 예제

이 예제는 다음을 보여줍니다:

- 생성, 삭제 및 업데이트 `GamePoint` Parse 객체.
- 문자열, 더블, 불리언, 파일, 지리적 포인트, 목록 및 날짜를 포함한 다양한 데이터 유형을 처리합니다.

iOS에서는 시뮬레이터에 필요한 접근 권한을 부여해야 합니다.

:::hint{type="info"}
- iOS는 보호된 리소스에 대한 개발자 가이드를 제공합니다. 이 가이드의 예제 애플리케이션은 [위치](https://developer.apple.com/documentation/bundleresources/information-property-list/nslocationwheninuseusagedescription) 및  [사진](https://developer.apple.com/documentation/bundleresources/information-property-list/nsphotolibraryaddusagedescription) 권한 키가 필요합니다. 해당 키를 `Info.plist` 파일에 추가하세요. 이 파일은 `/ios/Runner/Info.plist`:
:::

웹에서 Flutter 앱을 실행하는 경우 위 단계를 건너뛸 수 있습니다:

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/foundation.dart';
import 'dart:io';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  const keyParseApplicationId = 'YOUR_APPLICATION_ID';
  const keyParseClientKey = 'YOUR_CLIENT_KEY';
  const keyParseServerUrl = 'https://parseapi.back4app.com';
  await Parse().initialize(
    keyParseApplicationId,
    keyParseServerUrl,
    clientKey: keyParseClientKey,
    autoSendSessionId: true,
    debug: true,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'DataTypes',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

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

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _stringController = TextEditingController();
  final TextEditingController _doubleController = TextEditingController();
  final TextEditingController _listStringController = TextEditingController();
  final TextEditingController _listIntController = TextEditingController();
  final TextEditingController _pointerController = TextEditingController();
  final TextEditingController _objectIdController = TextEditingController();
  final TextEditingController _uniqueValueController = TextEditingController();
  final TextEditingController _removeValueController = TextEditingController();

  bool _boolValue = false;
  DateTime _selectedDate = DateTime.now();
  final String _responseMessage = '';
  XFile? pickedFile;
  bool isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DataTypes'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              _buildTextField('String Field', _stringController),
              const SizedBox(height: 20),
              _buildTextField('Double Field', _doubleController,
                  isNumeric: true),
              const SizedBox(height: 20),
              _buildSwitch('Bool Field', _boolValue, (value) {
                setState(() {
                  _boolValue = value;
                });
              }),
              const SizedBox(height: 20),
              TextButton(
                onPressed: () => _selectDate(context),
                child: Text(
                    'Select Date: ${_selectedDate.toLocal()}'.split(' ')[0]),
              ),
              const SizedBox(height: 20),
              _buildTextField(
                  'List String Field (comma-separated)', _listStringController),
              const SizedBox(height: 20),
              _buildTextField(
                  'List Int Field (comma-separated)', _listIntController),
              const SizedBox(height: 20),
              _buildTextField('Pointer Field (Object ID)', _pointerController),
              const SizedBox(height: 20),
              _buildImagePicker(),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: _saveData,
                child: const Text('Save'),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () async {
                  await _deleteData(_objectIdController.text);
                  // await dummyFunction();
                },
                child: const Text('Delete'),
              ),
              const SizedBox(height: 20),
              _buildTextField(
                  'Object ID to Delete or Update', _objectIdController),
              ElevatedButton(
                onPressed: () async {
                  await _updateData(_objectIdController.text);
                },
                child: const Text('Update'),
              ),
              const SizedBox(height: 20),
              _buildIncrementDecrementButtons(),
              const SizedBox(height: 20),
              _buildListOperations(),
              const SizedBox(height: 20),
              Text(_responseMessage),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTextField(String label, TextEditingController controller,
      {bool isNumeric = false}) {
    return TextField(
      controller: controller,
      keyboardType: isNumeric ? TextInputType.number : TextInputType.text,
      decoration: InputDecoration(
        border: const OutlineInputBorder(),
        labelText: label,
      ),
    );
  }

  Widget _buildSwitch(String label, bool value, Function(bool) onChanged) {
    return SwitchListTile(
      title: Text(label),
      value: value,
      onChanged: onChanged,
    );
  }

  Widget _buildImagePicker() {
    return GestureDetector(
      child: pickedFile != null
          ? Container(
              width: 250,
              height: 250,
              decoration: BoxDecoration(border: Border.all(color: Colors.blue)),
              child: kIsWeb
                  ? Image.network(pickedFile!.path)
                  : Image.file(File(pickedFile!.path)),
            )
          : Container(
              width: 250,
              height: 250,
              decoration: BoxDecoration(border: Border.all(color: Colors.blue)),
              child: const Center(
                  child: Text('Click here to pick image from GamePoint')),
            ),
      onTap: () async {
        final picker = ImagePicker();
        XFile? image = (await picker.pickImage(source: ImageSource.gallery));
        if (image != null) {
          setState(() {
            pickedFile = image;
          });
        }
      },
    );
  }

  Widget _buildIncrementDecrementButtons() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          child: ElevatedButton(
            onPressed: () async {
              await _incrementField(_objectIdController.text);
            },
            child: const Text('+ Double'),
          ),
        ),
        const SizedBox(width: 10), // Adding some space between buttons
        Expanded(
          child: ElevatedButton(
            onPressed: () async {
              await _decrementField(_objectIdController.text);
            },
            child: const Text('- Double'),
          ),
        ),
      ],
    );
  }

  Widget _buildListOperations() {
    return Column(
      children: [
        _buildTextField('Unique Value to Add', _uniqueValueController),
        ElevatedButton(
          onPressed: () async {
            await _addUniqueToList(
                _objectIdController.text, _uniqueValueController.text);
          },
          child: const Text('Add Unique'),
        ),
        const SizedBox(height: 20),
        _buildTextField('Value to Remove', _removeValueController),
        ElevatedButton(
          onPressed: () async {
            await _removeFromList(
                _objectIdController.text, _removeValueController.text);
          },
          child: const Text('Remove'),
        ),
      ],
    );
  }

  Future<void> _saveData() async {
    //Parse values
    String stringValue = _stringController.text;
    List<String> listStringValue = _listStringController.text.isNotEmpty
        ? _listStringController.text
            .split(',') // Split by comma
            .map((e) =>
                e.trim()) // Remove any surrounding whitespace from each element
            .toList()
        : [];
    List<int> listtIntValue = _listIntController.text.isNotEmpty
        ? _listIntController.text
            .split(',') // Split by comma
            .map(
                (e) => int.parse(e.trim())) // Convert each string to an integer
            .toList()
        : [];
    double? doubleValue;
    if (_doubleController.text.isNotEmpty) {
      doubleValue = double.parse(_doubleController.text);
    }
    bool boolValue = _boolValue;
    DateTime selectedDate = _selectedDate;

    final gamePoint = ParseObject('GamePoint')
      ..set('bool', boolValue)
      ..set('date', selectedDate);
    if (stringValue.isNotEmpty) gamePoint.set('string', stringValue);
    if (doubleValue != null) gamePoint.set('double', doubleValue);
    if (listStringValue.isNotEmpty) {
      gamePoint.setAddAll('listString', listStringValue);
    }
    if (listtIntValue.isNotEmpty) gamePoint.setAddAll('listint', listtIntValue);
    if (_pointerController.text.isNotEmpty) {
      gamePoint.set('pointer', _parsePointer(_pointerController.text));
    }

    if (pickedFile != null) {
      setState(() {
        isLoading = true;
      });

      ParseFileBase? parseFile;

      if (kIsWeb) {
        parseFile =
            ParseWebFile(await pickedFile!.readAsBytes(), name: 'file.jpg');
      } else {
        parseFile = ParseFile(File(pickedFile!.path));
      }
      await parseFile.save();

      gamePoint.set('file', parseFile);
    }

    var apiResponse = await gamePoint.save();

    if (apiResponse.success && apiResponse.results != null) {
      setState(() {
        isLoading = false;
        pickedFile = null;
      });

      ScaffoldMessenger.of(context)
        ..removeCurrentSnackBar()
        ..showSnackBar(const SnackBar(
          content: Text(
            'File saved successfully on Back4app',
            style: TextStyle(color: Colors.white),
          ),
          duration: Duration(seconds: 3),
          backgroundColor: Colors.blue,
        ));
    } else {
      print("This is your request error: ${apiResponse.error}");
    }
  }

  Future<void> _updateData(String objectId) async {
    final gamePoint = ParseObject('GamePoint')..objectId = objectId;
    // Update fields with new values
    if (_stringController.text.isNotEmpty) {
      gamePoint.set('string', _stringController.text);
    }

    if (_doubleController.text.isNotEmpty) {
      gamePoint.set('double', double.parse(_doubleController.text));
    }

    gamePoint.set('bool', _boolValue);
    gamePoint.set('date', _selectedDate);

    if (_listStringController.text.isNotEmpty) {
      List<String> listStringValue =
          _listStringController.text.split(',').map((e) => e.trim()).toList();
      gamePoint.setAddAll('listString', listStringValue);
    }

    if (_listIntController.text.isNotEmpty) {
      List<int> listIntValue = _listIntController.text
          .split(',')
          .map((e) => int.parse(e.trim()))
          .toList();
      gamePoint.setAddUnique('listint', listIntValue);
    }

    if (_pointerController.text.isNotEmpty) {
      gamePoint.set('pointer', _parsePointer(_pointerController.text));
    }

    if (pickedFile != null) {
      ParseFileBase? parseFile;
      if (kIsWeb) {
        parseFile =
            ParseWebFile(await pickedFile!.readAsBytes(), name: 'file.jpg');
      } else {
        parseFile = ParseFile(File(pickedFile!.path));
      }
      await parseFile.save();
      gamePoint.set('file', parseFile);
    }

    // Save the updated object
    var response = await gamePoint.save();

    if (response.success) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Data updated successfully!')),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
            content: Text('Error updating data: ${response.error!.message}')),
      );
    }
  }

  Future<void> _incrementField(String objectId) async {
    final gamePoint = ParseObject('GamePoint')
      ..objectId = objectId
      ..setIncrement('double', 1);
    await gamePoint.save();
  }

  Future<void> _decrementField(String objectId) async {
    final gamePoint = ParseObject('GamePoint')
      ..objectId = objectId
      ..setDecrement('double', -1);
    await gamePoint.save();
  }

  Future<void> _addUniqueToList(String objectId, String value) async {
    final gamePoint = ParseObject('GamePoint')
      ..objectId = objectId
      ..setAddUnique('listString', value);
    await gamePoint.save();
  }

  Future<void> _removeFromList(String objectId, String value) async {
    final gamePoint = ParseObject('GamePoint')
      ..objectId = objectId
      ..setRemove('listString', value);
    await gamePoint.save();
  }

  Future<void> _selectDate(BuildContext context) async {
    final DateTime? picked = await showDatePicker(
      context: context,
      initialDate: _selectedDate,
      firstDate: DateTime(2000),
      lastDate: DateTime(2101),
    );
    if (picked != null && picked != _selectedDate) {
      setState(() {
        _selectedDate = picked;
      });
    }
  }

  Future<void> _deleteData(String objectId) async {
    // Delete data logic
    final parseObject = ParseObject('GamePoint')
      ..objectId = objectId
      ..unset("listString");

    await parseObject.save();
  }

  // Convert Pointer field to a suitable format
  ParseObject _parsePointer(String objectId) {
    // There should be an existing Game class with objects
    final pointer = ParseObject('Game')..objectId = objectId;
    return pointer;
  }
}
```

이 코드는 Flutter에서 Parse SDK를 초기화하고, 주요 애플리케이션을 설정하며, 제목이 있는 간단한 홈 페이지를 표시합니다.

## 결론

이 가이드에서는 ParseObjects와 Parse에서 사용할 수 있는 다양한 데이터 유형에 대해 배웠습니다. 또한 back4app 백엔드에서 데이터 유형을 저장하고 검색하는 작업을 처리하는 방법도 배웠습니다.
