cph-cachet / flutter-plugins

A collection of Flutter plugins developed by CACHET
541 stars 650 forks source link

[carp_background_location ^1.0.2+1] Package possibly affecting StreamBuilder with Firestore #234

Closed alekseifm closed 3 years ago

alekseifm commented 3 years ago

Device / Emulator and OS

Please complete the following information for each phone and/or emulator you're experiencing this bug on:

Describe the bug

I'm having a weird behaviour using StreamBuilder with Firestore after using carp_background_location. All collections from Firestore are streamed correctly at beginning of the run, but when I uses the background_location, all the StreamBuilders stop working (returns null for the snapshot data). Normal behaviour just comes back when I close the app and run main.dart again.

To Reproduce

  1. StreamBuilder works for all collections from Firestore on the app
  2. Use carp_background_location like on the example/lib/main.dart on a specific screen
  3. All streams return null on snapshot.data on the app

Expected behavior

  1. Stream any collection from Firestore using StreamBuilder
  2. Use carp_background_location
  3. Streams are working (with updated information for the collections that I saved data from carp_background_location. Data are saving correctly to the Firestore)

Flutter doctor

[✓] Flutter (Channel stable, 1.22.4, on Mac OS X 10.15.7 19H2 darwin-x64, locale en-BR)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 12.1)
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.49.1)

I am relative new to Flutter, don't know if I am losing something obvious here. and thanks for the amazing package!

alekseifm commented 3 years ago

I still couldn't solve it, here are the codes to reproduce the behaviour. I tried to simplify with some mocked data, but I believe that any integration with Firestore with Streambuilder would return null after listening to location data. If anyone has any idea of what could be causing this please share here, I am really stuck since no error or warn is shown. Thanks in advance!

