I am working on application which is implemented using Expo and react native. I have splash screen in my app.The icon in splash screen loading screen is coming correct in android devices but very small in IOS devices ( in different dpi).I want to make icon resize based on dpi which present under loading .Can anyone help me out here.Thank you.
Config file:-
"expo": {
"name": "ExpoApp",
"description": "No description",
"slug": "evosus",
"privacy": "unlisted", // public
"sdkVersion": "19.0.0",
"version": "1.0.0",
"orientation": "portrait,landscape",
"primaryColor": "green",
"icon": "./assets/icons/ball.png",
//"icon": "./assets/icons/logo_Dark.png",
"notification": {
"icon": "./assets/icons/ball.png",
"color": "#000000"
},
"loading": {
//"icon": "./assets/icons/loading-icon.png",
"icon": "./assets/icons/icon.png",//I want to resize this icon based on screen dpi
"hideExponentText": true
},
"packagerOpts": {
"assetExts": [
"ttf"
]
},
"ios": {
"supportsTablet": true
}
//,"androidStatusBarColor": "#444444"
}
}
App.js component:-
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, SQLite } from 'expo';
import { Ionicons, EvilIcons } from '#expo/vector-icons';
import RootNavigation from './navigation/RootNavigation';
// Services
import DB from "./src/Services/DBDefinitionService";
import appService from "./src/Services/AppService";
console.ignoredYellowBox = [ 'Setting a timer' ];
export default class App extends React.Component {
state = {
assetsAreLoaded: false,
};
componentWillMount() {
this._loadAssetsAsync();
// Added by Anil G on 23/08/2017
this.db_init();
this.db_device_info_save();
}
render() {
if (!this.state.assetsAreLoaded && !this.props.skipLoadingScreen) {
return <AppLoading/>;
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
{Platform.OS === 'android' &&
<View style={styles.statusBarUnderlay} />}
<RootNavigation />
</View>
);
}
}
async _loadAssetsAsync() {
try {
await Promise.all([
Asset.loadAsync([
require('./assets/images/robot-dev.png'),
require('./assets/images/robot-prod.png'),
]),
Font.loadAsync([
// This is the font that we are using for our tab bar
Ionicons.font,
EvilIcons.font,
// We include SpaceMono because we use it in HomeScreen.js. Feel free
// to remove this if you are not using it in your app
{ 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
'roboto-regular': require('./assets/fonts/Roboto-Regular.ttf'),
'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf'),
},
]),
]);
} catch (e) {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(
'There was an error caching assets (see: App.js), perhaps due to a ' +
'network timeout, so we skipped caching. Reload the app to try again.'
);
console.log(e);
} finally {
this.setState({ assetsAreLoaded: true });
}
}
async db_init() {
await DB.init();
}
async db_device_info_save() {
await appService.device_info_save();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
statusBarUnderlay: {
height: 24,
backgroundColor: 'rgba(0,0,0,0.2)',
},
});
If I remember properly you can define it the same way you do in iOS, adding #x2, #x4...to your icon name and defining one for each dpi.
Related
I am working on a bare Expo project that uses the Location SDK (https://docs.expo.io/versions/latest/sdk/location/) to geofence an area.
Geofencing works fine on iOS, however, on Android it can be really slow in the background (or when the app is force-quitted). It can sometimes take more than 15 minutes to run the registered task when exiting or entering a geofence.
I have also noticed that I can force trigger the registered geofence task to run, if I open an app such as Google Maps and hit the GPS button.
Is it possible to speed up location updates somehow, or perhaps configure something in Android Studio?
package.json includes:
"expo-location": "^12.0.4",
"expo-notifications": "^0.11.5",
"expo-permissions": "^12.0.1",
"expo-task-manager": "^9.0.0",
"react": "17.0.1",
"react-native": "0.64.1",
Let me know if you need anything else.
import React, { useEffect, useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import {
getForegroundPermissionsAsync,
requestBackgroundPermissionsAsync,
requestForegroundPermissionsAsync,
startGeofencingAsync,
} from "expo-location";
import * as Notifications from "expo-notifications";
import { LocationGeofencingEventType } from "expo-location";
import * as TaskManager from "expo-task-manager";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
TaskManager.defineTask(
"GEOFENCE_TASK",
({ data: { eventType, region }, error }) => {
if (error) {
// check `error.message` for more details.
return;
}
if (eventType === LocationGeofencingEventType.Enter) {
console.log("You've entered region:", region);
Notifications.scheduleNotificationAsync({
content: {
title: "ENTERED GEOFENCE",
body: region.identifier,
},
trigger: null,
});
} else if (eventType === LocationGeofencingEventType.Exit) {
console.log("You've left region:", region);
Notifications.scheduleNotificationAsync({
content: {
title: "EXITED GEOFENCE",
body: region.identifier,
},
trigger: null,
});
}
}
);
export default function App() {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const setUp = async () => {
const { granted: notificationsGranted } =
await Notifications.getPermissionsAsync();
if (!notificationsGranted) {
await Notifications.requestPermissionsAsync();
}
const { granted: fgGranted } = await getForegroundPermissionsAsync();
if (!fgGranted) {
await requestForegroundPermissionsAsync();
await requestBackgroundPermissionsAsync();
}
const geofences = [
{
identifier: "Stockholm",
latitude: 59.332598,
longitude: 18.035258,
radius: 100,
notifyOnEnter: true,
notifyOnExit: true,
},
];
await startGeofencingAsync("GEOFENCE_TASK", geofences);
};
setUp();
}, []);
return (
<View style={styles.container}>
{isLoading ? <Text>App is Loading</Text> : <Text>Loading done</Text>}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
I discovered the same "issue" and to mitigate it, i use a foreground notification to put improve the geofencing service.
The good news is thats trick works well with "app in-use" permission. (but background location permission is mandatory)
Regards.
I have a React Native app I built using the Expo managed workflow. I am using Facebook login for my app along with Firebase authentication. My understanding is this can not be tested when running in the Expo app, and can only be used in a standalone app.
FB login is working fine in my iOS standalone app, however, in android, it crashes before the Facebook login screen even appears. My LandingPage loads, but as soon as I press the "Login With Facebook" button, it crashes.
Below is my app.json file and my login code. Per the instructions, I have also added the Facebook Key Hash to my project on the Facebook developer site. Can anyone help me understand what is wrong?
app.json:
{
"expo": {
"name": "Pick Up",
"slug": "PickUp",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/ball-and-glove-5.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"notification": {
"icon": "./assets/ball-and-glove-5.png",
"color": "#fff",
"androidMode": "default"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"userInterfaceStyle": "light",
"config": {
"googleMobileAdsAppId": "ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxx",
"googleMapsApiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"bundleIdentifier": "com.myapp.name",
"buildNumber": "1.0.0"
},
"userInterfaceStyle": "automatic",
"android": {
"useNextNotificationsApi": true,
"userInterfaceStyle": "dark",
"config": {
"googleMobileAdsAppId": "ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxx",
"googleMaps": {
"apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
},
"package": "com.myapp.name",
"versionCode": 1
},
"web": {
"favicon": "./assets/favicon.png"
},
"facebookScheme": "fbxxxxxxxxxxxxxxxx",
"facebookAppId": "xxxxxxxxxxxxxxx",
"facebookDisplayName": "myname"
}
}
Login Code:
import React, { useState, useEffect, useContext } from 'react';
import { Alert, Animated, Image, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import * as WebBrowser from 'expo-web-browser';
import * as Facebook from 'expo-facebook';
import * as firebase from 'firebase';
import { loginUser } from '../../firebase.js';
import { StoreContext } from '../../contexts/storeContext';
const turquoise = "#4ECDC4";
const LandingPage = ({ navigation }) => {
const store = useContext(StoreContext);
const handleFacebook = async () => {
try {
await Facebook.initializeAsync();
const options = {
permissions: ["public_profile"]
}
const { type, token } = await Facebook.logInWithReadPermissionsAsync(options);
if (type === "success") {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
if (credential) {
const bool = await loginUser(credential);
}
}
else {
Alert.alert("", "There was a problem. Please try again.");
}
}
catch (e) {
console.log("e : ", e);
Alert.alert("", e);
}
}
return (
<View>
<StatusBar barStyle="dark-content" backgroundColor={ turquoise } />
<View>
<TouchableOpacity onPress={ handleFacebook }>
<Ionicons name="logo-facebook" />
<Text>Continue with Facebookk</Text>
</TouchableOpacity>
<TouchableOpacity onPress={ () => navigation.navigate("PhoneVerification") }>
<Text>Use cell phone number</Text>
</TouchableOpacity>
</View>
<View>
<Image
resizeMode="contain"
source={require('../../assets/new-logo.png')}
/>
</View>
</View>
)
}
The issue was that for Android, Facebook.initializeAsync() needs to be passed an argument -- your FB appID.
const handleFacebook = async () => {
try {
await Facebook.initializeAsync("123456789"); // <-- your FB appID
const options = {
permissions: ["public_profile"]
}
const { type, token } = await Facebook.logInWithReadPermissionsAsync(options);
if (type === "success") {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
// do the rest of your Firebase login logic
}
else {
Alert.alert("", "There was a problem. Please try again.");
}
}
catch (e) {
console.log("e : ", e);
Alert.alert("", e);
}
}
When the app loads there is a white background then a flicker before the splash screen is shown. I have removed the splash screen from the app.json file since i am manually loading and hiding the splash screen. (Leaving the splash screen in the app.json file results in the splash screen being shown followed by a white flicker and then the splash scrren is shown again a second time)
App.js
import React from 'react';
import { StyleSheet, View, Image } from 'react-native'
import { MyAuthStack, MyMainDrawer } from './Screens/Navigators'
import firebase from './firebase'
import { AppLoading, SplashScreen } from 'expo';
import { Asset } from 'expo-asset';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
authState: false,
imgUrl: '',
isSplashReady: false,
isAppReady: false,
}
}
_cacheSplashResourcesAsync = async () => {
const gif = require('./assets/splash.png');
return Asset.fromModule(gif).downloadAsync();
}
_cacheResourcesAsync = async () => {
SplashScreen.hide();
const images = [
require('./assets/addthreadicon.png'),
require('./assets/500x500.png'),
];
const cacheImages = images.map((image) => Asset.fromModule(image).downloadAsync());
await Promise.all(cacheImages);
firebase.auth().onAuthStateChanged(user => {
if (user != null) {
const userRef = firebase.database().ref(`users/${firebase.auth().currentUser.uid}/img`)
userRef.once('value', snapshot => {
this.setState({ imgUrl: snapshot.val(), authState: true, isAppReady: true })
})
} else {
this.setState({ imgUrl: '', authState: false, isAppReady: true })
}
})
};
render() {
const { isSplashReady, isAppReady, authState } = this.state;
if (!isSplashReady) {
return (
<AppLoading
startAsync={this._cacheSplashResourcesAsync}
onFinish={() => this.setState({ isSplashReady: true })}
onError={process.env.NODE_ENV === 'production' ? undefined : console.warn /* eslint-disable-line no-console */}
autoHideSplash={false}
/>
);
}
return (
<View style={{ flex: 1 }}>
{!isAppReady ? (
<Image
source={require('./assets/splash.png')}
onLoad={this._cacheResourcesAsync}
style={{ width: '100%', height: '100%' }}
/>
) : (
<View style={{ flex: 1 }}>
{authState ? (<MyMainDrawer imgUrl={this.state.imgUrl} />) : (<MyAuthStack />)}
</View>
)
}
</View>
)
}
}
});
app.json
{
"expo": {
"name": "Blank Template",
"slug": "movie",
"privacy": "public",
"sdkVersion": "36.0.0",
"platforms": [
"ios",
"android",
"web"
],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"android": {
"package": "com.saim.moviethreads",
"versionCode": 1,
"config": {
"googleMobileAdsAppId": ""
}
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourcompany.yourappname",
"buildNumber": "1.0.0"
}
}
}
Splash Screen flickers because you want to change the state of the application as soon as the splash screen loads. I suggest you use AppLoading from expo, this controls the visibility of the splash screen.
<AppLoading
startAsync={*function to load when splash screen starts*}
onFinish={set the state to finished here}
/>
When I build a stand alone android app the header left back button disappears, yet it's there if you click on it. It has no issue on the emulator. What could cause this?
I'm not sure when it started because I was relying on the emulator, but I do know that it was working at some point
Here is my app.json
{
"name": "appname",
"displayName": "appname",
"expo": {
"name": "appname",
"version": "1.0.0",
"slug": "appslug",
"orientation": "portrait",
"privacy": "unlisted",
"sdkVersion": "32.0.0",
"description": "",
"platforms": [
"ios",
"android"
],
"icon": "./assets/images/icon.png",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"enabled": true,
"fallbackToCacheTimeout": 30000,
"checkAutomatically": "ON_LOAD"
},
"ios": {
"buildNumber": "1.0.0",
"icon": "./assets/images/icon.png",
"bundleIdentifier": "my.unique.id"
// "splash": {
// "backgroundColor": "#FFFFFF",
// "resizeMode": "cover",
// "image": "./assets/iphone/Default-667h#2x.png"
// }
},
"android": {
"versionCode": 1,
"icon": "./assets/images/icon.png",
"package": "my.unique.id",
"adaptiveIcon": {
"foregroundImage": "./assets/images/icon.png",
"backgroundColor": "#FFFFFF"
}
// "splash": {
// "backgroundColor": "#FFFFFF",
// "resizeMode": "cover",
// "mdpi": "./assets/android/res/drawable-mdpi/background.9.png", // natural sized image (baseline),
// "hdpi": "./assets/android/res/drawable-hdpi/background.9.png", // scale 1.5x
// "xhdpi": "./assets/android/res/drawable-xhdpi/background.9.png", // scale 2x
// "xxhdpi": "./assets/android/res/drawable-xxhdpi/background.9.png", // scale 3x
// "xxxhdpi": "./assets/android/res/drawable-xxxhdpi/background.9.png" // scale 4x
// }
},
"hooks": {
"postPublish": [
{
"file": "sentry-expo/upload-sourcemaps",
"config": {
"organization": "my.org",
"project": "proj",
"authToken": "************"
}
}
]
},
"primaryColor": "#fefefe"
}
}
And here is my App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AppNavigator from './navigation/AppNavigator';
import { Ionicons } from '#expo/vector-icons';
import Sentry from 'sentry-expo';
// Remove this once Sentry is correctly setup.
Sentry.enableInExpoDevelopment = true;
Sentry.config('https://sentry').install();
export default class App extends React.Component {
state = {
isLoadingComplete: false,
};
async componentDidMount() {
await Font.loadAsync({
'Roboto': require('native-base/Fonts/Roboto.ttf'),
'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
...Ionicons.font,
});
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
Asset.loadAsync([
require('./assets/images/robot-dev.png'),
require('./assets/images/robot-prod.png'),
]),
Font.loadAsync({
// This is the font that we are using for our tab bar
...Icon.Ionicons.font,
// We include SpaceMono because we use it in HomeScreen.js. Feel free
// to remove this if you are not using it in your app
'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
}),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
/navigation/MainTabNavigator.js
import React from 'react';
import {Platform} from 'react-native';
import {createBottomTabNavigator, createStackNavigator} from 'react-navigation';
import TabBarIcon from '../components/TabBarIcon';
import HomeScreen from '../screens/HomeScreen';
import NotificationScreen from '../screens/NotificationScreen';
import SettingsScreen from '../screens/SettingsScreen';
import ProfileScreen from "../screens/ProfileScreen";
import DraftScreen from "../screens/DraftScreen";
import StatsScreen from "../screens/StatsScreen";
import Colors from "../constants/Colors";
import ViewStoryScreen from "../screens/ViewStoryScreen";
import LoginScreen from "../screens/LoginScreen";
import RegisterScreen from "../screens/RegisterScreen";
import MyStoriesScreen from "../screens/MyStories";
import EditStoryScreen from "../screens/EditStoryScreen";
import AddStoryScreen from "../screens/AddStoryScreen";
const headerStyle = {
/* The header config from HomeScreen is now here */
/*
For full list of options
https://reactnavigation.org/docs/en/stack-navigator.html#navigationoptions-for-screens-inside-of-the-navigator
*/
defaultNavigationOptions: {
headerStyle: {
backgroundColor: Colors.headerBackgroundColor,
},
headerTintColor: Colors.headerTintColor,
headerTitleStyle: {
fontWeight: 'bold',
},
headerBackTitleStyle: {color: Colors.headerTintColor},
headerBackStyle: {color: Colors.headerTintColor},
headerBackAllowFontScaling: true,
},
};
const HomeStack = createStackNavigator({
Home: HomeScreen,
ViewStoryScreen: ViewStoryScreen,
EditStory: EditStoryScreen,
AddStory: AddStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
}
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? `ios-home`
: 'md-home'
}
/>
),
};
const NotificationStack = createStackNavigator({
Links: NotificationScreen,
ViewStoryScreen: ViewStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
});
NotificationStack.navigationOptions = {
tabBarLabel: 'Notifications',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={Platform.OS === 'ios' ? 'ios-notifications' : 'md-notifications'}
/>
),
};
const SettingsStack = createStackNavigator({
Settings: SettingsScreen,
Profile: ProfileScreen,
Drafts: DraftScreen,
Stats: StatsScreen,
Login: LoginScreen,
Register: RegisterScreen,
MyStories: MyStoriesScreen,
ViewStoryScreen: ViewStoryScreen,
EditStory: EditStoryScreen,
AddStory: AddStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
}
);
SettingsStack.navigationOptions = {
tabBarLabel: 'Settings',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'}
/>
),
};
export default createBottomTabNavigator({
HomeStack,
NotificationStack,
SettingsStack,
});
Solution
As #Masuk Helal Anik mentioned this is a bug
Here is what worked for me, but had to sacrifice header back title.
In every screen add this
static navigationOptions = ({navigation}) => {
return {
headerLeft: (
<Ionicons
name={Platform.OS === "ios" ? "ios-arrow-back" : "md-arrow-back"}
size={Platform.OS === "ios" ? 35 : 24}
color={Colors.headerTintColor}
style={
Platform.OS === "ios"
? { marginBottom: -4, width: 25, marginLeft: 9 }
: { marginBottom: -4, width: 25, marginLeft: 20 }
}
onPress={() => {
navigation.goBack();
}}
/>
),
title: 'Screen Title'
}
};
It seems to me like a bug. As a solution in this issue is stated
if you use expo you should include the assets from react-navigation in
your assetBundlePatterns so the images are bundled with your app when
you build a standalone app. the easiest way to do this is to just
bundle all assets that your app uses:
https://github.com/expo/new-project-template/blob/6d4c5636de573852dfd2f7715cfa152fd9c84f89/app.json#L20-L22.
to fix it in development mode within expo, you can cache the assets
locally as per this guide. we do this in the navigationplayground
example app, so you can copy that code from here.
There is some workaround also. Try out them to find which one working for you!
I have an Expo app, and I'm trying to handle push notifications sent while the app is in the foreground. It works fine in Android, but iOS it's crashing the app as it's received.
I have a push notification being sent from a Rails server:
params = ({
to: token.expo_push_token,
title: user.first_name,
sound: "default",
body: msg.body,
})
puts params
puts params.class
x = Net::HTTP.post_form(URI.parse('https://exp.host/--/api/v2/push/send'), params)
puts x.body
I can see in the server it sends:
Hash
app[worker.1]: {"data":{"id":"9058abf3-7352-4181-a69d-0b5fc8a8525c","status":"ok"}}
4 TID-godk4ew98 ExpoPushWorker JID-51b823f8feeaf42c313e392e INFO: done: 2.005 sec
And if the app is closed, the push notification appears on the lock screen. If the app is open in the foreground, nothing happens.
I want to listen for notifications when the app is open, and I have this in App.js:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import MessagesScreenRouter from './app/components/Router/MessagesScreenRouter';
import Sentry from 'sentry-expo';
import reducers from './app/reducers';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { Notifications } from 'expo';
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
notification: {},
}
this._handleNotification = this._handleNotification.bind(this)
}
_handleNotification = (notification) => {
console.log(notification)
this.setState({notification: notification});
};
componentWillUnmount(){
this._notificationSubscription && this._notificationSubscription.remove()
}
componentDidMount() {
this.notificationSubscription = Notifications.addListener(
(notification) => this._handleNotification(notification),
);
}
render() {
return (
<View style={{flex:1}}>
<StatusBar hidden={true} />
<View style={{height: 50, justifyContent: 'center', alignItems: 'center'}}>
<Text>Origin: {this.state.notification.origin}</Text>
<Text>Data: {JSON.stringify(this.state.notification)}</Text>
</View>
<Provider store={createStore(reducers)}>
<MessagesScreenRouter/>
</Provider>
</View>
);
}
}
I've tried many suggestions from tutorials all day, but I can not get this to work. What am I missing here?
You can't test it on simulator as expo docs states
Note: iOS and Android simulators cannot receive push notifications. To test them out you will need to use a real-life device. Additionally, when calling Permissions.askAsync on the simulator, it will resolve immediately with "undetermined" as the status, regardless of whether you choose to allow or not.
Just run exp publish and test it on expo client. Also you have to call for permission using Permissions.askAsync in the first place.
Doc's sample work like a charm, check it out: https://docs.expo.io/versions/v28.0.0/guides/push-notifications#__next
Expo has likely been updated in that aspect since then, and now it might even be using the command you have mentioned in the comment (exp start -m tunnel). As foreground notifications are still not available on iOS so far (which might have even caused your issue in the first place), this answer is rather for people looking to implement push notifications than fixing the issue above.
I have created a file downloader and previewer that shows both internal and external notifications on both OSes without running into such issues. The code is available on GitHub and an elaboration is given in this SO answer.
The most relevant code for this post is in regard to the use of local notifications from Expo while the app is in background, and showing them in foreground using toasts from the react-native-toast package. This functionality may be replaceable by Expo Notifications once this feature gets implemented.
For completeness, here is the code for the project:
App.js
import React, { Component } from 'react';
import { View, ScrollView, StyleSheet, Button, Alert, Platform, Text, TouchableWithoutFeedback } from 'react-native';
import { FileSystem, Constants, Notifications, Permissions } from 'expo';
import Toast, {DURATION} from 'react-native-easy-toast';
async function getiOSNotificationPermission() {
const { status } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
if (status !== 'granted') {
await Permissions.askAsync(Permissions.NOTIFICATIONS);
}
}
export default class App extends Component {
constructor(props) {
super(props);
// this.toast = null;
this.listenForNotifications = this.listenForNotifications.bind(this);
// this.openFile = this.openFile.bind(this);
this.state = {
filePreviewText: ''
}
}
_handleButtonPress = () => {
let fileName = 'document.txt';
let fileUri = FileSystem.documentDirectory + fileName;
FileSystem.downloadAsync(
"https://raw.githubusercontent.com/expo/expo/master/README.md",
fileUri
).then(({ uri }) => {
console.log('Finished downloading to ', uri);
const localnotification = {
title: 'Download has finished',
body: fileName + " has been downloaded. Tap to open file.",
android: {
sound: true,
},
ios: {
sound: true,
},
data: {
fileUri: uri
},
};
localnotification.data.title = localnotification.title;
localnotification.data.body = localnotification.body;
let sendAfterFiveSeconds = Date.now();
sendAfterFiveSeconds += 3000;
const schedulingOptions = { time: sendAfterFiveSeconds };
Notifications.scheduleLocalNotificationAsync(
localnotification,
schedulingOptions
);
})
.catch(error => {
console.error(error);
Alert.alert(error);
});
};
listenForNotifications = () => {
const _this = this;
Notifications.addListener(notification => {
if (notification.origin === 'received') {
// We could also make our own design for the toast
// _this.refs.toast.show(<View><Text>hello world!</Text></View>);
const toastDOM =
<TouchableWithoutFeedback
onPress={() => {this.openFile(notification.data.fileUri)}}
style={{padding: '10', backgroundColor: 'green'}}>
<Text style={styles.toastText}>{notification.data.body}</Text>
</TouchableWithoutFeedback>;
_this.toast.show(toastDOM, DURATION.FOREVER);
} else if (notification.origin === 'selected') {
this.openFile(notification.data.fileUri);
}
// Expo.Notifications.setBadgeNumberAsync(number);
// Notifications.setBadgeNumberAsync(10);
// Notifications.presentLocalNotificationAsync(notification);
// Alert.alert(notification.title, notification.body);
});
};
componentWillMount() {
getiOSNotificationPermission();
this.listenForNotifications();
}
componentDidMount() {
// let asset = Asset.fromModule(md);
// Toast.show('Hello World');
}
openFile = (fileUri) => {
this.toast.close(40);
console.log('Opening file ' + fileUri);
FileSystem.readAsStringAsync(fileUri)
.then((fileContents) => {
// Get file contents in binary and convert to text
// let fileTextContent = parseInt(fileContents, 2);
this.setState({filePreviewText: fileContents});
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.buttonsContainer}>
<Button style={styles.button}
title={"Download text file"}
onPress={this._handleButtonPress}
/>
<Button style={styles.button}
title={"Clear File Preview"}
onPress={() => {this.setState({filePreviewText: ""})}}
/>
</View>
<ScrollView style={styles.filePreview}>
<Text>{this.state.filePreviewText}</Text>
</ScrollView>
<Toast ref={ (ref) => this.toast=ref }/>
</View>
);
// <Toast
// ref={ (ref) => this.toast=ref }
// style={{backgroundColor:'green'}}
// textStyle={{color:'white'}}
// position={'bottom'}
// positionValue={100}
// opacity={0.8}
// />
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
buttonsContainer: {
flexDirection: 'row',
},
button: {
flex: 1
},
filePreview: {
flex: 1,
padding: 10,
},
toastText: {
color: 'white',
padding: 5,
justifyContent: 'flex-start',
},
});
package.json: Currently using forked repo, switch back when feature becomes available.
{
"name": "local-notification-with-ios",
"version": "0.0.0",
"description": "No description",
"author": null,
"private": true,
"main": "node_modules/expo/AppEntry.js",
"dependencies": {
"expo": "^32.0.0",
"react": "16.5.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
"prop-types": "^15.5.7",
"react-native-easy-toast": "git+https://github.com/SiavasFiroozbakht/react-native-easy-toast.git#6eed52f4d64c796cb49bdafcd7b3986cf5975d62"
}
}