---
title: Flutter에서 파일 저장 및 검색 구현 가이드
slug: docs-kr/flutter/parse-sdk/flutter-save-file
description: Back4App을 사용하여 Flutter 앱에서 파일을 저장하고 검색하는 방법을 배우십시오.
image: https://www.back4app.com/images/logo-social.png
createdAt: 2024-03-18T15:10:02.884Z
---

# 플러터 앱에서 파일 저장하기

## 소개

때때로 애플리케이션은 ParseObject 내부에 저장하기에는 너무 큰 데이터를 저장해야 합니다. 가장 일반적인 사용 사례는 이미지를 저장하는 것이지만, 문서, 비디오, 음악 및 기타 이진 데이터에도 사용할 수 있습니다.

Parse에 파일을 저장하려면 항상 파일을 다른 데이터 객체와 연결해야 합니다. 그래야 객체를 쿼리할 때 이 파일 경로를 검색할 수 있습니다. 연결하지 않으면 파일은 저장되지만 Cloud에서 찾을 수 없습니다.
또한 중요한 팁은 파일에 파일 확장자가 있는 이름을 부여하는 것입니다. 이 확장자는 Parse가 파일 유형을 파악하고 적절하게 처리할 수 있도록 합니다. 또한 각 업로드는 고유 식별자를 받으므로 동일한 이름을 사용하여 여러 파일을 업로드하는 데 문제가 없습니다.

Flutter에서는 ParseFile과 ParseWebFile을 사용하여 애플리케이션 파일을 Cloud에 저장하고 검색할 수 있습니다. 이 가이드는 Back4app 클라우드 저장소를 관리하기 위해 Flutter 앱에서 파일을 저장하고 검색하는 방법을 설명합니다.

:::hint{type="danger"}
- 파일을 데이터 객체에 연결하지 않으면 파일이 고아 파일이 되어 Back4App Cloud에서 찾을 수 없습니다.
:::

::embed[]{url="https://www.youtube.com/embed/C2XQGks2ivA"}

## 전제 조건

