Flutter
...
Data Objects
Flutter를 사용한 Parse 데이터 핸들링 가이드
18 분
이 가이드는 flutter에서 parse 데이터 유형을 소개하며, main dart 파일의 예제를 사용합니다 parse 데이터 저장소는 parseobject를 중심으로 하며, 이는 json 호환 데이터의 키 값 쌍을 포함하고 있어 스키마가 없습니다 이는 사전 정의된 스키마 없이도 데이터를 저장할 수 있음을 의미합니다 원하는 키 값 쌍을 설정할 수 있으며, 우리의 백엔드는 이를 저장합니다 예를 들어, 게임의 최고 점수를 추적한다고 가정해 보겠습니다 단일 parse 객체는 다음을 포함할 수 있습니다 코드 가독성과 유지 관리를 향상시키기 위해 nameyourclasseslikethis 및 nameyourkeyslikethis 를 사용하는 것을 권장합니다 우리 앱 이해하기 back4app을 더 잘 이해하기 위해, 주요 지원 데이터 유형과 함께 flutter 애플리케이션에서 parse 작업의 코드 예제를 살펴보겠습니다 이 가이드는 parse와 flutter를 사용하는 데 초점을 맞추기 때문에 flutter 앱 코드에 대한 설명은 포함되지 않습니다 전제 조건 이 튜토리얼을 완료하려면 다음이 필요합니다 안드로이드 스튜디오 https //developer android com/studio 또는 vs 코드 설치 (와 플러그인 다트와 플러터) 백4앱에서 생성된 앱 참고 새 파스 앱 튜토리얼 을 따라 백4앱에서 파스 앱을 만드는 방법을 배우세요 백4앱에 연결된 플러터 앱 참고 플러터 프로젝트에 파스 sdk 설치 를 따라 백4앱에 연결된 플러터 프로젝트를 만드세요 안드로이드 또는 ios를 실행하는 장치(또는 가상 장치) 1 파스 객체 작업하기 각 parseobject는 서로 다른 데이터 유형을 구분하는 데 사용되는 클래스 이름(예 gamepoint)을 가지고 있습니다 다음은 데이터 유형으로 새 parse 객체를 생성하고 저장하는 방법입니다 문자열 , 정수 , & 부울 final gamepoint = parseobject('gamepoint') set('score', 1337) set('playername', 'sean plott') set('cheatmode', false); await gamepoint save(); 새로운 parse 객체를 쿼리하고 데이터 유형을 검색하려면 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 값을 증가시키는 업데이트는 다음과 같이 작성됩니다 final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setincrement('intfield', 1); await gamepoint save(); 감소시키려면 final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setdecrement('intfield', 1); await gamepoint save(); setincrement() 및 setdecrement() 를 save() 호출과 함께 사용하면 여러 필드를 수정할 수 있는 더 큰 저장 작업의 일환으로 값을 업데이트할 수 있습니다 이는 추가 네트워크 요청을 피하는 데 더 좋습니다 3 목록 parse는 setadd , setaddunique , setremove , 및 해당 모두 버전을 포함하여 목록 데이터 작업을 위한 메서드를 제공합니다 목록 \["a","b","c"] 3 1 setadd final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setadd('liststringfield', 'd'); await gamepoint save(); 결과 \["a","b","c","d"] 3 2 setaddall final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setaddall('liststringfield', \['e','f']); await gamepoint save(); 결과 \["a","b","c","d","e","f"] setaddall 는 목록에 이미 포함된 중복 요소를 추가하지 않습니다 3 3 setaddunique final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setaddunique('liststringfield', \['a', 'e', 'g']); await gamepoint save(); 결과 \["a","b","c","d","e","f","g"] 3 4 setremove final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' setremove('liststringfield', 'd'); await gamepoint save(); 결과 \["a","b","c","e","f","g"] 4 parseobject에서 필드 제거 객체에서 단일 필드를 삭제하려면 unset 작업을 사용하십시오 final gamepoint = parseobject('gamepoint') objectid = 'yourobjectid' unset("liststringfield"); 5 파일 parsefile을 사용하면 클라우드에서 애플리케이션 파일을 저장하고 검색할 수 있습니다 아래는 로컬 이미지 파일을 back4app에 업로드하는 기본 예제입니다 // 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(); } 이 코드 조각은 이미지 파일을 검색하는 방법을 보여줍니다 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를 추가하면 객체가 기준점에 얼마나 가까운지를 고려하는 쿼리를 가능하게 합니다 예 // 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에서는 시뮬레이터에 필요한 접근 권한을 부여해야 합니다 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 앱을 실행하는 경우 위 단계를 건너뛸 수 있습니다 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 백엔드에서 데이터 유형을 저장하고 검색하는 작업을 처리하는 방법도 배웠습니다