【发布时间】:2020-11-25 22:16:20
【问题描述】:
我正在开发一个 Flutter 应用程序,它由一个具有 Parse Server 背景的员工调度程序组成。该应用程序的工作原理如下:
1) 用户打开应用程序;
2)应用程序显示可用员工的卡片列表(如果服务器数据库中有一些)
3)用户可以编辑、确认或删除员工的可用性。
应用程序正在运行。这意味着我也可以从我的 Parse Server 和我的应用程序屏幕一样对数据进行 CRUD。
目标:数据库中的每一次数据更改都需要刷新(实时)用户屏幕,移除、删除或更新(更改的)数据本身。
我的想法:使用 parse_server_sdk: ^2.0.1 作为依赖项 (pubspec.yaml) 并实现实时查询(如 github 文档 here)
问题:编译后,尽管我的 AndroidStudio 在视觉上没有崩溃,我的应用程序本身也没有崩溃,但它显示(日志)“LiveQuery 不起作用,如果提供了 ParseConnectivityProvider” .
没有问题(main.dart):
void main() async{
runApp(MyApp());
}
导致问题:
void main() async{
//the following parameters are in other file (constants.dart)...they are working
Parse().initialize(
kParseApplicationId,
kParseServerUrl,
masterKey: kParseMasterKey,
clientKey: kParseClientKey,
debug: true,
liveQueryUrl: kLiveQueryUrl,
autoSendSessionId: true,
);
var dietPlan = ParseObject('DietPlan')
..set('Name', 'Ketogenic')
..set('Fat', 65);
await dietPlan.save();
var response = await dietPlan.save();
if (response.success) {
dietPlan = response.result;
}
//until here, the program works well.The data is inserted and retrieved, ok...but exactly here..the problem!
final LiveQuery liveQuery = LiveQuery(); //from here..this is causing problems
QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('DietPlan'))
..whereEqualTo('intNumber', 1);
Subscription subscription = await liveQuery.client.subscribe(query);
subscription.on(LiveQueryEvent.delete, (value) {
print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
runApp(MyApp());
}
我没有找到任何解决方案 googleing.Any。经过大量研究,我已经(深入研究库) Parse.Initialize(...) 将connectivityProvider 作为其参数之一,但我没有填写该参数。
另外,当我插入该行时,问题就开始了:
final LiveQuery liveQuery = LiveQuery();
有人可以帮我解决这个问题吗?在此先感谢
在下面,我展示了我所有的代码(main.dart):
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:intl/intl.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:marca_horario/model/data.dart';
import 'package:marca_horario/network_utils/data_utils.dart';
import 'package:http/http.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:marca_horario/constants.dart';
void myLQ() async {
Parse().initialize(
kParseApplicationId,
kParseServerUrl,
masterKey: kParseMasterKey,
clientKey: kParseClientKey,
debug: true,
liveQueryUrl: kLiveQueryUrl,
autoSendSessionId: true,
);
var dietPlan = ParseObject('DietPlan')
..set('Name', 'Ketogenic')
..set('Fat', 65);
await dietPlan.save();
var response = await dietPlan.save();
if (response.success) {
dietPlan = response.result;
}
final LiveQuery liveQuery = LiveQuery();
QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('DietPlan'))
..whereEqualTo('intNumber', 1);
Subscription subscription = await liveQuery.client.subscribe(query);
subscription.on(LiveQueryEvent.delete, (value) {
print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
}
void main() async{
Parse().initialize(
kParseApplicationId,
kParseServerUrl,
masterKey: kParseMasterKey,
clientKey: kParseClientKey,
debug: true,
liveQueryUrl: kLiveQueryUrl,
autoSendSessionId: true,
);
var dietPlan = ParseObject('DietPlan')
..set('Name', 'Ketogenic')
..set('Fat', 65);
await dietPlan.save();
var response = await dietPlan.save();
if (response.success) {
dietPlan = response.result;
}
final LiveQuery liveQuery = LiveQuery();
QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('DietPlan'))
..whereEqualTo('intNumber', 1);
Subscription subscription = await liveQuery.client.subscribe(query);
subscription.on(LiveQueryEvent.delete, (value) {
print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
print((value as ParseObject).objectId);
print((value as ParseObject).updatedAt);
print((value as ParseObject).createdAt);
print((value as ParseObject).get('objectId'));
print((value as ParseObject).get('updatedAt'));
print((value as ParseObject).get('createdAt'));
});
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [const Locale('pt', 'BR')],
home: Home()
);
}
}
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
var _listTiles = List<String>();
Color _iconColor = Colors.black;
Color standardIconColor = Colors.black;
Color alternateIconColor = Colors.green;
TextEditingController _nameController = TextEditingController();
var _scaffoldKey = new GlobalKey<ScaffoldState>();
String standardTileTitle = "Adicione um horário disponível...";
String _titleTile = "Adicione um horário disponível...";
String _tileSubtitle = "Edite o nome do funcionário...";
int _selectedIndexBottomNavBar = 0;
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = new GlobalKey<RefreshIndicatorState>();
@override
Widget build(BuildContext context) {
return RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () async {
setState(() {
});},
child: Scaffold(
key: _scaffoldKey,
floatingActionButton: FloatingActionButton(
onPressed: () {
invokeDatePicker();
},
child: Icon(Icons.add),
),
bottomNavigationBar: bottomNavigationBar(),
appBar: AppBar(
title: Text('Marca Horário'),
),
// body: Center(
// child: Text('Hello World'),
body: bodyStartScreen()
),
);
}
Widget bottomNavigationBar(){
void _onItemTapped(int index) {
setState(() {
_selectedIndexBottomNavBar = index;
});
print(_selectedIndexBottomNavBar);
if(_selectedIndexBottomNavBar == 2){
DatePicker.showDateTimePicker(context,
showTitleActions: true,
minTime: DateTime(2020, 1, 1),
maxTime: DateTime(2021, 12, 31),
onChanged: (date) {
print('change $date');
},
onConfirm: (date) {
print('confirm $date');
_listTiles.add(DateFormat.yMMMEd('pt_BR').add_Hm().format(date).toString());
_titleTile = DateFormat.yMMMEd('pt_BR').add_Hm().format(date).toString();
setState(() {
});
},
currentTime: DateTime.now(),
locale: LocaleType.pt);
}
}
return BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Funcionário',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_time),
label: 'Marcar',
),
],
currentIndex: _selectedIndexBottomNavBar,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
);
}
Widget bodyStartScreen(){
return Column(
children: [
//the main title of the screen
Padding(
padding: EdgeInsets.all(16.0),
child: Text("Horários Possíveis",
style: TextStyle(
fontSize: 18.0
),
),
),
//gets available employees and datetimes from the server
FutureBuilder(builder: (context,snapshot){
if (snapshot.data != null) {
List<Data> dataList = snapshot.data;
return Expanded(
child: ListView.builder(
itemBuilder: (_, position) {
return Card(
child: ListTile(
title: Text(dataList[position].dateTime),
subtitle: Text(dataList[position].employee),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(icon: Icon(Icons.edit), onPressed: () {
//Show dialog box to update item
showUpdateDialog(dataList[position]);
}),
IconButton(icon: Icon(Icons.check_circle, color: Colors.green,), onPressed: () {
}),
//Show dialog box to delete item
IconButton(icon: Icon(Icons.delete), onPressed: () {
deleteData(dataList[position].objectId);
}),
],
),
),
);
},
itemCount: dataList.length,
),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
future: getDataList(),
),
Divider(
color: Colors.black,
),
scheduleTile()
],
);
}
void invokeDatePicker(){
DatePicker.showDateTimePicker(context,
showTitleActions: true,
minTime: DateTime(2020, 1, 1),
maxTime: DateTime(2021, 12, 31),
onChanged: (date) {
print('change $date');
},
onConfirm: (date) {
print('confirm $date');
_listTiles.add(DateFormat.yMMMEd('pt_BR').add_Hm().format(date).toString());
_titleTile = DateFormat.yMMMEd('pt_BR').add_Hm().format(date).toString();
setState(() {
});
},
currentTime: DateTime.now(),
locale: LocaleType.pt);
}
void showUpdateDialog(Data data) {
_nameController.text = data.employee;
showDialog(context: context,
builder: (_) => AlertDialog(
content: Container(
width: double.maxFinite,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: "Atualizar funcionário disponível",
),
),
),
actions: <Widget>[
FlatButton(onPressed: () {
Navigator.pop(context);
data.employee = _nameController.text;
updateData(data);
}, child: Text("Atualizar")),
FlatButton(onPressed: () {
Navigator.pop(context);
}, child: Text("Cancelar")),
],
)
);
}
Widget scheduleTile(){
return Padding(
padding: EdgeInsets.only(bottom: 80.0),
child: Card(
color: Colors.grey,
child: ListTile(
title: Text(_titleTile),
subtitle: Text(_tileSubtitle),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.edit,
color: standardIconColor,
size: 20.0,
),
onPressed: () {
setState(() {
employeeAvailable();
});
},
),
IconButton(
icon: Icon(
Icons.check_circle_outline,
color: _iconColor,
size: 20.0,
),
onPressed: () {
setState(() {
(_titleTile != standardTileTitle) ? confirmSchedule() : fillTimeDialog();
});
},
)
],
),
),
),
);
}
void fillTimeDialog(){
showDialog(context: context,
builder: (_) => AlertDialog(
content: Container(
width: double.maxFinite,
child: Text("Insira o horário disponível!"),
),
actions: <Widget>[
FlatButton(onPressed: () {
Navigator.pop(context);
}, child: Text("OK")),
],
)
);
}
void employeeAvailable(){
showDialog(context: context,
builder: (_) => AlertDialog(
content: Container(
width: double.maxFinite,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: "Funcionário",
),
),
),
actions: <Widget>[
FlatButton(onPressed: () {
Navigator.pop(context);
//addTodo();
setState(() {
_tileSubtitle = "Disponível: " + _nameController.text;
});
}, child: Text("Inserir")),
FlatButton(onPressed: () {
Navigator.pop(context);
setState(() {
_tileSubtitle = " ";
});
}, child: Text("Desfazer")),
],
)
);
}
void confirmSchedule(){
showDialog(context: context,
builder: (_) => AlertDialog(
content: Container(
width: double.maxFinite,
child: Text("Confirma disponibilidade?")
),
actions: <Widget>[
FlatButton(onPressed: () {
Navigator.pop(context);
//addTodo();
addData();
setState(() {
_iconColor = alternateIconColor;
_tileSubtitle = "Disponível: " + _nameController.text;
});
}, child: Text("Confirma")),
FlatButton(onPressed: () {
Navigator.pop(context);
setState(() {
_iconColor = standardIconColor;
_tileSubtitle = " ";
});
}, child: Text("Não")),
],
)
);
}
void addData() {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Row(
children: <Widget>[
Text("Adicionando informações..."),
CircularProgressIndicator(),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
duration: Duration(minutes: 1),
));
Data data = Data(employee: _tileSubtitle, dateTime: _titleTile);
DataUtils.addData(data)
.then((res) {
_scaffoldKey.currentState.hideCurrentSnackBar();
Response response = res;
if (response.statusCode == 201) {
//Successful
_nameController.text = "";
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Informações disponibilizadas!"), duration: Duration(seconds: 1),));
setState(() {
//Update UI
});
}
});
}
void deleteData(String objectId) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("Excluindo disponibilidade..."),
CircularProgressIndicator(),
],
),
duration: Duration(minutes: 1),
),);
DataUtils.deleteData(objectId)
.then((res) {
_scaffoldKey.currentState.hideCurrentSnackBar();
Response response = res;
if (response.statusCode == 200) {
//Successfully Deleted
_scaffoldKey.currentState.showSnackBar(SnackBar(content: (Text("Disponibilidade excluída!")),duration: Duration(seconds: 1),));
setState(() {
});
} else {
//Handle error
}
});
}
void updateData(Data data) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("Atualizando disponibilidade..."),
CircularProgressIndicator(),
],
),
duration: Duration(minutes: 1),
),);
DataUtils.updateData(data)
.then((res) {
_scaffoldKey.currentState.hideCurrentSnackBar();
Response response = res;
if (response.statusCode == 200) {
//Successfully Deleted
_nameController.text = "";
_scaffoldKey.currentState.showSnackBar(SnackBar(content: (Text("Disponibilidade atualizada!"))));
setState(() {
});
} else {
//Handle error
}
});
}
Future <List<Data>> getDataList() async{
List<Data> dataList = [];
Response response = await DataUtils.getDataList();
print("Code is ${response.statusCode}");
print("Response is ${response.body}");
if (response.statusCode == 200) {
var body = json.decode(response.body);
var results = body["results"];
for (var data in results) {
dataList.add(Data.fromJson(data));
}
} else {
//Handle error
}
return dataList;
}
}
data_utils.dart:
import 'dart:convert';
import 'package:http/http.dart';
import 'package:marca_horario/model/data.dart';
import 'package:marca_horario/constants.dart';
class DataUtils {
//static final String _baseUrl = "https://parseapi.back4app.com/classes/";
static final String _baseUrl = baseUrl;
//CREATE
static Future<Response> addData(Data data) async {
String apiUrl = _baseUrl + "Data";
Response response = await post(apiUrl,
headers: {
'X-Parse-Application-Id': kParseApplicationId,
'X-Parse-REST-API-Key': kParseRestApiKey,
'Content-Type': 'application/json'
},
body: json.encode(data.toJson()),
);
return response;
}
//READ
static Future getDataList() async{
String apiUrl = _baseUrl + "Data";
Response response = await get(apiUrl, headers: {
'X-Parse-Application-Id' : kParseApplicationId,
'X-Parse-REST-API-Key' : kParseRestApiKey,
});
return response;
}
//UPDATE
static Future updateData(Data data) async{
String apiUrl = _baseUrl + "Data/${data.objectId}";
Response response = await put(apiUrl, headers: {
'X-Parse-Application-Id' : kParseApplicationId,
'X-Parse-REST-API-Key' : kParseRestApiKey,
'Content-Type' : 'application/json',
},
body: json.encode(data.toJson())
);
return response;
}
//DELETE
static Future deleteData(String objectId) async{
String apiUrl = _baseUrl + "Data/$objectId";
Response response = await delete(apiUrl, headers: {
'X-Parse-Application-Id' : kParseApplicationId,
'X-Parse-REST-API-Key' : kParseRestApiKey,
});
return response;
}
}
【问题讨论】:
标签: android-studio flutter dart parse-server