Streambuilder with Firestore screen (MainApp) + Location stream widget (LocationStreamWidget) ``` import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:provider/provider.dart'; import 'begin_activity.dart'; class MainApp extends StatefulWidget { @override _MainAppState createState() => _MainAppState(); } class _MainAppState extends State { final _firestore = FirebaseFirestore.instance; @override Widget build(BuildContext context) { void moveToPage() async { await Navigator.push( context, MaterialPageRoute(builder: (context) => LocationStreamWidget()), ); } return Scaffold( backgroundColor: Colors.grey, floatingActionButton: FloatingActionButton( mini: true, onPressed: () { moveToPage(); }, child: Icon( Icons.add, color: Colors.white, size: 30, ), backgroundColor: Colors.blue, ), body: Stack( children: [ Padding( padding: EdgeInsets.fromLTRB(10, 0, 10, 10), child: StreamBuilder( stream: _firestore .collection('users') .doc('user_id') .collection('corridas') .orderBy('date', descending: true) .snapshots(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data == null) { return Center( child: SpinKitCubeGrid( size: 51.0, color: Colors.blue,)); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (snapshot.data.docs.isEmpty) { return Container( height: MediaQuery.of(context).size.height * 0.55, child: Padding( padding: const EdgeInsets.fromLTRB(8.0, 50.0, 8.0, 0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.directions_run, size: 50, ), Text( Strings.noTracks, style: TextParameters.noActivities, textAlign: TextAlign.center, ), ], ), )); } else if (snapshot.hasData) { return Column( children: [ Expanded( child: ListView.builder( itemCount: snapshot.data.docs.length, itemBuilder: (context, index) { final docAux = snapshot.data.docs; return Container( child: Column( children: [ Text(_printDate(docAux[index]['date']) .toString()), Text( docAux[index]['distance'].toStringAsFixed(2), ), ], )); }, ), ), ], ); } else { return Center( child: SpinKitCubeGrid( size: 51.0, color: Colors.blue,)); } }, ), ), ], ), ); } } String _printTime(int timeInMilliseconds) { String twoDigits(int n) { if (n >= 10) return "$n"; return "0$n"; } Duration duration = Duration(milliseconds: timeInMilliseconds); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); if (duration.inHours > 0) return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; else return "$twoDigitMinutes:$twoDigitSeconds"; } String _printDate(Timestamp timestamp) { var formattedAux = timestamp.toDate(); var day = formattedAux.day.toString().padLeft(2, '0'); var month = formattedAux.month.toString().padLeft(2, '0'); var year = formattedAux.year; return '$year' + '-' + '$month' + '-' + '$day'; } ``` ``` import 'dart:async'; import 'package:carp_background_location/carp_background_location.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:provider/provider.dart'; import 'package:timer_builder/timer_builder.dart'; class LocationStreamWidget extends StatefulWidget { @override State createState() => LocationStreamState(); } enum LocationStatus { UNKNOWN, RUNNING, STOPPED } class LocationStreamState extends State { final globalKey = GlobalKey(); LocationManager locationManager = LocationManager.instance; Stream dtoStream; StreamSubscription dtoSubscription; LocationStatus _status = LocationStatus.UNKNOWN; // Definitions List _allPositions; List _positions = []; double totalDistance; double intermediaryDist; double distAux; double distLatLong; int totalTime; int intermediaryTime; int timeAux; List _allPositionsInter; List _positionsInter = []; final _firestore = FirebaseFirestore.instance; @override void initState() { super.initState(); locationManager.interval = 1; locationManager.distanceFilter = 0; dtoStream = locationManager.dtoStream; totalDistance = 0.0; intermediaryDist = 0.0; totalTime = 0; intermediaryTime = 0; _positions.clear(); } void onData(LocationDto dto) { setState(() { if (_status == LocationStatus.UNKNOWN) { _status = LocationStatus.RUNNING; } _positions.add(dto); _allPositions = _positions; print(_positions.length); _positionsInter.add(dto); _allPositionsInter = _positionsInter; final firstTime = DateTime.fromMillisecondsSinceEpoch( _allPositionsInter.first.time ~/ 1) .toLocal(); final lastTime = DateTime.fromMillisecondsSinceEpoch(_allPositionsInter.last.time ~/ 1) .toLocal(); totalTime = intermediaryTime + lastTime.difference(firstTime).inMilliseconds; print(totalTime); }); } void start() async { if (dtoSubscription != null) { dtoSubscription.resume(); } dtoSubscription = dtoStream.listen(onData); await locationManager.start(); setState(() { _status = LocationStatus.RUNNING; }); } void pause() async { setState(() { intermediaryDist = totalDistance; intermediaryTime = totalTime; _positionsInter.clear(); _allPositionsInter.clear(); _status = LocationStatus.STOPPED; }); dtoSubscription.cancel(); await locationManager.stop(); } String _printTime(int timeInMilliseconds) { String twoDigits(int n) { if (n >= 10) return "$n"; return "0$n"; } Duration duration = Duration(milliseconds: timeInMilliseconds); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); if (duration.inHours > 0) return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; else return "$twoDigitMinutes:$twoDigitSeconds"; } @override void dispose() { if (dtoSubscription != null) { dtoSubscription.cancel(); dtoSubscription = null; _allPositions.clear(); _allPositionsInter.clear(); totalDistance = 0; intermediaryDist = 0.0; distAux = 0.0; _status = LocationStatus.STOPPED; totalTime = 0; intermediaryTime = 0; locationManager.start(); if (_positions != null) { _positions.clear(); _positionsInter.clear(); _allPositionsInter.clear(); locationManager.start(); } } super.dispose(); } @override Widget build(BuildContext context) { return TimerBuilder.periodic(Duration(milliseconds: 500), builder: (context) { return Scaffold( key: globalKey, body: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Expanded( child: Container( child: FutureBuilder( future: locationManager.checkIfPermissionGranted(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } if (snapshot.data == false) { return Text('No gps'); } return Container( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Text( '${totalTime != null ? _printTime(totalTime) : '0'}', ), _getButtons(user), ], ), ), ); }), ), ), ], ), ); }); } Widget _getButtons(UserClasse user) { final _currentDate = _positions?.isNotEmpty ?? false ? (DateTime.fromMillisecondsSinceEpoch(_positions[0].time ~/ 1) .toLocal()) : 0; if (dtoSubscription == null) return Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: RaisedButton( child: Text('Begin'), color: Colors.blue, onPressed: start), ), ], ), ); else if (_status == LocationStatus.RUNNING) return Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: RaisedButton( child: Text('Pause'), color: Colors.blue, onPressed: pause), ), ], ), ); else if (_status == LocationStatus.STOPPED) { return Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: RaisedButton( child: Text('Back'), color: Colors.blue, onPressed: start), ), SizedBox( width: 15, ), Expanded( child: RaisedButton( child: Text('Stop'), color: Colors.blue, onPressed: () {}, onLongPress: () async { _firestore .collection('users') .doc('user_id') .collection('corridas') .add({ 'date': _currentDate, 'distance': 41.7, 'pace': 1244, 'average_speed': 12, 'runtime': 14134, 'max_speed': 41, 'max_altitude': 13, 'delta_altitude': 13, 'lat_first': -23.213, 'long_first': -23.213, 'polylines': 'feaf', 'markers': [1, 2, 3], }); Navigator.of(context).pop(); }, ), ), ], ), ); } else return Padding( padding: EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: RaisedButton( child: Text('Start'), color: Colors.blue, onPressed: () {}), ), ], ), ); } } ```
alekseifm commented 3 years ago

I believe what is causing this behaviour is a depended package background_locator. The same issue is related here https://github.com/rekab-app/background_locator/issues/167.

alekseifm commented 3 years ago

Solved by: https://github.com/FirebaseExtended/flutterfire/issues/4108#issuecomment-738485098