Flutter Cancel Geolocator Listener - dart

I am using Geolocator library to detect the location of the user. It works well however, I would like the listener to stop receiving any updates when the particular screen requesting the location updates is closed. I have no idea how to achieve this. Here is my code
class _CheckLoactionScreenState extends State<CheckLocationScreen>{
String _geoHash = "No Geo Hash";
String _placeId = "No Place Detected";
String _coordinates = "";
var geolocator = Geolocator();
var locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 10);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: _body(),
);
}
_body(){
return Center(
child: Text(_geoHash),
),
);
}
_getLocation(){
geolocator.getPositionStream(locationOptions).listen(
(Position position) {
//print("${position.toString()}");
var newGeoHash = Geohash.encode(position.latitude, position.longitude).substring(0,8);
if(newGeoHash != _geoHash){
setState((){
_geoHash = newGeoHash;
});
}
_coordinates = position.toString();
print(_geoHash);
});
}
#override
void initState() {
super.initState();
_getLocation();
}
#override
void dispose() {
print("**** dispose");
geolocator.getPositionStream(null).listen(null);
super.dispose();
}
}
I attempt to cancel the listener in the dispose method but the listener still persists.

StreamSubscription _getPositionSubscription;
_getLocation(){
_getPositionSubscription = geolocator.getPositionStream(locationOptions).listen(
...
}
#override
void dispose() {
_getPositionSubscription?.cancel();
super.dispose();
}

Related

StateError (Bad state: No element) on IOS only

This error does not occur on Android or web but only on IOS. It seem very trivial but I can't figure out what's wrong.
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dart:convert';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScanQrPage extends StatefulWidget {
#override
_ScanQrPageState createState() => _ScanQrPageState();
}
class _ScanQrPageState extends State<ScanQrPage> {
final qrKey = GlobalKey();
late QRViewController qrViewController;
late Barcode barcode;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
Navigator.of(context).pop("");
return new Future(() => true);
},
child: Scaffold(
body: Stack(
children: [
buildQrView(context),
],
),
),
);
}
Widget buildQrView(BuildContext context) {
return QRView(
onQRViewCreated: onQRViewCreated,
key: qrKey,
overlay: QrScannerOverlayShape(
cutOutSize: MediaQuery.of(context).size.width * 0.8),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
qrViewController.dispose();
super.dispose();
}
void onQRViewCreated(QRViewController qrViewController) {
setState(() {
this.qrViewController = qrViewController;
});
qrViewController.scannedDataStream.listen((event) {
setState(() {
this.barcode = event;
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
String rawData = event.code;
Uri data = Uri.dataFromString(rawData);
String para1 = data.queryParameters["buy"] ??
""; //get parameter with attribute "para1"
Codec<String, String> stringToBase64 = utf8.fuse(base64);
if (para1 != "") {
placer = stringToBase64.decode(para1);
}
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Navigator.pop(context, placer);
});
});
});
}
}
I've tried all the solutions with the same error found on stackoverflow (addPostFrameCallback and Future(Duration.zero)) but none of them are exactly the same and does not seem to fix my problem.
I don't think I have having the same issue as any of the other questions.
The exception is happening on the Navigator.pop(context, placer);
Does anyone have any idea how to overcome this?
Why does this only happen on IOS?

Remove debug banner from this specific main.dart

Not sure where to add the: debugShowCheckedModeBanner: false, I'm trying to build for ios with xcode.
Here is the current main.dart:
Not sure what I need to change in order to get this to build. I know it has something to do with MaterialApp but I can't figure out the placement.
`import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:kittscoaching/src/app.dart';
import 'package:kittscoaching/src/resources/theme.dart';
void main() {
runApp(App());
}
class App extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if(_error) {
//TODO:
//return SomethingWentWrong();
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
// TODO:
return Container(
decoration: BoxDecoration(color: KittsTheme.primary),
child: Center(
child: Directionality(
textDirection: TextDirection.ltr,
child: Text('Loading...')
)
)
);
}
return MyApp();
}
}`
There isn't a MaterialApp in the code that you're showing. Find the MaterialApp, if there is one, and apply the property there.
Or run your app in release mode: flutter run --release
Or open dev tools and click off the debug banner from there.
Hope this will help you
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:kittscoaching/src/app.dart';
import 'package:kittscoaching/src/resources/theme.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: App(),
debugShowCheckedModeBanner: false,
);
}
}
class App extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if(_error) {
//TODO:
//return SomethingWentWrong();
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
// TODO:
return Container(
decoration: BoxDecoration(color: KittsTheme.primary),
child: Center(
child: Directionality(
textDirection: TextDirection.ltr,
child: Text('Loading...')
)
)
);
}
return MyApp();
}
}
Simply you have to specify it in MaterialApp widget. Find the sample code below.
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
),
),

