Background service issue : App crush when using geolocator - ios

I'm working on a fitness/health flutter app project.
My app work just works fine on android, but the app crashes and stops working immediately in ios when I'm invoking location service.
I have a button inside a map view page to start counting steps and time of the walking sessions.
My button's code
RaisedButton(
textColor: Colors.white,
color: checkRun == false
? Settings.mainColor()
: Colors.red,
child: Container(
padding: EdgeInsets.all(15),
child: checkRun == false
? Text(allTranslations.text("startNow"))
: Text(allTranslations.text("endNow"))),
onPressed: () async {
rightButtonPressed();
if (checkRun == false) {
getLocation();
} else if (checkRun == true) {
setState(() {
checkRun = false;
});
try {
FormData formdata = new FormData();
// get user token
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
Map<String, dynamic> authUser = jsonDecode(
sharedPreferences
.getString("authUser"));
dio.options.headers = {
"Authorization":
"Bearer ${authUser['authToken']}",
};
formdata.add("startLongitude",
points.first.longitude);
formdata.add(
"endLongitude", points.last.longitude);
formdata.add(
"startLatitude", points.first.latitude);
formdata.add(
"endLatitude", points.last.latitude);
formdata.add("date", DateTime.now());
meter = distance.as(
lm.LengthUnit.Meter,
lm.LatLng(points.first.latitude,
points.first.longitude),
lm.LatLng(points.last.latitude,
points.last.longitude));
setState(() {});
print(meter);
formdata.add("distance", meter.toInt());
formdata.add("steps", _polylineIdCounter);
formdata.add("calories", (_polylineIdCounter*0.0512).toInt());
response = await dio.post(
"http://104.248.168.117/api/mapInformation",
data: formdata);
if (response.statusCode != 200 &&
response.statusCode != 201) {
return false;
} else {
print('success -->');
print('Response = ${response.data}');
return true;
}
} on DioError catch (e) {
return false;
}
}
// return true;
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)))
getLoaction code :
getLocation() {
setState(() {
checkRun = true;
});
print('CheckRun = > $checkRun');
// Fired whenever a location is recorded
bg.BackgroundGeolocation.onLocation((bg.Location location) {
print('[location] - $location');
print('<--------- start onLocation -----------> ');
print(location.coords.latitude);
print(location.coords.longitude);
print('<--------- End onLocation -----------> ');
if (checkRun == true) {
setState(() {
points.add(_createLatLng(
location.coords.latitude, location.coords.longitude));
print('Points=> $points');
_add();
});
} else if (checkRun == false) {
setState(() {
points.clear();
});
}
});
// Fired whenever the plugin changes motion-state (stationary->moving and vice-versa)
bg.BackgroundGeolocation.onMotionChange((bg.Location location) {
print('[motionchange] - $location');
print('<--------- Locaiton onMotionChange -----------> ');
updatelat=location.coords.latitude;
updatelong=location.coords.longitude;
setState(() {
});
print(location.coords.latitude);
print(location.coords.longitude);
print('<--------- / Locaiton onMotionChange -----------> ');
});
// Fired whenever the state of location-services changes. Always fired at boot
bg.BackgroundGeolocation.onProviderChange((bg.ProviderChangeEvent event) {
});
////
// 2. Configure the plugin
//
bg.BackgroundGeolocation.ready(bg.Config(
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
distanceFilter: 10.0,
stopOnTerminate: false,
startOnBoot: true,
debug: false,
logLevel: bg.Config.LOG_LEVEL_INFO,
reset: true))
.then((bg.State state) {
if (!state.enabled) {
////
// 3. Start the plugin.
//
print('[ready] success: $state');
bg.BackgroundGeolocation.start();
}
});}
I'm using these packages:
flutter_background_geolocation: ^1.2.4
geolocator: ^5.0.1

Map view is crushing in the release version because flutter_background_geolocation is required you to buy a license to make it work in release versions, so it will work fine with you when you build the app in debug mode but it will crush on release mode without a license.

Related

Custom iOS notifications sound local notifications package Flutter

