First off, "warm launch" means to launch the app from a background state using a system push notification.
I'm struggling to route my application to the proper screen for warm launch notifications.
On a cold launch, the notification is available via PushNotificationIOS.popInitialNotification(), but returns null on a warm launch. Responding to the 'notification' event is difficult, because there's no way that I'm aware of to determine if the app is launching from the background, or if it's receiving a notification while running in the foreground.
What is the proper way to handle warm launch notifications in React Native?
Here's my PushNotificationHandler, which wraps the core navigation of the app:
export default class PushNotificationHandler extends React.Component {
constructor(props) {
super(props)
this.state = {
modalInitialRoute: null,
modalOpen: false,
}
}
openModalWithRoute(route) {
this.setState({
modalInitialRoute: route,
modalOpen: true,
})
}
closeModal() {
this.setState({
modalOpen: false,
})
}
renderModal() {
const {
modalInitialRoute,
modalOpen,
} = this.state
return (
<Modal
close={() => this.closeModal()}
open={modalOpen}
initialRoute={modalInitialRoute}/>
)
}
componentWillMount() {
PushNotificationIOS.addEventListener('register', this._onPushNotificationRegistration)
PushNotificationIOS.addEventListener('notification', this._onPushNotification)
}
componentDidMount() {
this.requestPushNotificationPermission()
const notification = PushNotificationIOS.popInitialNotification()
if (notification) {
this.displayNotification(notification)
}
}
componentWillUnmount() {
PushNotificationIOS.removeEventListener('register', this._onPushNotificationRegistration)
PushNotificationIOS.removeEventListener('notification', this._onPushNotification)
}
requestPushNotificationPermission() {
PushNotificationIOS.checkPermissions((permissions) => {
// If no permissions are allowed, request permissions.
if (!(permissions.alert || permissions.badge || permissions.sound)) {
PushNotificationIOS.requestPermissions()
}
})
}
_onPushNotificationRegistration(deviceToken) {
const {
registerForPushNotifications,
} = this.props
const userId = this.props.parentId
registerForPushNotifications(userId, deviceToken)
}
_onPushNotification(notification) {
const {
receivePushNotification,
} = this.props
receivePushNotification(notification)
}
displayNotification(notification) {
const {
receivePushNotification
} = this.props
const data = notification.getData()
switch (data.type) {
case WORKSHEET_FEEDBACK:
this.openModalWithRoute({
component: SubmissionDetailsScreenContainer,
title: 'Submission Details',
key: 'submissionDetails',
props: {
submissionId: data.sID,
},
})
default:
break
}
}
render() {
//return this.renderModal()
return (
<View>
<View>
{this.props.children}
</View>
{this.renderModal()}
</View>
)
}
}
To listen for push notifications when the app is in the foreground or background add these two delegates to the bottom of your AppDelegate.m.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UA_LINFO(#"Application received remote notification %#", userInfo);
[RCTPushNotificationManager application:application didReceiveRemoteNotification:userInfo]; }
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
UA_LINFO(#"Application received remote notification -- UIBackgroundFetchResult: %#", userInfo);
[RCTPushNotificationManager application:application didReceiveRemoteNotification:userInfo];}
These delegates will call the RCTPushNotificationManager bridge passing the notification data which can then be handled from your app by listening to the notification event on the PushNotificationIOS component. You could register this handler from componentWillMount of your top-level component of your react native app.
PushNotificationIOS.addEventListener( 'notification', this._onNotification );
I accomplished this by creating an app state that stored wether the app was in background or foreground. When you use remote notification event handler, it pops only when you are currently in the app or coming in from a warm notification. So I just route the user only if the app was in background state and I received a remote notification. All handled in the componentDidMount I used redux to store the global state, but you could use another state handler.
componentDidMount() {
AppState.addEventListener('change', (appState) => {
if (currentAppState === 'background') {
this.props.dispatch(updateStatus(false));
} else if (currentAppState === 'active') {
this.props.dispatch(updateStatus(true));
}
});
PushNotificationIOS.addEventListener('notification', (notification) => {
if (this.props.status === false) {
let data = notification.getData();
this.props.remoteNavigator.resetTo({});
});
}
In this example this.props.status is wether my app is in the background or foreground. Which gets updated by the redux action updateStatus
Related
I'm using vue-cli-plugin-electron-builder for my electron app and I try to figure out how would I deal to open a component in a new window, which practically is a video player. The scenario is the following:
I have a list of movie dialogues with start and end timestamps. As I click on start timestamp per row the new window should open and the video player should start.
At this state I'm able to open a new window as it follows:
import { remote } from "electron";
export default {
methods: {
startVideo(id, startTimestamp) {
// eslint-disable-next-line no-console
console.log(id);
// eslint-disable-next-line no-console
console.log(startTimestamp);
let videoPlayerWindow = new remote.BrowserWindow({
show: true,
width: 1440,
height: 900,
webPreferences: { plugins: true }
});
}
}
}
but I don't know how to inject in this case the video-player child component.
I have done something similar so I should be able to set you on the right path here. You may just need to fill in some gaps
You will need to create a sub page using vue-cli.
So in your vue.config.js add
module.exports = {
//...
pages: {
index: 'src/main.js', // your main window
video_player: 'src/video_player/main.js' // your video window
}
}
Then, an example component in your src/video_player/main.js (This is where you would load your video player component)
import Vue from 'vue'
Vue.config.productionTip = false
const ipc = require('electron').ipcRenderer;
new Vue({
render: h => h('div', 'This is your video player, use a component here instead')
}).$mount('#app')
// listen for data from the main process if you want to, put this in your component if necessary
ipc.on('data', (event, data) => {
// use data
})
Now in your main process or src/background.js you will need to add an event listener to open the window from the renderer process.
import { app, protocol, BrowserWindow, ipcMain } from 'electron' // import ipcMain
...
// create the listener
ipcMain.on('load-video-window', (event, data) => {
// create the window
let video_player = new BrowserWindow({ show: true,
width: 1440,
height: 900,
webPreferences: {
nodeIntegration: true,
plugins: true
} })
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
video_player.loadURL(process.env.WEBPACK_DEV_SERVER_URL + 'video_player.html')
if (!process.env.IS_TEST) video_player.webContents.openDevTools()
} else {
video_player.loadURL(`app://./video_player`)
}
video_player.on('closed', () => {
video_player = null
})
// here we can send the data to the new window
video_player.webContents.on('did-finish-load', () => {
video_player.webContents.send('data', data);
});
});
Finally, in your renderer process, emit the event to open the window
import { ipcRenderer } from "electron"
export default {
methods: {
startVideo(id, startTimestamp) {
// eslint-disable-next-line no-console
console.log(id);
// eslint-disable-next-line no-console
console.log(startTimestamp);
let data = {id, startTimestamp}
// emit the event
ipcRenderer.send('load-video-window', data);
}
}
}
Hopefully that helps.
Noah Klayman has done a complete example here https://github.com/nklayman/electron-multipage-example.
You will just need to adapt.
I am working on a nativescript-angular application that receives [nativescript-plugin-firebase] notificatons.
The project is up to date. The package.json contains the following lines:
"tns-android": {
"version": "5.4.0"
},
"tns-core-modules": "~5.4.0",
"#angular/core": "~8.0.0"
"nativescript-plugin-firebase": "^9.0.1"
The notifications are fine but the app crashes when tapping the notification message if:
the app is opened (in foreground)
the device is closed
then tap the lock screen notification message
the app crashes
and displays:
An uncaught Exception occurred on 'main' thread.
java.lang.RuntimeException: Unable to destroy activity
I have encounter the same error on Android Emulator and on devices having Android 7, 8 and 9.
Screen error here
And here is the notification code inside src\app\app.component.ts :
ngOnInit(): void {
/// *** Firebase Cloud Messaging ***
firebase.addOnMessageReceivedCallback(
(message) => {
const that = this;
let contentId: string = "";
if (message.foreground) {
/** If the app is already open, show a dialog message. **/
confirm({
title: message.title,
message: message.body,
okButtonText: "Open",
cancelButtonText: "Cancel"
}).then(function (result) {
// result argument is boolean
if (result) {
if (message.data.contentId) {
contentId = message.data.contentId;
if (parseInt(contentId) > 0) {
that.routerExtensions.navigate(["/car-detail/" + contentId], { clearHistory: false });
}
}
}
// console.log("Dialog result: " + result);
});
}
else {
/** Else if the message arrived when the app is in the background, this code is executed when the user taps on the notification. **/
if (message.data.contentId) {
contentId = message.data.contentId;
if (parseInt(contentId) > 0) {
this.routerExtensions.navigate(["/car-detail/" + contentId], { clearHistory: false });
}
}
}
}
)
if (this.authenticationService.FirebaseToken == undefined || this.authenticationService.FirebaseToken == null) {
firebase.init({
// Optionally pass in properties for database, authentication and cloud messaging, see their respective docs.
onPushTokenReceivedCallback: function (token: any) {
console.log("Firebase push token: " + token);
}
, showNotificationsWhenInForeground: true
}).then(
() => {
console.log("firebase.init done");
},
error => {
console.log(`firebase.init error: ${error}`);
}
);
firebase.getCurrentPushToken().then((token: string) => {
// may be null if not known yet
console.log(`Current push token: ${token}`);
this.authenticationService.FirebaseToken = token;
});
}
// *** / Firebase Cloud Messaging ***
}
I have the below code in Angular component
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
this.source.addEventListener('datarec', datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
this._erdayService.currentMessage.subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
The issue is the this._date is initially loaded erday and UI view is according to erday. Now when I change the this._date to message, the UI view gets changed.
But still the erday data is shown in UI and the UI view fluctuates between erday & message and I'm not able to stop the this.source.addEventListener().
I tried to destroy in ngOnDestroy(),but it is not working.
I even tried this.source.close();.
Can someone help to know how to stop the listener created before calling another listener on same source ?
You subscribe to 2 data sources that emits continuously :
- The first being this._erdayService.currentMessage
- The second is this.source (when you trigger this.connect())
So this._date will change continuously. So you have to decide which data source you want to keep.
Case 1: You want to keep this.source as your data provider:
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
sourceListenerSubscription$ : Observable<any>;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
this.sourceSubscription$ = Observable.fromEvent(this.source, 'datarec').subscribe( datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
// take only one erday message, then listen to your spring server
this._erdayService.currentMessage.take(1).subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
Case 2: You want to keep erday as your data provider:
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
sourceListenerSubscription$ : Observable<any>;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
// take date once from spring server, and keep erday as data source
this.sourceSubscription$ = Observable.fromEvent(this.source, 'datarec').take(1).subscribe( datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
this._erdayService.currentMessage.subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
I'm writing an VR application using React VR and would make gaze buttons with a progress bar (or something) to show the user how long he must watch to that button. How could I do that?
I'm thinking to use this pseudocode (may be there are some bug's inside this code):
constructor(props) {
super(props);
this.state = {
watchTime: 3,
progress: 0,
watching: true
};
}
render() {
return (
<VrButton onEnter={ () => this.animateProgress() }
onExit={ () => this.stopProgress() }
onClick={ ()=> this.click() }></VrButton>
);
}
animateProgress() {
this.setState({watching: true});
while (this.state.watchTime >== this.state.progress && this.state.watching === true) {
// after a timeout of one second add 1 to `this.state.progress`
}
this.click();
}
stopProgress() {
this.setState({
progress: 0,
watching: false
});
}
click() {
// Handels the click event
}
Is there an easier way to do this?
You need to add some things to your project.
Install a simple raycaster using
npm install --save simple-raycaster
Inside vr/client.js add this code:
import { VRInstance } from "react-vr-web";
import * as SimpleRaycaster from "simple-raycaster";
function init(bundle, parent, options) {
const vr = new VRInstance(bundle, "librarytests", parent, {
raycasters: [
SimpleRaycaster // Add SimpleRaycaster to the options
],
cursorVisibility: "auto", // Add cursorVisibility
...options
});
vr.start();
return vr;
}
window.ReactVR = { init };
Source: npm simple-raycaster
Inside the index.vr.js use this code:
constructor(props) {
super(props);
this.click = this.click.bind(this); // make sure this.click is in the right context when the timeout is called
}
render() {
return (
<VrButton onEnter={ () => this.animateProgress() }
onExit={ () => this.stopProgress() }
onClick={ ()=> this.click() }></VrButton>
);
}
animateProgress() {
this.timeout = this.setTimeout(this.click, 1000); // or however many milliseconds you want to wait
// begin animation
}
stopProgress() {
clearTimeout(this.timeout);
this.timeout = null;
// end animation
}
click() {
// ...
}
Source: andrewimm at GitHub facebook/react-vr
I try to use SocketIO in ReactNative by follow this link
https://github.com/facebook/react-native/issues/4393,
On IOS it work very well but Android it could not work
Result Of Socket Object
connected:false
index.android.js
window.navigator.userAgent = 'react-native';//'react-native';
const io = require('socket.io-client/socket.io');
export default class testApp extends Component {
componentWillMount(){
this.socket = io.connect('http://localhost:3000', {
jsonp: false,
transports: ['websocket']
});
// Socket Object connected:false
}
componentDidMount(){
console.log(this.socket)
this.socket.on('connect', () => {
console.log('ready to emit')
console.log('connected!');
});
}
package.json
"react-native": "0.35.0",
"socket.io-client": "^1.5.1"
I could not found similar problem
I missing something?
edited :
I'm not sure can I test socketIO in localhost with ReactNative but It's work when I test on IOS emulator
edited2 :
My fault It cannot test on local environment server
but It's work on IOS not android
Can Anybody Explained Why?
I also wanted to use Socket.IO with ExpressJS server and React Native but couldn't get it to work.
Then used https://facebook.github.io/react-native/docs/network.html#websocket-support with https://github.com/websockets/ws
And works great.
this FullExample for Socket.io in clint ( I Hope Work for you )
import React from 'react';
import SocketIOClient from 'socket.io-client'
const USER_ID = '#userId';
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [],
userId: null
};
this.determineUser = this.determineUser.bind(this);
this.onReceivedMessage = this.onReceivedMessage.bind(this);
this.onSend = this.onSend.bind(this);
this._storeMessages = this._storeMessages.bind(this);
this.socket = SocketIOClient('http://localhost:3000');
this.socket.on('message', this.onReceivedMessage);
this.determineUser();
}
/**
* When a user joins the chatroom, check if they are an existing user.
* If they aren't, then ask the server for a userId.
* Set the userId to the component's state.
*/
determineUser() {
AsyncStorage.getItem(USER_ID)
.then((userId) => {
// If there isn't a stored userId, then fetch one from the server.
if (!userId) {
this.socket.emit('userJoined', null);
this.socket.on('userJoined', (userId) => {
AsyncStorage.setItem(USER_ID, userId);
this.setState({ userId });
});
} else {
this.socket.emit('userJoined', userId);
this.setState({ userId });
}
})
.catch((e) => alert(e));
}
// Event listeners
/**
* When the server sends a message to this.
*/
onReceivedMessage(messages) {
this._storeMessages(messages);
}
/**
* When a message is sent, send the message to the server
* and store it in this component's state.
*/
onSend(messages=[]) {
this.socket.emit('message', messages[0]);
this._storeMessages(messages);
}
render() {
var user = { _id: this.state.userId || -1 };
return (
<></>
);
}
}
const Local = Platform.OS === 'ios' ? 'http://localhost:3000' : 'http://10.0.2.2:3000'
import io from "socket.io-client";
//
this.socket = io(Local);
// console.log(this.socket)
this.socket.emit(Socket_category, Socket_online_subset);
this.socket.on(Socket_connection_name, this.onReceivedMessage);
onReceivedMessage =(messages)=> {consol,log(message)}
io.on('connection', function (client) {console.log('User Joined :)')
client.on(Path_Socket.Socket_category, function (room_name) {
console.log('joined room online ;) '+room_name);
client.join(room_name);
})
}
io.sockets.in(Socket_online_subset)
.emit(Socket_connection_name, data(any thing));
may be this will through error
import io from "socket.io-client/socket.io"
Then just add below line....
import io from "socket.io-client/dist/socket.io";
then in componentDidMount or useEffect function just add below line. Never use it under constructor of class component.
var socket = io("https://localhost.com:3000", { jsonp: false });
// client-side
socket.on("chat_message", (msg) => {
console.log(msg);
});