LiveQuery does not work, if there is ParseConnectivityProvider provided

434

Problem solved!
My problem was:import 'package:parse_server_sdk/parse_server_sdk.dart';
It needs to be replaced by:import 'package:parse_server_sdk_flutter/parse_server_sdk.dart'; and in pubspec.yaml it's necessary to use only the correspondent dependency:
parse_server_sdk_flutter: ^2.0.1

Share:
434
Lucas Studart
Author by

Lucas Studart

Updated on December 17, 2022

Comments

  • Lucas Studart
    Lucas Studart over 1 year

    I'm working on a Flutter app, that consists of a employee scheduler with a Parse Server background.The app works as the following:

    1)User opens the app;
    2)The app shows a list of cards of available employees (if there is some in the server database)
    3)The user is able to edit, confirm or delete the employee availability.

    The app is working. It means that I'm able to CRUD data also from my Parse Server as from my app screen.

    The goal: Every data change in the database needs to refresh (realtime) the user screen, removing, deleting or updating the (changed) data itself.

    My idea: use parse_server_sdk: ^2.0.1 as a dependency (pubspec.yaml) and implement Live Queries (as the github docs here)

    The Problem: after compilation, despite my AndroidStudio don't crashes visually speaking, nor my application itself, it shows me (logs) that "LiveQuery does not work, if there is ParseConnectivityProvider provided".

    Without problems(main.dart):

    void main() async{
    
      runApp(MyApp());
    }
    

    Causing problems:

    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());
    }
    

    I didn't found any solution googleing.Any at all. After many research, I've got (going deep in the libraries) that Parse.Initialize(...) have connectivityProvider as one of it's parameters, but I didn't fill that parameter.

    Also, the problems starts exactly when I've inserted the the line:

    final LiveQuery liveQuery = LiveQuery();
    

    Could someone help me to solve this issue?Thanks in advance

    In the following, I'm showing all my code(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;
      }
    }