I'm using Firebase Cloud Messaging to send notifications to my flutter app, and I'm using the flutter_local_notifications package to handle them, but when I tried to change the default notification sound it worked for android but didn't work for iOS, keep in mind that I add the "sound.aiff" to the root of the native iOS project, can anyone show me what I'm missing here, and thanks in advance
class FCMFunctions {
static final FCMFunctions _singleton = new FCMFunctions._internal();
FCMFunctions._internal();
factory FCMFunctions() {
return _singleton;
}
late FirebaseMessaging messaging;
//************************************************************************************************************ */
/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;
/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
//************************************************************************************************************ */
Future initApp() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
messaging = FirebaseMessaging.instance;
if (!kIsWeb) {
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
importance: Importance.high,
sound: RawResourceAndroidNotificationSound('sound'),
playSound: true,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
//for IOS Foreground Notification
await messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void initializeNotifications() async {
var initializationSettingsAndroid =
const AndroidInitializationSettings('icon');
var initializationSettingsIOS = const IOSInitializationSettings();
//var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: onSelectNotification,
);
}
Future onSelectNotification(String? payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
}
navigatorKey.currentState!.pushNamed(Routes.blackCurrency,
arguments: false); //message.data['category']
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Future subscripeToTopics(String topic) async {
await messaging.subscribeToTopic(topic);
}
///Expire : https://firebase.google.com/docs/cloud-messaging/manage-tokens
Future<String?> getFCMToken() async {
final fcmToken = await messaging.getToken();
return fcmToken;
}
void tokenListener() {
messaging.onTokenRefresh.listen((fcmToken) {
print("FCM Token dinlemede");
// TODO: If necessary send token to application server.
}).onError((err) {
print(err);
});
}
/// IOS
Future iosWebPermission() async {
if (Platform.isIOS || kIsWeb) {
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
}
}
///Foreground messages
///
///To handle messages while your application is in the foreground, listen to the onMessage stream.
void foreGroundMessageListener() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(
'///////////////////////////// NOTIFICATIONS ARE COMMING /////////////////////////////');
print('${message.notification!.body} ');
print("Message data type : ${message.data.runtimeType}");
var bodyRaw = message.data['body'];
var body = List<dynamic>.from(jsonDecode(bodyRaw ?? '[]'));
print("body : $body");
String bodyMessage = '';
if (body.first['sale'] == null) {
bodyMessage = body.first['name'];
} else if (body.first['name'] == null) {
bodyMessage = body.first['sale'];
} else {
bodyMessage =
body.map((e) => e['name'] + '-' + e['sale']).join(', ').toString();
}
RemoteNotification? notification = RemoteNotification(
android: const AndroidNotification(
smallIcon: 'assets/imgs/logo.png',
priority: AndroidNotificationPriority.highPriority,
visibility: AndroidNotificationVisibility.public,
),
apple: const AppleNotification(
sound: AppleNotificationSound(
name: 'sound',
volume: 1.0,
),
),
title: message.data['title'],
body: bodyMessage,
);
// message.notification;
AndroidNotification? android =
notification.android; //message.notification?.android;
if (notification != null && android != null && !kIsWeb) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
importance: Importance.max,
priority: Priority.max,
ticker: 'ticker',
sound: const RawResourceAndroidNotificationSound('sound'),
// icon: "#mipmap/icon",
playSound: true,
styleInformation: const DefaultStyleInformation(true, true),
),
iOS: const IOSNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
sound: 'sound',
),
),
);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('A new onMessageOpenedApp event was published!');
navigatorKey.currentState!.pushNamed(Routes.blackCurrency,
arguments: false); //message.data['category']
// Navigator.pushNamed(
// context,
// '/message',
// arguments: MessageArguments(message, true),
// );
});
}
}
final fcmFunctions = FCMFunctions();
Your server administrator will send you the sound name in notification payload which you added in iOS folder. Payload will look like this
{
aps =
{
alert = "notification message";
sound = "example.caf";
};
}
more information check this reference link
49
49
custom sound file for your app, follow this apple documentation.

'Location enable permission' alert disappear after few second(3 to 4 second) in react-native

I am using 'react-native-geolocation-service' library for enabling the location for the app, if location is disabled. So if location is disabled then permission alert is working fine in android but In IOS it is appear for few second like 2 or 3 second, after that it will close. Below is the sample of method.
static hasLocationPermissionIOS = async () => {
const status = await Geolocation.requestAuthorization('always');
if (status === 'granted') {
return 'GRANTED';
}
if (status === 'denied') {
return 'DENIED';
}
if (status === 'disabled') {
return 'DISABLED';
}
};
static hasLocationPermission = async () => {
if (Platform.OS === 'ios') {
Geolocation.requestAuthorization('whenInUse');
const hasPermission = await this.hasLocationPermissionIOS();
return hasPermission;
}
if (Platform.OS === 'android') {
const hasPermission = await this.hasLocationPermissionAndroid();
return hasPermission;
}
return false;
};
static hasLocationPermission = async () => {
if (Platform.OS === 'ios') {
Geolocation.requestAuthorization('whenInUse');
const hasPermission = await this.hasLocationPermissionIOS();
return hasPermission;
}
if (Platform.OS === 'android') {
const hasPermission = await this.hasLocationPermissionAndroid();
return hasPermission;
}
return false;
};
static getLocation = async () => {
const hasLocationPermission = await this.hasLocationPermission();
if (!hasLocationPermission) {
return;
}
return new Promise((resolve, reject = (error) => {}) => {
Geolocation.getCurrentPosition((position)=> {
resolve(position);
}, (error)=>{
resolve(error);
}, {
accuracy: {
android: 'high',
ios: 'best',
},
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000,
distanceFilter: 0,
forceRequestLocation: true,
showLocationDialog: true,
});
});
};
I referred the link but not able to find solution,
https://github.com/douglasjunior/react-native-get-location/issues/18
Thanks in advance!!!