:::hint{type="info"}
- Back4App에서 [생성된](https://www.back4app.com/docs/get-started/new-parse-app) 앱:
  - **참고:&#x20;**&#x20;[새 Parse 앱 튜토리얼](https://www.back4app.com/docs/get-started/new-parse-app)을 따라 Back4App에서 Parse 앱을 만드는 방법을 배우세요.
- Back4app에 연결된 Flutter 앱.
  - **참고:&#x20;**&#x20;[Flutter 프로젝트에 Parse SDK 설치하기](https://www.back4app.com/docs/flutter/parse-sdk/parse-flutter-sdk)를 따라 Back4App에 연결된 Flutter 프로젝트를 만드세요.
- Android 또는 iOS를 실행하는 장치(또는 가상 장치).
- 이 가이드 예제를 실행하려면 [image\_picker](https://pub.dev/packages/image_picker) 플러그인을 올바르게 설정해야 합니다. 장치에 저장된 이미지에 접근하기 위해 iOS에 대한 권한을 추가하는 것을 잊지 마세요. \{: .btn target=”\_blank” rel=”nofollow”}. Android 및 iOS 프로젝트 설정에 대한 지침을 주의 깊게 읽으세요.
:::

## 목표

Back4app에서 이미지를 업로드하고 표시하는 Flutter 갤러리 앱을 만듭니다.

## 1 - ParseFile 및 ParseWebFile 클래스 이해하기

이 Parse SDK for Flutter에는 세 가지 다른 파일 클래스가 있습니다.

- <font color="#2166ae">`ParseFileBase`</font>는 이 SDK가 처리할 수 있는 모든 파일 클래스의 기초가 되는 추상 클래스입니다.
- <font color="#2166ae">`ParseFile`</font>는 <font color="#2166ae">`ParseFileBase`</font>를 확장하며, 기본적으로 모든 플랫폼에서 파일 클래스로 사용됩니다(웹에는 유효하지 않음). 이 클래스는 원시 파일을 저장하기 위해 <font color="#2166ae">`File`</font>을 사용합니다.<font color="#2166ae">`dart:io`</font>
- <font color="#2166ae">`ParseWebFile`</font>는 Flutter Web에서 사용되는 <font color="#2166ae">`ParseFile`</font>에 해당합니다. 이 클래스는 원시 파일을 저장하기 위해 <font color="#2166ae">`Uint8List`</font>을 사용합니다.

파일을 조작하기 위해 <font color="#2166ae">`ParseFileBase`</font>에서 사용할 수 있는 메서드:

- <font color="#2166ae">`save()`</font> 또는 <font color="#2166ae">`upload()`</font>를 사용하여 파일을 클라우드에 저장합니다.
- <font color="#2166ae">`download()`</font>를 사용하여 파일을 검색하고 로컬 저장소에 저장합니다.

저장된 파일에서 정보를 가져오는 속성이 있습니다:

- <font color="#2166ae">`url`</font>: 파일 URL을 가져옵니다. 파일을 저장한 후 또는 Parse.Object에서 파일을 가져온 후에만 사용할 수 있습니다.
- <font color="#2166ae">`name`</font>: 파일 이름을 가져옵니다. 이는 사용자가 <font color="#2166ae">`save()`</font> 메서드를 호출하기 전에 지정한 파일 이름입니다. 메서드를 호출한 후, 속성은 고유 식별자를 받습니다.

## 2 - 이미지 업로드

이미지를 업로드하려면 <font color="#2166ae">`ParseFileBase`</font> 인스턴스를 생성한 다음 <font color="#2166ae">`save`</font> 메서드를 호출하면 됩니다.

이제 우리의 <font color="#2166ae">`upload`</font> 함수에서 그렇게 해봅시다:

```javascript
1         ParseFileBase? parseFile;
2
3         if (kIsWeb) {
4           //Flutter Web
5           parseFile = ParseWebFile(
6               await pickedFile!.readAsBytes(),
7               name: 'image.jpg'); //Name for file is required
8         } else {
9           //Flutter Mobile/Desktop
10          parseFile = ParseFile(File(pickedFile!.path));
11        }
12        await parseFile.save();
```

위의 코드 조각은 이미지를 생성하고 저장하며, 저장이 완료된 후 <font color="#2166ae">`ParseObject`</font>와 연결합니다. <font color="#2166ae">`Gallery`</font>.

```javascript
1      final gallery = ParseObject('Gallery')
2        ..set('file', parseFile);
3      await gallery.save();
```

## 3 - 이미지 표시하기

이미지를 표시하려면 이미지의 URL을 가져와야 합니다.

이미지를 업로드하려면 <font color="#2166ae">`ParseFileBase`</font> 인스턴스를 생성한 다음 <font color="#2166ae">`save`</font> 메서드를 호출하면 됩니다.

```javascript
1       ParseFileBase? varFile = parseObject.get<ParseFileBase>('file');
2
3       return Image.network(
4         varFile!.url!,
5         width: 200,
6         height: 200,
7         fit: BoxFit.fitHeight,
8       );
```

## 4 - Flutter 앱에서 업로드 및 검색

이제 간단한 인터페이스를 사용하여 Flutter 앱에서 이미지를 업로드하고 표시하는 예제를 사용해 보겠습니다.

Flutter 프로젝트를 열고, <font color="#2166ae">`main.dart`</font> 파일의 모든 코드를 정리하고 다음으로 교체하세요:

```dart
1   import 'dart:io';
2
3   import 'package:flutter/cupertino.dart';
4   import 'package:flutter/foundation.dart';
5   import 'package:flutter/material.dart';
6   import 'package:image_picker/image_picker.dart';
7   import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';
8
9   void main() async {
10    WidgetsFlutterBinding.ensureInitialized();
11
12    final keyApplicationId = 'YOUR_APP_ID_HERE';
13    final keyClientKey = 'YOUR_CLIENT_KEY_HERE';
14
15    final keyParseServerUrl = 'https://parseapi.back4app.com';
16
17    await Parse().initialize(keyApplicationId, keyParseServerUrl,
18        clientKey: keyClientKey, debug: true);
19
20    runApp(MaterialApp(
21      title: 'Flutter - Storage File',
22      debugShowCheckedModeBanner: false,
23      home: HomePage(),
24    ));
25  }
26  
27  class HomePage extends StatefulWidget {
28    @override
29    _HomePageState createState() => _HomePageState();
30  }
31  
32  class _HomePageState extends State<HomePage> {
33    PickedFile? pickedFile;
34 
35    List<ParseObject> results = <ParseObject>[];
36    double selectedDistance = 3000;
37
38    @override
39    Widget build(BuildContext context) {
40      return Scaffold(
41          body: Padding(
42        padding: const EdgeInsets.all(16.0),
43        child: Column(
44          crossAxisAlignment: CrossAxisAlignment.stretch,
45          children: [
46            Container(
47              height: 200,
48              child: Image.network(
49                  'https://blog.back4app.com/wp-content/uploads/2017/11/logo-b4a-1-768x175-1.png'),
50            ),
51            SizedBox(
52              height: 16,
53            ),
54            Center(
55              child: const Text('Flutter on Back4app - Save File',
56                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
57            ),
58            SizedBox(
59              height: 16,
60            ),
61            Container(
62              height: 50,
63              child: ElevatedButton(
64                child: Text('Upload File'),
65                style: ElevatedButton.styleFrom(primary: Colors.blue),
66                onPressed: () {
67                  Navigator.push(
68                    context,
69                    MaterialPageRoute(builder: (context) => SavePage()),
70                  );
71                },
72              ),
73            ),
74            SizedBox(
75              height: 8,
76            ),
77            Container(
78                height: 50,
79                child: ElevatedButton(
80                  child: Text('Display File'),
81                  style: ElevatedButton.styleFrom(primary: Colors.blue),
82                  onPressed: () {
83                    Navigator.push(
84                      context,
85                      MaterialPageRoute(builder: (context) => DisplayPage()),
86                    );
87                  },
88                ))
89          ],
90        ),
91      ));
92    }
93  }
94  
95  class SavePage extends StatefulWidget {
96    @override
97    _SavePageState createState() => _SavePageState();
98  }
99
100 class _SavePageState extends State<SavePage> {
101   PickedFile? pickedFile;
102   bool isLoading = false;
103 
104   @override
105   Widget build(BuildContext context) {
106     return Scaffold(
107       appBar: AppBar(
108         title: Text('Upload Fie'),
109       ),
110       body: Padding(
111         padding: const EdgeInsets.all(12.0),
112         child: Column(
113           crossAxisAlignment: CrossAxisAlignment.stretch,
114           children: [
115             SizedBox(height: 16),
116             GestureDetector(
117               child: pickedFile != null
118                   ? Container(
119                       width: 250,
120                       height: 250,
121                       decoration:
122                           BoxDecoration(border: Border.all(color: Colors.blue)),
123                       child: kIsWeb
124                           ? Image.network(pickedFile!.path)
125                           : Image.file(File(pickedFile!.path)))
126                   : Container(
127                       width: 250,
128                       height: 250,
129                       decoration:
130                           BoxDecoration(border: Border.all(color: Colors.blue)),
131                       child: Center(
132                         child: Text('Click here to pick image from Gallery'),
133                       ),
134                     ),
135               onTap: () async {
136                 PickedFile? image =
137                     await ImagePicker().getImage(source: ImageSource.gallery);
138 
139                 if (image != null) {
140                   setState(() {
141                     pickedFile = image;
142                   });
143                 }
144               },
145             ),
146             SizedBox(height: 16),
147             Container(
148                 height: 50,
149                 child: ElevatedButton(
150                   child: Text('Upload file'),
151                   style: ElevatedButton.styleFrom(primary: Colors.blue),
152                   onPressed: isLoading || pickedFile == null
153                       ? null
154                       : () async {
155                           setState(() {
156                             isLoading = true;
157                           });
158                           ParseFileBase? parseFile;
159 
160                           if (kIsWeb) {
161                             //Flutter Web
162                             parseFile = ParseWebFile(
163                                 await pickedFile!.readAsBytes(),
164                                 name: 'image.jpg'); //Name for file is required
165                           } else {
166                             //Flutter Mobile/Desktop
167                             parseFile = ParseFile(File(pickedFile!.path));
168                           }
169                           await parseFile.save();
170 
171                           final gallery = ParseObject('Gallery')
172                             ..set('file', parseFile);
173                           await gallery.save();
174
175                           setState(() {
176                             isLoading = false;
177                             pickedFile = null;
178                           });
179 
180                           ScaffoldMessenger.of(context)
181                             ..removeCurrentSnackBar()
182                             ..showSnackBar(SnackBar(
183                               content: Text(
184                                 'Save file with success on Back4app',
185                                 style: TextStyle(
186                                   color: Colors.white,
187                                 ),
188                               ),
189                               duration: Duration(seconds: 3),
190                               backgroundColor: Colors.blue,
191                             ));
192                         },
193                 ))
194           ],
195         ),
196       ),
197     );
198   }
199 }
200
201 class DisplayPage extends StatefulWidget {
202   @override
203   _DisplayPageState createState() => _DisplayPageState();
204 }
205
206 class _DisplayPageState extends State<DisplayPage> {
207   @override
208   Widget build(BuildContext context) {
209     return Scaffold(
210       appBar: AppBar(
211         title: Text("Display Gallery"),
212       ),
213       body: FutureBuilder<List<ParseObject>>(
214           future: getGalleryList(),
215           builder: (context, snapshot) {
216             switch (snapshot.connectionState) {
217               case ConnectionState.none:
218               case ConnectionState.waiting:
219                 return Center(
220                   child: Container(
221                       width: 100,
222                       height: 100,
223                       child: CircularProgressIndicator()),
224                 );
225               default:
226                 if (snapshot.hasError) {
227                   return Center(
228                     child: Text("Error..."),
229                   );
230                 } else {
231                   return ListView.builder(
232                       padding: const EdgeInsets.only(top: 8),
233                       itemCount: snapshot.data!.length,
234                       itemBuilder: (context, index) {
235                         //Web/Mobile/Desktop
236                        ParseFileBase? varFile =
237                             snapshot.data![index].get<ParseFileBase>('file');
238 
239                         //Only iOS/Android/Desktop
240                         /*
241                         ParseFile? varFile =
242                             snapshot.data![index].get<ParseFile>('file');
243                         */
244                         return Image.network(
245                           varFile!.url!,
246                           width: 200,
247                           height: 200,
248                           fit: BoxFit.fitHeight,
249                         );
250                       });
251                 }
252             }
253           }),
254     );
255   }
256 
257   Future<List<ParseObject>> getGalleryList() async {
258     QueryBuilder<ParseObject> queryPublisher =
259         QueryBuilder<ParseObject>(ParseObject('Gallery'))
260           ..orderByAscending('createdAt');
261     final ParseResponse apiResponse = await queryPublisher.query();
262 
263     if (apiResponse.success && apiResponse.results != null) {
264       return apiResponse.results as List<ParseObject>;
265     } else {
266       return [];
267     }
268   }
269 }
```

당신의 <font color="#2166ae">`ApplicationId`</font> 및 <font color="#2166ae">`Client Key`</font> 자격 증명을 찾으려면 앱 대시보드->설정->보안 및 키로 이동하세요 [Back4App 웹사이트](https://www.back4app.com/).

다음 두 값을 사용하여 <font color="#2166ae">`main.dart`</font>에서 코드를 업데이트하세요 <font color="#2166ae">`ApplicationId`</font> 및 <font color="#2166ae">`ClientKey`</font>를 Back4app에 입력하세요.

- keyApplicationId = <font color="#2166ae">`AppId`</font>
- keyClientKey = <font color="#2166ae">`Client Key`</font>

프로젝트를 실행하면 앱이 이미지와 같이 로드됩니다.

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/SeiDfLamRqJFys0hfz8xh_image.png" size="50" width="320" height="638" position="center" showCaption="false"}

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/vkn0AimenNqVuC0LWgWVP_image.png" size="50" width="320" height="638" position="center" showCaption="false"}

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/gVi8ZXJg-N0luCP83yayN_image.png" size="50" width="320" height="638" position="center" showCaption="false"}

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/SJhjGyiemfZxKoOMICKBE_image.png" size="50" width="320" height="638" position="center" showCaption="false"}

## 결론

이 시점에서, 당신은 Back4App에 이미지를 업로드하고 Flutter 애플리케이션에 표시했습니다.
