Flutter 中解析数据类型的使用指南
本指南介绍了Flutter中的Parse数据类型,使用来自main.dart文件的示例。Parse数据存储围绕ParseObject展开,它包含JSON兼容数据的键值对,使其无模式。这意味着您可以存储任何数据,而无需提前定义模式。
您可以设置任何您想要的键值对,我们的后端将存储它们。
例如,假设您正在跟踪游戏的高分。一个单一的Parse对象可以包含:
score: 1337, playerName: "Sean Plott", cheatMode: false
我们建议您NameYourClassesLikeThis和nameYourKeysLikeThis以增强代码的可读性和可维护性。
理解我们的应用程序
为了更好地理解Back4app,让我们探索Flutter应用程序中Parse操作的代码示例,以及主要支持的数据类型。本指南不会解释任何Flutter应用程序代码,因为主要关注点是将Parse与Flutter结合使用。
先决条件
要完成本教程,您需要:
- 一个连接到 Back4app 的 Flutter 应用。
- 一个运行 Android 或 iOS 的设备(或虚拟设备)。
1 - 使用 Parse 对象
每个 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 - 计数器
您可以使用 set() 方法在 ParseObject 中递增或递减整数字段。
然而,这并不有效,如果多个客户端试图更新同一个计数器,可能会导致问题。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, 及其各自的 All 版本。
列表: ["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 开始,存在可能导致上传文件时出现错误的重大更改。请遵循 此指南 来修复您可能遇到的任何上传问题。
稍后的指南将进一步讨论并展示如何 使用 Parse 保存和显示文件.
6 - 地理坐标点
Parse 允许您将现实世界的纬度和经度坐标与其 GeoPoint 数据类型的对象关联。将 ParseGeoPoint 添加到 ParseObject 将使查询能够考虑对象与参考点的接近程度。
示例:
// 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();您可以在这个 main.dart 文件中找到保存地理坐标点位置的独立示例。
完整应用示例
此示例演示如何:
- 创建、删除和更新一个 GamePoint Parse 对象。
- 处理各种数据类型,包括字符串、双精度浮点数、布尔值、文件、地理点、列表和日期。
在 iOS 上,您需要授予模拟器必要的访问权限。
如果您在网页上运行 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后端检索的操作。