My app uses the gcm module to listen for notifications and displays android notifications for every new notification that comes in. Additionally, the current window gets updated with the new count of unread messages.
This window is created using Ti.UI.createWindow({exitOnClose:true})
The problem is, that when the user presses the back button, the application stops. This means, I don't receive any more notifications and thus cannot display them in the notifications bar.
Is there a way to make titanium hide the app when pressing the back button, but not stop it, so that my code stil is running in the background?
I know of the possibility to start a service, but the downside of this is that i cannot update my window, when it's currently visible to the user, since there seems to be no way to communicate between the service and the app. Or is there a way?
app.js
//this is the most important line in this code.
//if I do exitOnClose:true, I stop receiving notifications every 5 seconds when pressing the back button (not good!, I want to keep getting notifications)
//if I do exitOnClose:false, I go back to a blank, "powered by titanium" window, when pressing the back button (not good!, I want the app to go to the background)
var win = Ti.UI.createWindow({exitOnClose:true});
//not part of the question
var label = Ti.UI.createLabel({text:"0"});
win.add(label);
win.open();
var notifications = [];
//listen for notifications (not part of the question)
listenForNotifications(function(notification){
//handle the notification
notifications.push(notification);
//update window
label.text = "Notification Count: "+notifications.length;
//display notification in title bar
displayNotificationInTitleBar(notification);
})
//this function is just dummy code to simulate listening for notifications in background using the gcm module
//it simulates a new notification every 5 seconds with an always increasing id
//it actually does not matter what module I use for notifications, Just take it as given that there runs code in the background,
//that I don't want to stop, after the user taps the backbutton
function listenForNotifications(cb){
var i = 0;
setInterval(function(){
cb({id:i++});
},5000);
}
//This function is actually not part of the question, it's just a sample
function displayNotificationInTitleBar(notification){
var intent = Ti.Android.createIntent({
action: Ti.Android.ACTION_MAIN,
packageName:"com.company.backgroundnotificationstest",
className:"com.company.backgroundnotificationstest.BackgroundnotificationstestActivity",
flags:Ti.Android.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Ti.Android.FLAG_ACTIVITY_SINGLE_TOP
});
intent.addCategory(Ti.Android.CATEGORY_LAUNCHER);
intent.putExtra("notificationid",notification.id);
Titanium.Android.NotificationManager.notify(notification.id, Titanium.Android.createNotification({
contentTitle: "New Notification",
contentText : "ID: "+notification.id,
contentIntent: Ti.Android.createPendingIntent({
intent:intent,
type : Ti.Android.PENDING_INTENT_FOR_ACTIVITY
}),
flags : Titanium.Android.ACTION_DEFAULT | Titanium.Android.FLAG_AUTO_CANCEL | Titanium.Android.FLAG_SHOW_LIGHTS
}));
}
The sample app is available at: https://github.com/VanCoding/TitaniumBackgroundNotificationsTest
Feel free to compile it and see it yourself :)
Since you set exitOnClose your app will exit on closing the window you have created. To prevent exiting from the app you need to reset the key as follows while creating the window.
Ti.UI.createWindow({exitOnClose: false});
If you would like to show the notifications in the notifications tray, make sure that you have set the following keys
showTrayNotification
showTrayNotificationsWhenFocused : This will display the notification in the tray even when the app is focused.
To show/hide a badge, you can try the following tip.
You need to keep track how many notifications you received and you need to update it upon receiving the notification. Just update the badge using the value stored. I tried this solution and is working great in one of my app
After a bit of thinking, I came to the following (a bit hacky) solution:
win.addEventListener("android:back",function(){ //listen for the back-button-tap event
e.cancelBubble = true; //prevent the back-button default action
//display the home screen
var intent = Ti.Android.createIntent({
action: Ti.Android.ACTION_MAIN,
flags:Ti.Android.FLAG_ACTIVITY_NEW_TASK
});
intent.addCategory(Ti.Android.CATEGORY_HOME);
Ti.Android.currentActivity.startActivity(intent);
});
Related
I am building a two person game app using vue.js. The app uses vuex for state management and Firestore as the backend server.
If the user leaves the app by either closing the browser tab or navigating away, the games Firestore files need to be deleted. However, if the user refreshes the page, the Firestore files need to remain so that the reload process can repopulate the game.
So I need to determine if the user has refreshed the page as opposed to closing the browser or navigating away.
As shown below, in vue's created lifecycle I setup a "beforeunload" event Listener and also start my Firestore listeners
created() {
// This window event listener fires when the user
// navigates away from or closes the browser window
window.addEventListener("beforeunload", (event) => {
const isByRefresh = getUnloadInitiator();
if (!isByRefresh) {
this.stopFirestoreListeners("beforeunload");
}
// Cancel the event. This allows the user to cancel via popup. (for debug purposes)
event.preventDefault();
event.returnValue = "";
// the absence of a returnValue property on the event
// guarantees the browser unload happens
// delete event["returnValue"];
});
this.startFirestoreListeners("created");
},
The getUnloadInitiator function is shown below. This is where I need help. Right now all this function does is console.log various performance values.
function getUnloadInitiator() {
// check for feature support before continuing
if (performance.mark === undefined) {
console.log("performance.mark NOT supported");
return false;
}
console.log("===============================");
// Yes I know that performance.navigation is depreciated.
const nav = performance.navigation;
console.log("nav=", nav);
console.log("===============================");
// Use getEntriesByType() to just get the "navigation" events
var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
var p = perfEntries[i];
console.log("= Navigation entry[" + i + "]=", p);
// other properties
console.log("type = " + p.type);
}
console.log("===============================");
performance.mark("beginLoop");
const entries = performance.getEntries({
name: "beginLoop",
entryType: "mark",
});
const firstEntry = entries[0];
console.log("firstEntry.type=", firstEntry.type);
console.log("===============================");
//TODO: Determine how unload was initiated
return true;
}
Below is the output from my console.logs. They are the same for refreshing the page, closing the browser tab, or navigating away. All show "reload" as the navigation type.
===============================
nav= PerformanceNavigation {type: 1, redirectCount: 0}
===============================
= Navigation entry[0]= PerformanceNavigationTiming {unloadEventStart: 25.399999976158142, unloadEventEnd: 25.69999998807907, domInteractive: 633, domContentLoadedEventStart: 633, domContentLoadedEventEnd: 633, …}
type = reload
===============================
firstEntry.type= reload
===============================
Any help on how to differentiate between refreshing the page, closing the browser tab, or navigating away would be appreciated. There must be away, because the native cancel browser popup I'm using for debug purposes differentiates between fresh and browser tab close.
Thanks
You can use a source of authority as persistence, be it firestore, local storage, or cookies. you are able to get the browser's tab ID with tab.id and compare it to an existing one should one exist.
browser.pageAction.show(tab.id);
Source: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Working_with_the_Tabs_API
I have a SPA with a service worker.
I don't want to force users to update, so usually the new worker updates in the background, but I notify users and they can click a button to message the worker to skipWaiting. When this happens I rely on oncontrollerchange to force any other tabs they have open to refresh.
I want to show a link to release notes after the worker has upgraded, either due to refresh of all tabs or they force the refresh.
However, I don't want to show these notes the first time they visit, or every time the service worker activates. If they don't read the release notes I don't want to keep nagging them about it.
Is there an event or reliable design pattern I can use to tell (in the page) that the service worker has updated to a new version?
A straightforward solution would be to add a query parameter to the current URL, and instead of calling location.reload() to reload your page, instead change the location value to that URL.
Something like:
// Inside your `controllerchange` listener, call reloadWithNotes()
function reloadWithNotes() {
const url = new URL(location.href);
url.searchParams.set('showNotes', '');
location = url.href;
}
// Elsewhere...
window.addEventListener('load', () => {
const url = new URL(location.href);
if (url.searchParams.has('showNotes')) {
// Show your release notes.
// Remove the parameter.
url.searchParams.delete('showNotes');
window.history.replaceState({}, document.title, url.href);
}
});
I'm totally new to WebExtension (trying to use them under Firefox). I've written a browser action. In order to keep a persistent state I figured that I have to implement a background script.
How can I acccess variables defined in the background script from my browser-action script?
Or is the assumption wrong that the background script can contain the state for the browser action?
Ok, got it. I found a good start here and here.
I use message posting for communication between my browser-action and background script.
Think of a game where you can act in the browser action popup and the game state is in the background script. Here is an example for getting number of coins (player money) from the background script to the browser-action:
browser-action:
var _playerCoins = 0;
// I connect a 'port' with the name 'getCoins'.
var _port = chrome.runtime.connect({name: "getCoins"});
// This is the message that is called if the other side posts a message via the port.
// The background script puts the current amount of coins into the message
_port.onMessage.addListener(function(msg) {
// Save the number of coins in a local variable
_playerCoins = msg;
// Display number of coins on my browser action html page
document.getElementById("coins").innerHTML="Coins: " + _playerCoins;
});
background script:
// Add a listener for port connections
chrome.runtime.onConnect.addListener(function(port) {
// If there is a 'getCoins' connection coming in...
if(port.name == "getCoins") {
// ...add a listener that is called when the other side posts a message on the port.
port.onMessage.addListener(function(msg) {
port.postMessage(_playerCoins);
});
}
}
I have built an app using titanium alloy
index.js
// Use the Alloy.Globals.Facebook namespace to make Facebook module API calls
var facebookModule = Alloy.Globals.Facebook;
//set facebook app id
facebookModule.appid = Ti.App.Properties.getString("ti.facebook.appid");
//set permissions i.e what data I want
facebookModule.permissions = ['user_friends','user_photos'];
// Do not force a facebook html popover but use the native dialog if possible
facebookModule.forceDialogAuth = false;
//invoke method onto button from module
$.fbButton.style = facebookModule.BUTTON_STYLE_WIDE;
$.index.open();
In my index.js controller I have this segment of code, it executes and I am presented with a log in screen.
I then fall into 2 problems:
1) "FB Session: Should only be used from a single thread"
2) I am unable to get the access token.
Not sure how to resolve both as the inbuilt login function has it's own event handler.
Cheers
Like you said, the inbuilt login function does have it's own handler.. so you should listen for event changes, something like this:
facebookModule.addEventListener('login', function(e) {
if (e.success) {
Ti.App.Properties.setString('face_token', facebookModule.getAccessToken());
// DO SOMETHING WITH THE TOKEN - open new window, auth the user...
}
});
If you try to get the access token BEFORE the login event is fired, you'll end up bad.
Now about the single thread thing.. I did run into this a while back.. I'm not sure exactly what I did to solve it, but I think it might be related to opening multiple windows or event allowing more than one call to the facebook API. Try to check if you are closing your windows and if the login function is being called more than once.
Let me know if that works for you. Good luck.
I'm wrapping a web app in a Windows Store app shell using a x-ms-webview. This works fine, but I have one problem. I use PayPal and since PayPal doesn't allow to be iframed I need to open PayPal in a new browser window.
On regular browsers this isn't a problem. The window open and when the user returns from PayPal I can a callback on "opener" and update the users account.
But when doing this in a Windows Store app the window.open triggers IE to launch. The problem is to return to my app and let it know that the user finished the transaction.
My first idea was just to use a URI activation. This kind of works, but I having trouble knowing if the PayPal page was launch from a regular browser or an app. I also think it is confusing for the user to be taken to another app to make the purchase.
I would prefer to have the window open in my app, but I'm not sure how I would open open a new x-ms-webview as a modal window overlapping existing webview.
What is the best way to communicate from the current web view and the app?
Can I use postMessage to send messages between the app and the x-ms-webview even though the src of the web view is a http hosted site?
Thank you for your help.
I found a solution to this.
First, you will need to use a https url for the embedded site. The reason for this is that the solution include postMessage and invokeScriptAsync.
First, my markup in my app looks something like this to have one webview for the app and one web view for the PayPal popup.
<x-ms-webview id="webview" src="https://myapp"></x-ms-webview>
<div id="paypalContainer">
<div class="paypal-header"><button id="paypalClose" type="reset">Close</button></div>
<div class="paypal-body"><x-ms-webview id="paypalWebView" src="about:blank"></x-ms-webview></div>
</div>
Then, when the web app is ready to use PayPal, I use window.external.notify to send a message to the Windows Store app.
if (window.external && 'notify' in window.external) {
window.external.notify(JSON.stringify({ action: 'paypal' }));
}
The windows store app listens for Script Notify events and displays the paypalWebView.
webview.addEventListener("MSWebViewScriptNotify", scriptNotify);
function scriptNotify(e) {
var data = JSON.parse(e.value);
if (data.action === "paypal") {
var loading = document.getElementById("loading-indicator");
var container = document.getElementById("paypalContainer");
var paypalWebView = document.getElementById("paypalWebView");
var paypalClose = document.getElementById("paypalClose");
if (paypalWebView.src === "about:blank") {
paypalWebView.addEventListener('MSWebViewNavigationCompleted', function (e) {
loading.classList.remove('loading');
var successUrl = '/paypal/success';
if (event.target.src.indexOf(successUrl) !== -1) {
var operation = webview.invokeScriptAsync("updateUser");
operation.oncomplete = function () {
(new Windows.UI.Popups.MessageDialog("Your account is refreshed", "")).showAsync().done();
};
operation.start();
}
});
paypalWebView.addEventListener('MSWebViewNavigationStarting', function (e) {
console.log("Started loading");
loading.classList.add('loading');
});
paypalClose.addEventListener('click', function () {
container.classList.remove("visible");
});
}
paypalWebView.src = "https://myapp/paypal/";
container.classList.add("visible");
}
}
So, basically, when the script notify event fires, I parse the sent json string to an object and check what kind of action it is. If it's the first time I run this I setup some naviation event handlers that check if the web view reach the Success page. If we have, I use incokeScriptAsync to let the web app know that we're done so it can refresh the user account the new payment.
I think you can use a similar solution for authentication and just check your your return URL after authenticating.
Hope this helps!