Flutter: Asking Location permission pauses all app execution, how to let it run asynchronously

When my app requests location permission the entire app stops until the dialog is complete (e.g. if the permission dialog pops up during a page transition the transition will freeze mid transition until the dialog is resolved).
Literally it causes execution to pause.
Using: flutter_riverpod, location.
The offending code:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/all.dart';
import 'package:location/location.dart';
class MapApiBloc extends StateNotifier<MapApiState> {
// Instantiating a location service
static Location _location = Location();
// This will subscribe to the user's location
StreamSubscription<LocationData> _streamSubscription;
// Permissions stuff
bool _serviceEnabled;
PermissionStatus _permissionGranted;
// Initial (empty) state
static const MapApiState _initialState = MapApiState(userLocation: null);
MapApiBloc() : super(_initialState) {
init();
}
// This runs when you initialize the class
init() async {
// Checks if user toggled on location service
_serviceEnabled = await _location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await _location.requestService();
if (!_serviceEnabled) {
return;
}
}
// Asks for permission
_permissionGranted = await _location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await _location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return;
}
}
// Starts the subscription
_streamSubscription = _location.onLocationChanged.listen((event) {
state = MapApiState(userLocation: event);
});
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
}
class MapApiState {
final LocationData userLocation;
const MapApiState({#required this.userLocation});
}
final mapApiProvider = StateNotifierProvider<MapApiBloc>((ref) {
return MapApiBloc();
});
UI Code:
class ViewNearbyMapPage extends StatefulWidget {
#override
_ViewNearbyMapPageState createState() => _ViewNearbyMapPageState();
}
class _ViewNearbyMapPageState extends State<ViewNearbyMapPage> {
Completer<GoogleMapController> _controller = Completer();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Search Sellers")),
body: Consumer(
builder: (context, watch, child) {
var location = watch(mapApiProvider.state);
if (location.userLocation?.latitude == null) {
return Center(child: CircularProgressIndicator());
}
CameraPosition _myPosition = CameraPosition(
target: LatLng(location.userLocation.latitude,
location.userLocation.longitude),
zoom: 14.4746,
);
return GoogleMap(
initialCameraPosition: _myPosition,
onMapCreated: (controller) {
_controller.complete(controller);
},
);
},
),
);
}
}

How to continuously check internet connect or not on Flutter?

