I am following this plugin (learning) background locator . I have previously used other background location plugins but they are not maintained anymore.
I am following the tutorial as described in the description of the plugin and so far this is my code.
I am unsure about what exactly a callback is and how do I use it.
This is my code so far but I don't get any results.
static void backgroundLocationCallBack(LocationDto location) async {
await BackgroundLocator.initialize();
await BackgroundLocator.isServiceRunning();
}
void getLocationLiveUpdates() async {
return await BackgroundLocator.registerLocationUpdate(
backgroundLocationCallBack,
autoStop: false,
// disposeCallback: backgroundLocationDisposeCallBack,
androidSettings: AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
)));
}
Callbacks are used to perform some actions during certain events.
Here, the callback attribute takes backgroundLocationCallBack as its value, and as per the package code, it is used for performing appropriate actions when the LocationDto is available. This LocationDto object will contain the Location information you are looking for, and you can use it according to your use-case.
Similarly, disposeCallback is used to perform certain actions when dispose is called on the BackgroundLocator object. You can do something like close all the Ports that were opened for fetching the location.
There are other callbacks as well such as initCallback and initDataCallback which are used for setting the callbacks to initialise various parameters for your location service and to provide an initial data for the params respectively.
I need to ensure that a certain HTTP request was send successfully. Therefore, I'm wondering if a simple way exists to move such a request into a background service task.
The background of my question is the following:
We're developing a survey application using flutter. Unfortunately, the app is intended to be used in an environment where no mobile internet connection can be guaranteed. Therefore, I’m not able to simply post the result of the survey one time but I have to retry it if it fails due to network problems. My current code looks like the following. The problem with my current solution is that it only works while the app is active all the time. If the user minimizes or closes the app, the data I want to upload is lost.
Therefore, I’m looking for a solution to wrap the upload process in a background service task so that it will be processed even when the user closes the app. I found several posts and plugins (namely https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124 and https://pub.dartlang.org/packages/background_fetch) but they don’t help in my particular use case. The first describes a way how the app could be notified when a certain event (namely the geofence occurred) and the second only works every 15 minutes and focuses a different scenario as well.
Does somebody knows a simple way how I can ensure that a request was processed even when there is a bad internet connection (or even none at the moment) while allowing the users to minimize or even close the app?
Future _processUploadQueue() async {
int retryCounter = 0;
Future.doWhile(() {
if(retryCounter == 10){
print('Abborted after 10 tries');
return false;
}
if (_request.uploaded) {
print('Upload ready');
return false;
}
if(! _request.uploaded) {
_networkService.sendRequest(request: _request.entry)
.then((id){
print(id);
setState(() {
_request.uploaded = true;
});
}).catchError((e) {
retryCounter++;
print(e);
});
}
// e ^ retryCounter, min 0 Sec, max 10 minutes
int waitTime = min(max(0, exp(retryCounter)).round(), 600);
print('Waiting $waitTime seconds till next try');
return new Future.delayed(new Duration(seconds: waitTime), () {
print('waited $waitTime seconds');
return true;
});
})
.then(print)
.catchError(print);
}
You can use the plugin shared_preferences to save each HTTP response to the device until the upload completes successfully. Like this:
requests: [
{
id: 8eh1gc,
request: "..."
},
...
],
Then whenever the app is launched, check if any requests are in the list, retry them, and delete them if they complete. You could also use the background_fetch to do this every 15 minutes.
I have a routine that logs into gmail using Protractor, that is called from the middle of my script (which is why some things that appear unnecessary are there), but I have isolated it as much as I can. When I run it not headless it passes. When I run it headless, it fails. I looked at the related posts, and they did not seem to be Protractor specific, and they did seem to parallel my code here.
Here is the code:
const EC = ExpectedConditions;
beforeAll(function(){
});
beforeEach(function() {
//because I am using gmail after sending an email from an angular app with a link to get back into one
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
afterEach(function() {
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
var gmailLogin = function(){
browser.waitForAngularEnabled(false);//gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000);//because ignore sync takes time to settle in
browser.driver.manage().timeouts().implicitlyWait(10000);//set in config, but seems to work only if here
browser.get("https://mail.google.com/mail");
browser.wait(EC.titleContains("Gmail"), 10000, "wait for gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
function(retval){},function(err){}
)
var variousInput = element(by.id('identifierId'));
browser.wait(EC.presenceOf(variousInput), 10000, "wait for identier ID prompt").then(
function(retVal){
var variousInput2 = browser.driver.findElement(by.id('identifierId'));
variousInput2.sendKeys("myemail here");
variousInput2=browser.driver.findElement(by.id("identifierNext"));
variousInput2.click();
variousInput2 = browser.driver.findElement(by.name('password'));
variousInput2.sendKeys('my password here');
variousInput2=browser.driver.findElement(by.id("passwordNext"));
variousInput2.click();
},
function(err){}//assume not found because cookie still around, proceed to next step
)
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
describe('runs gmail test for so', function() {
it('tests gmail', function() {
gmailLogin();
expect(browser.getTitle()).toContain('Inbox');
}, 2 * 60 * 1000); //should always come up within 2 minutes
}); //end of describe
And here is the headed configuration file:
exports.config = {
directConnect: true,
allScriptsTimeout: 120000,
getPageTimeout: 60000,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
chromeOptions: {
//args: ["--headless","--disable-gpu","--no-sandbox"]
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directory when
// protractor is called.
specs: [
'./so.ts'
],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 180000
},
beforeLaunch: function() {
},
onPrepare() {
browser.manage().window().setSize(1600, 1000);
browser.driver.manage().timeouts().implicitlyWait(15000);
}
}
}
and here is the headless (you can see I threw the kitchen sink at the options).
exports.config = {
directConnect: true,
allScriptsTimeout: 60000,
getPageTimeout: 30000,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
chromeOptions: {
args: ["--headless","--disable-gpu","--window-size=1600,1000","--disable-infobars","--disable-extensions","--auth-server-whitelist","--remote-debugging-port=9222"]
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directory when
// protractor is called.
specs: [
'./so.ts'
],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 180000
},
beforeLaunch: function() {
},
onPrepare() {
// screen size set in chrome options
browser.driver.manage().timeouts().implicitlyWait(15000);
}
}
}
If there is some kind of underlying, undocumented wisdom about which locators work or do not work headless, I would love to know.
Thanks,
jk
SLIGHT UPDATE: I cleaned up the code to use only explicit waits and straight Protractor (which is how I had it originally before reading pieces on the web that were based in other languages). Here is the revised version, which still passes not headless and fails headless (I also removed the implicit wait setting in OnPrepare() and all but the first three chrome options when doing headless).
var gmailLogin = function() {
browser.waitForAngularEnabled(false); //gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000); //because ignore sync takes time to settle in
browser.get("https://mail.google.com/mail");
browser.wait(EC.titleContains("Gmail"), 10000, "wait for gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
function(retval) {},
function(err) {}
);
var variousInput = element(by.id('identifierId'));
browser.wait(EC.presenceOf(variousInput), 10000, "wait for identifier ID prompt").then(
function(retVal) {
var variousInput2 = element(by.id('identifierId'));
variousInput2.sendKeys("email address");
variousInput2 = element(by.id("identifierNext"));
variousInput2.click();
variousInput2 = element(by.name('password'));
browser.wait(EC.presenceOf(variousInput2), 10000, "wait for password prompt");
browser.wait(EC.visibilityOf(variousInput2), 10000, "wait for password prompt");
variousInput2.sendKeys('my password');
variousInput2 = element(by.id("passwordNext"));
variousInput2.click();
},
function(err) {} //assume not found because cookie still around, proceed to next step
)
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
BIGGER UPDATE: this may be something funky about headless after all. I added the following lines right before waiting for the identifier ID element(by.tagName('html')).getText().then(function(text){console.log(text);});
in not headless mode, that produced
Sign in
to continue to Gmail
Email or phone
Forgot email?
Not your computer? Use Guest mode to sign in privately.
Learn more
NEXT
Create account
English (United States)
HelpPrivacyTerms
in headless, it gave
One account. All of Google.
Sign in to continue to Gmail
Find my account
Create account
One Google Account for everything Google
About Google Privacy Terms Help
followed by a long list of languages from Afrikaans to 繁體中. So it seems almost as if in headless the browser has forgotten where it lives (at the very least the addition of One account all of Google and the languages says it is not apples to apples). It makes me wonder if then IdentifierId might also have a different name in such a case.
One last update for now:
To debug I added the following code when that first page loads:
var inputs=element.all(by.tagName('input'));
inputs.each(function(element,index){
element.getAttribute("Id").then(function(text){console.log('input '+index+' '+text);})
})
not headless, we get:
input 0 identifierId
input 1 null
input 2 ca
input 3 ct
input 4 pstMsg
input 5 checkConnection
input 6 checkedDomains
but headless we get:
input 0 null
input 1 null
input 2 null
input 3 null
input 4 null
input 5 null
input 6 null
input 7 null
input 8 null
input 9 null
input 10 null
input 11 profile-information
input 12 session-state
input 13 null
input 14 _utf8
input 15 bgresponse
input 16 Email
input 17 Passwd-hidden
input 18 next
So Protractor is right that it cannot find by ID identifierID. But how come?
FINAL:
So depending on headless or not, google was redirecting to two different urls with two different sets of Ids and names. I posted revised code that handles both in my answer.
Thanks for the guidance, all.
So it turns out that Google will redirect the mail service request to two different versions of its interface depending on whether one goes in headless or not. I rewrote the code to handle either one. I also tried to simplify where I could, including no more implicit waits and adding more chaining (I also dipped my toes into ES6 as encouraged by Oleksii's comment).
const EC = ExpectedConditions;
beforeAll(function(){
});
beforeEach(function() {
//because I am using gmail after sending an email from an angular app with a link to get back into one
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
afterEach(function() {
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
var waitForIds = (id1,id2)=>{//waits for one of two ids, assumes they must exist or else it is an error
var elm = element.all(by.xpath("//*[#id = '"+id1+"' or #id = '"+id2+"']")).first();
browser.wait(EC.presenceOf(elm), 30000, "wait for "+id1+" or "+ id2);
return elm;
}
var gmailLogin = () => {
browser.waitForAngularEnabled(false); //gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000); //because ignore sync takes time to settle in
browser.get("https://accounts.google.com/ServiceLogin?service=mail");
browser.sleep(2000);
element(by.id('gbqfq')).isPresent().then((present) => {
//if present, we are already on the inbox screen, because we found the search pane
if (!present) { //still work to do to get there
browser.wait(EC.titleContains("Gmail"), 10000, "wait for a gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
(retval) => {}, (err) => {}
);
waitForIds('Email', 'identifierId').sendKeys("my email here");
waitForIds("identifierNext", "next").click();
waitForIds('Passwd', 'password').getAttribute('id').then((text) => {
element(by.name(text)).sendKeys('my password here');
waitForIds("signIn", "passwordNext").click();
})
}
})
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
describe('runs gmail test for so', function() {
it('tests gmail', function() {
gmailLogin();
expect(browser.getTitle()).toContain('Inbox');
}, 2 * 60 * 1000); //should always come up within 2 minutes
}); //end of describe
UPDATE: I am accepting this because it addresses my question of what happens and why and how to address is directly. I totally accept that despite answering the question, there are better ways to go about what I actually wanted to do (getting the href from an email) either by catching the email on its way out or by using the gmail api.
I'm working with cordova's BLE (bluetooth low energy)
After I subscribe to notifications of BLE (which returns Observable), I want to send some message to the ble device, what is the best way to perform this, basically I need to run a function once after the subscription is made so that once device responds back to me, the code in the subscription is run.
ble.startNotification(deviceId, uuid1, uuid2).subscribe(bufferData=> {
//do something with bufferData
})
now after this, I want to run something like a callback,
.then(()=> {
//send message to device (only once), after the message is sent, the device will respond back and the `do something with bufferData` code will be run
})
I could easily do a setTimeout and send a message to the device after few seconds, and of course it works, but I want to do it cleanly, after I'm sure the subscription happened (subscription to the Observable of course)
You can wrap existing method using create operator and add custom code that will be executed on every new subscription.
See the example:
// emulate cordova with "dummy" bluetooth interface
const BLE = {
startNotification: () => Rx.Observable.interval(1000)
}
const wrappedBLE = (...params) =>
Rx.Observable.create(observer => {
// constructor fn will be executed on every new subscribtion
const disposable = BLE.startNotification(...params).subscribe(observer);
// place code to send notification here, instead of console log
console.log('New subscriber for BLE with params: ', params);
return disposable;
});
wrappedBLE("param1", "param2", "param3")
.subscribe(e => console.log("received notification: ", e));
<script src="https://unpkg.com/rxjs#5.4.3/bundles/Rx.min.js"></script>
i have question. Is it possible to keep changes feed alive when hybrid app is in background or closed?
var changes = db.changes({
since: 'now',
live: true,
include_docs: true,
timeout: false
// attachments: true
}).on('change', function (change) {
onChange(change);
// handle change
}).on('complete', function (info) {
// changes() was canceled
}).on('error', function (err) {
console.log(JSON.stringify(err))
});
No. The only way this could work is with Service Worker and Background Sync. It seems there's a polyfill for iOS: https://github.com/MobileChromeApps/cordova-plugin-service-worker-background-sync