I'm attempting to create a xamarin forms application which will get location updates and feed them to an HTTP endpoint. I'm having a great deal of difficulty understanding how to go about running a service in the background so that I continue to receive location information regardless of if the application is open or not especially in face of the change in API level 26 https://developer.android.com/about/versions/oreo/background.html.
I was using a LocationCallback when the application was in the foreground and that seemed to work okay but I'm wondering if just waking up from time to time and looking at GetLastLocationAsync or if that information is only updated when something actively requests location information.
What's the best way to implement a background service which will feed device location to an endpoint regardless of if the application is in the foreground?
I achieved it by registering another service and subscribed to location updates from within it.
using Xamarin.Essentials;
public class Service
{
private IService service { get; }
public Service(IService service)
{
this.service = service;
}
public async Task StartListening()
{
if (CrossGeolocator.Current.IsListening)
return;
await CrossGeolocator.Current.StartListeningAsync(TimeSpan.FromSeconds(5), 10, true);
CrossGeolocator.Current.PositionChanged += PositionChanged;
CrossGeolocator.Current.PositionError += PositionError;
}
private void PositionChanged(object sender, PositionEventArgs e)
{
service.UpdateLocation(e.Position.Latitude, e.Position.Longitude);
}
private void PositionError(object sender, PositionErrorEventArgs e)
{
//Handle event here for errors
}
public async Task StopListening()
{
if (!CrossGeolocator.Current.IsListening)
return;
await CrossGeolocator.Current.StopListeningAsync();
CrossGeolocator.Current.PositionChanged -= PositionChanged;
CrossGeolocator.Current.PositionError -= PositionError;
}
}
}
Related
I want to set up a mobile application with flutter which also runs in the background. this application allows you to scan Bluetooth devices and listen to events to launch notification and/or start a ringtone.
I managed to do all this and it works very well with the flutter_blue plugin. But my problem is that the application has to keep running in the background.
I came here to seek help.
The app does exactly what this app does https://play.google.com/store/apps/details?id=com.antilost.app3&hl=fr&gl=US
There are 2 ways to do it.
All you have to do that is write a native code in JAVA/Kotlin for android and obc-c/swift for ios.
The best place to start with this is here
If you just follow the above link then you will be able to code MethodChannel and EventChannel, which will be useful to communicate between flutter and native code. So, If you are good at the native side then it won't be big deal for you.
// For example, if you want to start service in android
// we write
//rest of the activity code
onCreate(){
startBluetoothService();
}
startBluetoothService(){
//your code
}
//then, For the flutter
// Flutter side
MessageChannel msgChannel=MessageChannel("MyChannel");
msgChannel.invokeMethode("startBluetoothService");
// Native side
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "MyChannel";
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("startBluetoothService")) {
int response = startBluetoothService();
//then you can return the result based on the your code execution
if (response != -1) {
result.success(response);
} else {
result.error("UNAVAILABLE", "Error while starting service.", null);
}
} else {
result.notImplemented();
}
}
);
}
}
same as above you can write the code for the iOS side.
Second way is to write your own plugin for that you can take inspiration from alarm_manager or Background_location plugins.
I hope it helps you to solve the problem.
I have developed an android application where I need to fetch a list using rest API call and show on my application. The list gets updated frequently. I have written the following code using RxAndroid and retrofit to make the api call :
private void fetchAllData() {
disposable.add(Observable.interval(0,60,TimeUnit.SECONDS).
subscribeOn(Schedulers.io()).
flatMap(i -> apiService.getData(fetchActiveData)).
observeOn(AndroidSchedulers.mainThread()).
subscribeWith(new DisposableObserver<DataResponse>() {
#Override
public void onNext(DataResponse dataResponse) {
Log.i(TAG, "The rest api was called again");
List<Data> dataList = dataResponse.getData();
displayData(dataList,false);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "The exception is thrown :: " + e.getMessage());
displayData(null,true);
fetchAllData();
}
#Override
public void onComplete() {
}
})
);
}
Using above code , I am able to make api call every 60 seconds and update my list. If there is any new item which I am not displaying on the application , then for that i have the logic to show the notification.
To enhance my application , I need to make the api call when the application is closed and not running on background. Can someone please suggest me how do I achieve this with RxJava.
I am deploying my XF app at Android 4.4 and basing on this advise
I am trying to get a notification regarding established Wifi connection. I added CrossConnectivity.Current.ConnectivityChanged in my App.xaml.cs but this event seemed never happened when my application connected to a Wifi network. What did I do wrong? My code is the following:
public partial class App : Application {
public App (IWifiOperations mgr) {
InitializeComponent(); MainPage = new MainPage(mgr);
}
protected override void OnStart () {
CrossConnectivity.Current.ConnectivityChanged += Current_ConnectivityChanged;
}
private void Current_ConnectivityChanged(object sender, Plugin.Connectivity.Abstractions.ConnectivityChangedEventArgs e) {
var qq = "";
}
}
I have a project where a user can have multiple logins across multiple devices.
Now the user can subscribe to a particular topic on any device and the need is that the rest of the device logins should also do the same. Similar case is when one device unsubscribes, the rest should also follow suite.
In order to do this, I have made a Node under each user where all the subscriptions are maintained in the firebase database. I have a START_STICKY service which attaches a Firebase listener to this location and subs/unsubs from the topics when the changes occur. The code for the service is attached under the description.
In regular usage from observation, the service that i have does re-spawn due to the start sticky in case the system kills it. It will also explicitly respawn in case the user tampers with it using the developer options. The only cases which will cause it to completely cease are :
signout
data cleared
force stop
My questions are
how badly will keeping the listener attached affect the battery life. AFAIK Firebase has an exponential backoff when the web socket disconnects to prevent constant battery drain
Can the firebase listener just give up reconnecting if the connection is off for quite some time? If so, when is the backoff limit reached.
Is there a better way to ensure that a topic is subscribed and unsubscribed across multiple devices?
Is the service a good way to do this? can the following service be optimised? And yes it does need to run constantly.
Code
public class SubscriptionListenerService extends Service {
DatabaseReference userNodeSubscriptionRef;
ChildEventListener subscribedTopicsListener;
SharedPreferences sessionPref,subscribedTopicsPreference;
SharedPreferences.Editor subscribedtopicsprefeditor;
String userid;
boolean stoppedInternally = false;
SharedPreferences.OnSharedPreferenceChangeListener sessionPrefChangeListener;
#Nullable
#Override
public IBinder onBind(Intent intent) {
//do not need a binder over here
return null;
}
#Override
public void onCreate(){
super.onCreate();
Log.d("FragmentCreate","onCreate called inside service");
sessionPref = getSharedPreferences("SessionPref",0);
subscribedTopicsPreference=getSharedPreferences("subscribedTopicsPreference",0);
subscribedtopicsprefeditor=subscribedTopicsPreference.edit();
userid = sessionPref.getString("userid",null);
sessionPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d("FragmentCreate","The shared preference changed "+key);
stoppedInternally=true;
sessionPref.unregisterOnSharedPreferenceChangeListener(this);
if(userNodeSubscriptionRef!=null && subscribedTopicsListener!=null){
userNodeSubscriptionRef.removeEventListener(subscribedTopicsListener);
}
stopSelf();
}
};
sessionPref.registerOnSharedPreferenceChangeListener(sessionPrefChangeListener);
subscribedTopicsListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if(!(dataSnapshot.getValue() instanceof Boolean)){
Log.d("FragmentCreate","Please test subscriptions with a boolean value");
}else {
if ((Boolean) dataSnapshot.getValue()) {
//here we subscribe to the topic as the topic has a true value
Log.d("FragmentCreate", "Subscribing to topic " + dataSnapshot.getKey());
subscribedtopicsprefeditor.putBoolean(dataSnapshot.getKey(), true);
FirebaseMessaging.getInstance().subscribeToTopic(dataSnapshot.getKey());
} else {
//here we unsubscribed from the topic as the topic has a false value
Log.d("FragmentCreate", "Unsubscribing from topic " + dataSnapshot.getKey());
subscribedtopicsprefeditor.remove(dataSnapshot.getKey());
FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
}
subscribedtopicsprefeditor.commit();
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
//either an unsubscription will trigger this, or a re-subscription after an unsubscription
if(!(dataSnapshot.getValue() instanceof Boolean)){
Log.d("FragmentCreate","Please test subscriptions with a boolean value");
}else{
if((Boolean)dataSnapshot.getValue()){
Log.d("FragmentCreate","Subscribing to topic "+dataSnapshot.getKey());
subscribedtopicsprefeditor.putBoolean(dataSnapshot.getKey(),true);
FirebaseMessaging.getInstance().subscribeToTopic(dataSnapshot.getKey());
}else{
Log.d("FragmentCreate","Unsubscribing from topic "+dataSnapshot.getKey());
subscribedtopicsprefeditor.remove(dataSnapshot.getKey());
FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
}
subscribedtopicsprefeditor.commit();
}
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
//Log.d("FragmentCreate","Unubscribing from topic "+dataSnapshot.getKey());
//FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
//do nothing, this won't happen --- rather this isnt important
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("FragmentCreate","Failed to listen to subscriptions node");
}
};
if(userid!=null){
Log.d("FragmentCreate","Found user id in service "+userid);
userNodeSubscriptionRef = FirebaseDatabase.getInstance().getReference().child("Users").child(userid).child("subscriptions");
userNodeSubscriptionRef.addChildEventListener(subscribedTopicsListener);
userNodeSubscriptionRef.keepSynced(true);
}else{
Log.d("FragmentCreate","Couldn't find user id");
stoppedInternally=true;
stopSelf();
}
}
#Override
public int onStartCommand(Intent intent,int flags,int startId){
//don't need anything done over here
//The intent can have the following extras
//If the intent was started by the alarm manager ..... it will contain android.intent.extra.ALARM_COUNT
//If the intent was sent by the broadcast receiver listening for boot/update ... it will contain wakelockid
//If it was started from within the app .... it will contain no extras in the intent
//The following will not throw an exception if the intent does not have an wakelockid in extra
//As per android doc... the following method releases the wakelock if any specified inside the extra and returns true
//If no wakelockid is specified, it will return false;
if(intent!=null){
if(BootEventReceiver.completeWakefulIntent(intent)){
Log.d("FragmentCreate","Wakelock released");
}else{
Log.d("FragmentCreate","Wakelock not acquired in the first place");
}
}else{
Log.d("FragmentCreate","Intent started by regular app usage");
}
return START_STICKY;
}
#Override
public void onDestroy(){
if(userNodeSubscriptionRef!=null){
userNodeSubscriptionRef.keepSynced(false);
}
userNodeSubscriptionRef = null;
subscribedTopicsListener = null;
sessionPref = null;
subscribedTopicsPreference = null;
subscribedtopicsprefeditor = null;
userid = null;
sessionPrefChangeListener = null;
if(stoppedInternally){
Log.d("FragmentCreate","Service getting stopped due to no userid or due to logout or data clearance...do not restart auto.. it will launch when user logs in or signs up");
}else{
Log.d("FragmentCreate","Service getting killed by user explicitly from running services or by force stop ... attempt restart");
//well basically restart the service using an alarm manager ... restart after one minute
AlarmManager alarmManager = (AlarmManager) this.getSystemService(ALARM_SERVICE);
Intent restartServiceIntent = new Intent(this,SubscriptionListenerService.class);
restartServiceIntent.setPackage(this.getPackageName());
//context , uniqueid to identify the intent , actual intent , type of pending intent
PendingIntent pendingIntentToBeFired = PendingIntent.getService(this,1,restartServiceIntent,PendingIntent.FLAG_ONE_SHOT);
if(Build.VERSION.SDK_INT>=23){
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+600000,pendingIntentToBeFired);
}else{
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+600000,pendingIntentToBeFired);
}
}
super.onDestroy();
}
}
A service is not really necessary for what you're trying to do. There's no advantage to having a service, except that it may keep your app's process alive longer than it would without the service started. If you don't need to actually take advantage of the special properties of a Service, there's no point in using one (or you haven't really made a compelling case why it does need to be started all the time). Just register the listener when the app process starts, and let it go until the app process is killed for whatever reason. I highly doubt that your users will be upset about not having subscription updates if the app just isn't running (they certainly aren't using it!).
The power drain on an open socket that does no I/O is minimal. Also, an open socket will not necessarily keep the device's cell radio on at full power, either. So if the listen location isn't generating new values, then your listener is never invoked, and there is no network I/O. If the value being listened to is changing a lot, you might want reconsider just how necessary it is to keep the user's device busy with those updates.
The listener itself isn't "polling" or "retrying". The entire Firebase socket connection is doing this. The listener has no clue what's going on behind the scenes. It's either receiving updates, or not. It doesn't know or care about the state of the underlying websocket. The fact that a location is of interest to the client is actually managed on the server - that is what's ultimately responsible for noticing a change and propagating that to listening clients.
I am working on Xamarin Android Application.Before proceed to my next fragment I want to check Internet Connection and inform user about it ? How can i implement that ?And how to refresh whole fragment after user switch-on the internet?
Any advice or suggestion will be appreciated !
To get the network status you could use the following method in your activity:
public bool IsOnline()
{
var cm = (ConnectivityManager)GetSystemService(ConnectivityService);
return cm.ActiveNetworkInfo == null ? false : cm.ActiveNetworkInfo.IsConnected;
}
If I understood you correctly from this sentence: And how to refresh whole fragment after user switch-on the internet, You want to detect, whenever any changes in the connection status happens, Therefore you absolutely need to use broadcast receivers.
First of all you should implement a broadcast receiver with a simple Event named ConnectionStatusChanged as follows:
[BroadcastReceiver()]
public class NetworkStatusBroadcastReceiver : BroadcastReceiver
{
public event EventHandler ConnectionStatusChanged;
public override void OnReceive(Context context, Intent intent)
{
if (ConnectionStatusChanged != null)
ConnectionStatusChanged(this, EventArgs.Empty);
}
}
Then in your activity (in OnCreate() method for example, It doesn't matter) create an instance of that receiver and register it:
var _broadcastReceiver = new NetworkStatusBroadcastReceiver();
_broadcastReceiver.ConnectionStatusChanged += OnNetworkStatusChanged;
Application.Context.RegisterReceiver(_broadcastReceiver,
new IntentFilter(ConnectivityManager.ConnectivityAction));
Here is the body of the event handler:
private void OnNetworkStatusChanged(object sender, EventArgs e)
{
if(IsOnline()){
Toast.MakeText(this, "Network Activated", ToastLength.Short).Show();
// refresh content fragment.
}
}
To cut the long story short, NetworkStatusBroadcastReceiver receives any change in the network status of the device and invokes the ConnectionStatusChanged (When user enables data traffic or WiFi connection), Then you catch that event and check for network status using IsOnline() method. Very simple.
You can use the MVVMCross plugin : Connectivity
It wil expose a boolean
/// <summary>
/// Gets if there is an active internet connection
/// </summary>
bool IsConnected { get; }
and a delegate on change state
/// <summary>
/// Event handler when connection changes
/// </summary>
event ConnectivityChangedEventHandler ConnectivityChanged;
Try this :
NetworkStatus internetStatus = Reachability.InternetConnectionStatus();
if(!Reachability.IsHostReachable("http://google.com")) {
// Put alternative content/message here
}
else
{
// Put Internet Required Code here
}