I use this code for check internet. and I wrap this function into initState also. Snack bar always displays when internet not available. But after connecting to the internet, the snack bar is not disappeared. I can't use connectivity plugin because they said on Android, the plugin does not guarantee connection to the Internet.
checking1(TextEditingController usernameController, BuildContext context,
String _url, GlobalKey<ScaffoldState> _scaffoldKey) async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
usernameController.text == '' ?
showDialog(...some code...) :
usernameValidation(usernameController.text, context, _url);
}
}
on SocketException
catch (_) {
_showSnackBar(_scaffoldKey);
}
}
Full example demonstrating a listener of the internet connectivity and its source.
Original post
import 'dart:async';
import 'dart:io';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Map _source = {ConnectivityResult.none: false};
MyConnectivity _connectivity = MyConnectivity.instance;
#override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
setState(() => _source = source);
});
}
#override
Widget build(BuildContext context) {
String status = "Offline";
switch (_source.keys.toList()[0]) {
case ConnectivityResult.none:
status = "Offline";
break;
case ConnectivityResult.mobile:
status = "Mobile: Online";
break;
case ConnectivityResult.wifi:
status = "WiFi: Online";
break;
case ConnectivityResult.ethernet:
status = "Ethernet: Online";
break;
}
return Scaffold(
appBar: AppBar(title: Text("Internet")),
body: Center(child: Text(status)),
);
}
#override
void dispose() {
_connectivity.disposeStream();
super.dispose();
}
}
class MyConnectivity {
MyConnectivity._internal();
static final MyConnectivity _instance = MyConnectivity._internal();
static MyConnectivity get instance => _instance;
Connectivity connectivity = Connectivity();
StreamController controller = StreamController.broadcast();
Stream get myStream => controller.stream;
void initialise() async {
ConnectivityResult result = await connectivity.checkConnectivity();
_checkStatus(result);
connectivity.onConnectivityChanged.listen((result) {
_checkStatus(result);
});
}
void _checkStatus(ConnectivityResult result) async {
bool isOnline = false;
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
isOnline = true;
} else {
isOnline = false;
}
} on SocketException catch (_) {
isOnline = false;
}
controller.sink.add({result: isOnline});
}
void disposeStream() => controller.close();
}
Another option also can be this package: https://pub.dartlang.org/packages/flutter_offline that deal with this issue really straightforward.
You need first to import the package 'package:flutter_offline/flutter_offline.dart';
After that you include the OfflineBuilder on Widget build(BuildContext context) and it will read all all stream changes from ConnectivityResult continuously.
Like the example on the link or like the following one
#override
Widget build(BuildContext context) {
return OfflineBuilder(
debounceDuration: Duration.zero,
connectivityBuilder: (
BuildContext context,
ConnectivityResult connectivity,
Widget child,
) {
if (connectivity == ConnectivityResult.none) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(child: Text('Please check your internet connection!')),
);
}
return child;
},
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text("Home")
),
body: new Column(
children: <Widget>[
new Container(
decoration: new BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTxtSearchBox(),
),
new Divider(height: 10.0),
new FloatingActionButton.extended(
icon: Icon(Icons.camera_alt),
),
new Container(
...
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
drawer: MenuDrawer(),
)
);
}
The connectivity package will do what you want. It has an onConnectivityChanged stream which you can subscribe to. This will notify your app when the connectivity state changes. But just because your device is connected to a network doesn't mean it can access your server and be connected. So a DNS lookup would be a good idea before then updating the internal state of your application.
https://pub.dartlang.org/documentation/connectivity/latest/connectivity/Connectivity-class.html
I find this to be reliable & more convincing :
Future<bool> connectivityChecker() async {
var connected = false;
print("Checking internet...");
try {
final result = await InternetAddress.lookup('google.com');
final result2 = await InternetAddress.lookup('facebook.com');
final result3 = await InternetAddress.lookup('microsoft.com');
if ((result.isNotEmpty && result[0].rawAddress.isNotEmpty) ||
(result2.isNotEmpty && result2[0].rawAddress.isNotEmpty) ||
(result3.isNotEmpty && result3[0].rawAddress.isNotEmpty)) {
print('connected..');
connected = true;
} else {
print("not connected from else..");
connected = false;
}
} on SocketException catch (_) {
print('not connected...');
connected = false;
}
return connected;
}
Based on the bool value of connected returned, I'd run a timer based loop to check for internet again & again till its connected. Open to any suggestions

The method nextPage() and previousPage() in PageController doesn't work sometimes