Background Actions On ios React native

import BackgroundService from 'react-native-background-actions';
const veryIntensiveTask = async (taskDataArguments) => {
const { delay } = taskDataArguments;
await new Promise( async (resolve) => {
for (let i = 0; BackgroundService.isRunning(); i++) {
console.log(i);
await sleep(delay);
}
});
};
const options = {
taskName: 'Example',
taskTitle: 'ExampleTask title',
taskDesc: 'ExampleTask description',
taskIcon: {
name: 'ic_launcher',
type: 'mipmap',
},
color: '#ff00ff',
linkingURI: 'yourSchemeHere://chat/jane', // See Deep Linking for more info
parameters: {
delay: 1000,
},
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({taskDesc: 'New ExampleTask description'});
await BackgroundService.stop();
Using this code and is working fine on android but not working on IOS also integrated as documentation. But it is not showing notification on IOS.

WebRTC - how to switch between getUserMedia and getDisplayMedia tracks inside RTCPeerConnection

I'm trying to develop an app where users can can video call to each other and share their screens using WebRTC technology. I have succeed with either video call or screen sharing app and now I'm trying to make it to be able to switch between getUserMedia and getDisplayMedia on button click during a call inside the same RTCPeerConnection but it doesn't work.
This is how I thought it could work:
function onLogin(success) {
var configuration = { offerToReceiveAudio: true, offerToReceiveVideo: true, "iceServers" : [ { "url" : "stun:stun.1.google.com:19302" } ] };
myConnection = window.RTCPeerConnection ? new RTCPeerConnection(configuration, { optional: [] }) : new RTCPeerConnection(configuration, { optional: [] });
myConnection.onicecandidate = function (event) {
console.log("onicecandidate");
if (event.candidate) send({ type: "candidate", candidate: event.candidate });
};
myConnection.ontrack=function(e){
try{remoteVideo.src = window.webkitURL?window.webkitURL.createObjectURL(e.streams[0]):window.URL.createObjectURL(e.streams[0])}
catch(err){remoteVideo.srcObject=e.streams[0]}
}
myConnection.ondatachannel=openDataChannel
openDataChannel();
startAVStream();
//startSStream()
};
function startAVStream(enable){
if(sStream)sStream.getTracks().forEach( function (track) {
try{myConnection.removeTrack( track, sStream );}
catch(e){}
} );
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(s => {
if(!avStream){
avStream = s;
avStream.getTracks().forEach( function (track) {
myConnection.addTrack( track, avStream );
} );
}
}, function (error) { console.log(error); });
}
function startSStream(enable){
if(avStream)avStream.getTracks().forEach( function (track) {
try{myConnection.removeTrack( track, avStream );}
catch(e){}
} );
navigator.mediaDevices.getDisplayMedia({ video: true }).then(s => {
if(!sStream){
sStream = s;
sStream.getTracks().forEach( function (track) {
myConnection.addTrack( track, sStream );
} );
}
}, function (error) { console.log(error); });
}
Can anyone tell me how I can switch between tracks inside the same RTCPeerConnection or should I create 2 separate RTCPeerConnection - one for video/audio streaming and another for screen sharing?
Any help appreciated! Thanks!
You could use RTCRtpSender.replaceTrack to splice the screen capture track. This doesn't require renegotiation, and therefore has very low latency.
let newstream = navigator.mediaDevices.getDisplayMedia({});
let newtrack = newstream.getTracks()[1];
if(newtrack.kind !== 'video')
throw new Error('Eek!?');
pc.getSenders().forEach(async s => {
if(s.track && s.track.kind === 'video')
await s.replaceTrack(newtrack);
});
The test for s.track not being null deals with the case where you previously called replaceTrack(..., null).
shareScreen = () =>{
const success = (stream) => {
window.localStream = stream
// this.localVideoref.current.srcObject = stream
// localStream.replaceStream(stream);
this.setState({
localStream: stream
})
Object.values(this.state.peerConnections).forEach(pc => {
pc.getSenders().forEach(async s => {
console.log("s.track ",s.track);
if(s.track && s.track.kind === 'video'){
stream.getTracks().forEach(track => {
// pc.addTrack(track, this.state.localStream)
s.replaceTrack(track);
});
}
});
});
}
const failure = (e) => {
console.log('getUserMedia Error: ', e)
}
navigator.mediaDevices.getDisplayMedia({ cursor: true }).then(success).catch(failure)}

Flutter Widget Test: StreamBuilder snapshot has connectionState = waiting with a non-empty Stream

I'm trying to write a widget test for a widget that uses StreamBuilder. In the builder, I return a CircularProgressIndicator if snapshot.hasData is false, otherwise I return a ListView of widgets.
In my test I create a StreamController and add an element to it. When I run the test, I would expect to see snapshot.hasData = true, but instead it's false and I can see that the connectionState is waiting. So my test fails.
Somehow it seems that the first element is not pulled out of the stream, and the connection remains in waiting state. I'm not sure what I'm doing wrong.
Here's my widget test:
testWidgets('Job item pressed - shows edit job page',
(WidgetTester tester) async {
StreamController<List<Job>> controller =
StreamController<List<Job>>.broadcast(sync: true);
Job job = Job(id: '0', createdAt: 0, jobName: 'Dart');
controller.add([job]);
final page = JobsPage(
jobsStream: controller.stream,
);
MockRouter mockRouter = MockRouter();
await tester.pumpWidget(makeTestableWidget(
child: page,
auth: MockAuth(),
database: MockDatabase(),
router: mockRouter,
));
Finder waiting = find.byType(CircularProgressIndicator);
expect(waiting, findsNothing);
Finder placeholder = find.byType(PlaceholderContent);
expect(placeholder, findsNothing);
Finder item = find.byKey(Key('jobListItem-${job.id}'));
expect(item, findsOneWidget);
await controller.close();
});
And here is my widget code:
Widget _buildContent(BuildContext context) {
return StreamBuilder<List<Job>>(
stream: jobsStream,
builder: (context, snapshot) {
return ListItemsBuilder(
snapshot: snapshot,
itemBuilder: (BuildContext context, Job job) {
return JobListItem(
key: Key('jobListItem-${job.id}'),
title: job.jobName, // TODO: This be null?
onTap: () => _select(context, job),
);
},
);
},
);
}
And the ListItemsBuilder:
typedef Widget ItemWidgetBuilder<T>(BuildContext context, T item);
class ListItemsBuilder<T> extends StatelessWidget {
ListItemsBuilder({this.snapshot, this.itemBuilder});
final AsyncSnapshot<List<T>> snapshot;
final ItemWidgetBuilder<T> itemBuilder;
#override
Widget build(BuildContext context) {
// prints "waiting"
print('${snapshot.connectionState.toString()}');
if (snapshot.hasData) {
final items = snapshot.data;
if (items.length > 0) {
return _buildList(items);
} else {
return PlaceholderContent();
}
} else if (snapshot.error != null) {
print('${snapshot.error}');
return PlaceholderContent(
title: 'Something went wrong',
message: 'Can\'t load entries right now',
);
} else {
return Center(child: CircularProgressIndicator());
}
}
Widget _buildList(List<T> items) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return itemBuilder(context, items[index]);
},
);
}
}
I tried using a StreamController.broadcast(sync: true) instead of a simple StreamController, but it didn't make a difference.
Also any additional pump() and pumpAndSettle() calls don't make a difference.
Any ideas?
Solution
Use await tester.pump(Duration.zero);
Full test code:
testWidgets('Job item pressed - shows edit job page',
(WidgetTester tester) async {
StreamController<List<Job>> controller = StreamController<List<Job>>();
Job job = Job(id: '0', createdAt: 0, jobName: 'Dart');
controller.add([job]);
final page = JobsPage(
jobsStream: controller.stream,
);
MockRouter mockRouter = MockRouter();
await tester.pumpWidget(makeTestableWidget(
child: page,
auth: MockAuth(),
database: MockDatabase(),
router: mockRouter,
));
// this will cause the stream to emit the first event
await tester.pump(Duration.zero);
Finder waiting = find.byType(CircularProgressIndicator);
expect(waiting, findsNothing);
Finder placeholder = find.byType(PlaceholderContent);
expect(placeholder, findsNothing);
Finder item = find.byKey(Key('jobListItem-${job.id}'));
expect(item, findsOneWidget);
await controller.close();
});

Resources