Here is my code:
I response key event in Android MainActivity, and use BasicMessageChannel to post key message:
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "scroll";
private static final String KEY_LEFT = "keyLeft";
private static final String KEY_RIGHT = "keyRight";
private BasicMessageChannel messageChannel;
private FlutterView flutterView;
private String[] getArgsFromIntent(Intent intent) {
// Before adding more entries to this list, consider that arbitrary
// Android applications can generate intents with extra data and that
// there are many security-sensitive args in the binary.
ArrayList<String> args = new ArrayList<String>();
if (intent.getBooleanExtra("trace-startup", false)) {
args.add("--trace-startup");
}
if (intent.getBooleanExtra("start-paused", false)) {
args.add("--start-paused");
}
if (intent.getBooleanExtra("enable-dart-profiling", false)) {
args.add("--enable-dart-profiling");
}
if (!args.isEmpty()) {
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
return null;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
flutterView = new FlutterView(this);
String[] args = getArgsFromIntent(getIntent());
FlutterMain.ensureInitializationComplete(getApplicationContext(), args);
flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null);
messageChannel = new BasicMessageChannel<>(flutterView, CHANNEL, StringCodec.INSTANCE);
}
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.d(TAG, "dispatchKeyEvent: ACTION_DOWN keyCode = " + event.getKeyCode());
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
messageChannel.send(KEY_LEFT);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
messageChannel.send(KEY_RIGHT);
return true;
default:
break;
}
}
return super.dispatchKeyEvent(event);
}
#Override
protected void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
super.onDestroy();
}
#Override
protected void onPause() {
super.onPause();
flutterView.onPause();
}
#Override
protected void onPostResume() {
super.onPostResume();
flutterView.onPostResume();
}
}
When Flutter receives the message, I call nextPage() and previousPage() to scroll PageView, it doesn't work. But I find if I call nextPage() and previousPage() in onTap() method of the GestureDetector, it works:
class _Page {
_Page({
this.imagePath,
});
final String imagePath;
}
final List<_Page> _allPages = <_Page>[
new _Page(imagePath: 'images/1.jpg'),
new _Page(imagePath: 'images/2.jpg'),
new _Page(imagePath: 'images/3.jpg'),
new _Page(imagePath: 'images/4.jpg'),
new _Page(imagePath: 'images/5.jpg'),
new _Page(imagePath: 'images/6.jpg'),
];
class ScrollablePageDemo extends StatefulWidget {
#override
_ScrollablePageDemoState createState() => new _ScrollablePageDemoState();
}
class _ScrollablePageDemoState extends State<ScrollablePageDemo>
with SingleTickerProviderStateMixin {
PageController _controller;
static const String _channel = 'scroll';
static const String _emptyMessage = '';
static const String KEY_LEFT = "keyLeft";
static const String KEY_RIGHT = "keyRight";
static const BasicMessageChannel<String> platform =
const BasicMessageChannel<String>(_channel, const StringCodec());
#override
void initState() {
super.initState();
_controller = new PageController(
initialPage: 0, keepPage: true, viewportFraction: 1.0);
platform.setMessageHandler(changePage);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
Future<String> changePage(String message) async {
setState(() {
print(message);
if (message == KEY_RIGHT) {
//here doesn't work
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab right: page = " + _controller.page.toString());
} else if (message == KEY_LEFT) {
//here doesn't work
_controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab left: page = " + _controller.page.toString());
}
});
return _emptyMessage;
}
PageView buildPageView() {
return new PageView(
controller: _controller,
children: _allPages.map((_Page page) {
return new Container(
key: new ObjectKey(page.imagePath),
padding: const EdgeInsets.all(12.0),
child: new Card(
child: new Image.asset(page.imagePath, fit: BoxFit.fill)));
}).toList(),
);
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print('Listen PageView');
//here works
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
},
child: new Scaffold(
appBar: new AppBar(
title: new Text('PageView'),
),
body: buildPageView()));
}
}
void main() {
runApp(new MaterialApp(
title: 'Flutter Study',
home: new ScrollablePageDemo(),
));
}
The problem is with setState method inside changePage. The setState invokes build method whenever the state set. Thus the page gets built again whenever setState called. You can just remove the setState from changePage method.
example:
Future<String> changePage(String message) async {
print(message);
if (message == KEY_RIGHT) {
_controller.nextPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab right: page = " + _controller.page.toString());
} else if (message == KEY_LEFT) {
_controller.previousPage(duration: kTabScrollDuration, curve: Curves.ease);
print("tab left: page = " + _controller.page.toString());
}
return _emptyMessage;
}
Hope that helped!

Resources