Header back button disappears on stand alone build - react-native

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!

Related

Splash Screen white flicker before loading (React Native Expo)

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}
/>

withNavigation can only be used on a view hierarchy - Error when using FluidNavigation instead createStackNavigation

I got this issue today morning when I was trying to implement a Fluid Navigation between scenes using react-native-fluid-transitions. After I replace createStackNavigator for createFluidNavigator or FluidNavigator I got the screen issue below:
Invariant Violation: withNavigation can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.
Here are my files
index.js
import React from 'react';
import { Platform } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
} from 'react-navigation';
import screens from '../screens';
import AppDrawer, {
contentOptions as appDrawerContentOptions,
} from '../components/AppDrawer';
import { createFluidNavigator, FluidNavigator } from 'react-navigation-fluid-transitions';
// We already have a status bar from expo
if (Platform.OS === 'android') {
SafeAreaView.setStatusBarHeight(0);
}
const defaultStack = createFluidNavigator (
{
Home: {
screen: screens.CpfCheckScreen
},
Login: {
screen: screens.LoginScreen
},
SelectAssociate: {
screen: screens.SelectAssociateScreen
},
EmailRegister: {
screen: screens.EmailRegisterScreen
},
AssociateSelected: {
screen: screens.AssociatedSelectedScreen
},
Welcome: {
screen: screens.WelcomeScreen
},
Survey: {
screen: screens.SurveyScreen
},
WaitingIndication: {
screen: screens.WaitingIndicationScreen
},
BindingSuccess: {
screen: screens.BindingSuccessScreen
},
AuthenticationSteps: {
screen: screens.AuthenticationStepsScreen
},
ProcessingData: {
screen: screens.ProcessingDataScreen
},
SignaturePicture: {
screen: screens.SignaturePictureScreen
},
// CpfPicture: {
// screen: screens.CpfPictureScreen
// },
ConfirmData: {
screen: screens.ConfirmDataScreen
},
DataValidation: {
screen: screens.DataValidationScreen
},
MembershipTerms: {
screen: screens.MembershipTermsScreen
},
SuccessfullRegistration: {
screen: screens.SuccessfullRegistrationScreen
},
ApprovedRegistration: {
screen: screens.ApprovedRegistrationScreen
},
FailedRegistration: {
screen: screens.FailedRegistrationScreen
},
SecurityQuestion:
{
screen: screens.SecurityQuestionScreen
},
AssociatedLoginScreen: {
screen: screens.AssociatedLoginScreen
},
NewAssociateQuizScreen: {
screen: screens.NewAssociateQuizScreen
},
NewAssociateSuccessScreen: {
screen: screens.NewAssociateSuccessScreen
}
},
{
initialRouteName: 'AssociatedLoginScreen',
headerMode: 'none',
}
)
export default defaultStack;
screens.js
import InitializeScreen from './InitializeScreen';
import LoginScreen from './LoginScreen';
import EmailRegisterScreen from './EmailRegisterScreen';
import CpfCheckScreen from './CpfCheckScreen'
import SelectAssociateScreen from './SelectAssociateScreen'
import AssociatedSelectedScreen from './AssociatedSelectedScreen'
import WelcomeScreen from './WelcomeScreen'
import SurveyScreen from './SurveyScreen'
import WaitingIndicationScreen from './WaitingIndicationScreen'
import BindingSuccessScreen from './BindingSuccessScreen'
import AuthenticationStepsScreen from './AuthenticationStepsScreen'
import SignaturePictureScreen from './SignaturePictureScreen'
import ConfirmDataScreen from './ConfirmDataScreen'
// import RgPictureScreen from './RgPictureScreen'
// import CpfPictureScreen from './CpfPictureScreen'
import ProcessingDataScreen from './ProcessingDataScreen'
import DataValidationScreen from './DataValidationScreen'
import MembershipTermsScreen from './MembershipTermsScreen'
import SuccessfullRegistrationScreen from './SuccessfullRegistrationScreen'
import ApprovedRegistrationScreen from './ApprovedRegistrationScreen'
import FailedRegistrationScreen from './FailedRegistrationScreen'
import SecurityQuestionScreen from './SecurityQuestionScreen'
import AssociatedLoginScreen from './AssociatedLoginScreen'
import NewAssociateQuizScreen from './NewAssociateQuizScreen'
import NewAssociateSuccessScreen from './NewAssociateSuccessScreen'
/*
import ModalScreen from './ModalScreen';
import DetailsRegisterScreen from './DetailsRegisterScreen';
import DashboardScreen from './DashboardScreen';
import TaskDetailsScreen from './TaskDetailsScreen';
import TasksScreen from './TasksScreen';
import EventScreen from './EventScreen';
import TaskStepsScreen from './TaskStepsScreen';
import QRScanScreen from './QRScanScreen';
import TaskSuccessScreen from './TaskSuccessScreen';
import LogoutScreen from './LogoutScreen';
import ProductScreen from './ProductScreen';
import ProductQRScanScreen from './ProductQRScanScreen';
import ProductSuccessScreen from './ProductSuccessScreen';
import ContinueAsScreen from './ContinueAsScreen';
import UserProfileScreen from './UserProfileScreen';
import SocialPageLikeTaskScreen from './SocialPageLikeTaskScreen';
*/
export default {
InitializeScreen,
LoginScreen,
EmailRegisterScreen,
CpfCheckScreen,
SelectAssociateScreen,
AssociatedSelectedScreen,
WelcomeScreen,
SurveyScreen,
WaitingIndicationScreen,
BindingSuccessScreen,
AuthenticationStepsScreen,
SignaturePictureScreen,
// RgPictureScreen,
// CpfPictureScreen,
ConfirmDataScreen,
ProcessingDataScreen,
DataValidationScreen,
MembershipTermsScreen,
SuccessfullRegistrationScreen,
ApprovedRegistrationScreen,
FailedRegistrationScreen,
SecurityQuestionScreen,
AssociatedLoginScreen,
NewAssociateQuizScreen,
NewAssociateSuccessScreen
/*
ModalScreen,
DetailsRegisterScreen,
DashboardScreen,
TaskDetailsScreen,
TasksScreen,
EventScreen,
TaskStepsScreen,
QRScanScreen,
TaskSuccessScreen,
LogoutScreen,
ProductScreen,
ProductQRScanScreen,
ProductSuccessScreen,
ContinueAsScreen,
UserProfileScreen,
SocialPageLikeTaskScreen,*/
};
NewAssociateQuizForm
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Text } from 'native-base';
import { withFormik } from 'formik';
import { withNavigation } from 'react-navigation';
import yup from 'yup';
import { withApollo } from 'react-apollo';
import Auth from '../lib/auth';
import Button from './Button';
import colors from '../config/colors';
import { moderateScale } from '../config/scaling';
import { Transition } from 'react-navigation-fluid-transitions'
import Carousel, { Pagination } from 'react-native-snap-carousel';
import FormField from './FormField';
import ItemInput from './ItemInput';
import AlertContainer from './AlertContainer';
const enhancer = withFormik({
validationSchema: yup.object().shape({
name: yup
.string()
.required('Este campo é obrigatório'),
}),
mapPropsToValues: props => ({
associate: '',
}),
handleSubmit: (values, { props: { client, navigation }, setSubmitting }) => {
const { associate } = values;
console.log('handling submit')
if (!associate ) {
AlertContainer.show({
message: 'Sócio inexistente',
buttonText: 'Tentar novamente',
});
setSubmitting(false);
return;
}
Auth.loginRequest(associate)
.then(res => {
if (!res || !res.data || !res.data.token) {
// We can't tell much to the user
console.log('not working')
AlertContainer.show({
message: 'Erro interno',
buttonText: 'Tentar novamente',
});
return;
}
const { token } = res.data;
console.log('working')
Auth.login(token, client).then(() => navigation.navigate('App'));
})
.catch(error => {
console.log('error on login request', error);
const { response } = error;
if (response) {
// HTTP Request response
const { status } = response;
let message = 'Erro interno';
if (status === 400) {
message = 'Dados inválidos';
}
if (status === 401) {
message = 'Sócio inválido';
}
AlertContainer.show({
message,
buttonText: 'Tentar novamente',
});
} else {
// Raw error message
const errorString = error.toString();
if (errorString && errorString.indexOf('Network Error') >= 0) {
AlertContainer.show({
message: 'Erro de conexão',
buttonText: 'Tentar novamente',
});
}
}
})
.finally(() => setSubmitting(false));
},
});
const AssociateForm = props => {
const {
values,
touched,
errors,
handleSubmit,
setFieldValue,
setFieldTouched,
navigation,
isSubmitting,
} = props;
const renderItem = () => {
return (
<FormField
validateStatus={getValidateStatus('associate')}
error={getError('associate')}
>
<ItemInput
placeholder="Nome e sobrenome..."
onChangeText={text => {
setFieldValue('associate', text)
}}
onBlur={() => setFieldTouched('associate')}
initialValue={values.associate}
maxLength={32}
/>
</FormField>
)
}
const pagination = () => {
return (
<Pagination
dotsLength={3}
activeDotIndex={0}
containerStyle={{ backgroundColor: 'transparent' }}
dotStyle={{
width: 10,
height: 10,
borderRadius: 5,
marginHorizontal: 8,
backgroundColor: '#a18037'
}}
inactiveDotStyle={{
// Define styles for inactive dots here
}}
inactiveDotOpacity={0.4}
inactiveDotScale={0.6}
/>
);
}
const getValidateStatus = param => {
return errors[param] && touched[param]
? 'error'
: touched[param]
? 'success'
: '';
};
const getError = param => {
return errors[param] && touched[param] ? errors[param] : '';
};
return (
<View style={styles.container}>
<Text style={styles.title}>
Responda o questionário
</Text>
<Text style={styles.subtitle}>
{`Para ser um sócio Fiduc você precisa responder a este questionário e entraremos em contato para uma entrevista`}
</Text>
<View>
{ renderItem() }
{ pagination() }
</View>
<Button
primary
style={styles.submitButton}
onPress={ () => { navigation.navigate('NewAssociateSuccessScreen') }}
disabled={isSubmitting}
loading={isSubmitting}
>
<Text
style={[
styles.submitButtonText,
isSubmitting && { color: colors.secondary_text },
]}
uppercase={false}
>
Receber Indicação
</Text>
</Button>
</View>
);
};
export default withNavigation(withApollo(enhancer(AssociateForm)));
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
submitButton: {
height: moderateScale(20),
borderRadius: 0,
justifyContent: 'center',
marginTop: moderateScale(50),
paddingHorizontal: 48,
alignSelf: 'center',
},
submitButtonText: {
color: colors.button_text,
fontSize: moderateScale(18),
textAlign: 'center',
alignSelf: 'center',
},
whiteText: {
color: colors.secondary_text,
},
title: {
color: colors.text,
fontSize: moderateScale(23),
textAlign: 'center',
marginBottom: 8
},
subtitle: {
color: colors.text,
fontSize: moderateScale(12),
textAlign: 'justify',
marginBottom: 30
},
description: {
color: colors.secondary_text,
fontSize: moderateScale(16),
textAlign: 'center',
paddingVertical: moderateScale(10),
},
wrapper: {
}
});
package.json
{
"name": "fidelidade-app",
"version": "0.1.0",
"private": true,
"devDependencies": {
"babel-eslint": "^8.2.3",
"eslint": "^4.19.1",
"eslint-config-react-app": "^2.1.0",
"eslint-config-universe": "git+https://git#github.com/expo/eslint-config-universe",
"eslint-plugin-babel": "^5.1.0",
"eslint-plugin-flowtype": "^2.46.3",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-react": "^7.8.2",
"jest-expo": "26.0.0",
"prettier": "^1.12.1",
"react-native-scripts": "1.13.2",
"react-test-renderer": "16.3.0-alpha.1"
},
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "jest",
"postinstall": "patch -p0 < apollo.patch || true",
"lint": "eslint ."
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"apollo-boost": "^0.1.4",
"axios": "^0.18.0",
"expo": "^27.0.0",
"formik": "^0.11.11",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.1",
"moment-timezone": "^0.5.17",
"native-base": "^2.4.2",
"react": "16.3.1",
"react-apollo": "^2.1.3",
"react-native": "https://github.com/expo/react-native/archive/sdk-27.0.0.tar.gz",
"react-native-datepicker": "^1.7.2",
"react-native-easy-grid": "^0.1.17",
"react-native-gesture-password": "^0.3.4",
"react-native-maps-directions": "^1.6.0",
"react-native-masked-text": "^1.7.2",
"react-native-modal": "^6.1.0",
"react-native-scrollable-tab-view": "^0.8.0",
"react-native-snap-carousel": "^3.7.3",
"react-native-svg": "^6.5.2",
"react-native-swiper": "^1.5.13",
"react-navigation": "^2.0.1",
"react-navigation-fluid-transitions": "^0.2.6",
"yup": "^0.24.1"
}
}

StackNavigator and TabNavigaor is deprecated in react-native

In my project, after updating node modules, react navigation showed some issues. In the console it says,
The tabBarBottom is deprecated. Use react-navigation-tabs package.
stackNavigator function name is deprecated. Use createStackNavigator
tabNavigator is deprecated. Use createBottomTabNavigator
import React from "react";
import { StackNavigator } from "react-navigation";
import { TabNavFooter } from "./TabNavFooter";
import { SIGNIN_KEY, SIGNUP_KEY } from "../config/routeKeys";
import {
SignupScreen,
SigninScreen,
MainFeedScreen,
CommentScreen,
SharePostScreen
} from "../screens";
export const Routes = StackNavigator({
mainfeed: { screen: TabNavFooter },
signin: { screen: SigninScreen },
signup: { screen: SignupScreen },
comments: { screen: CommentScreen },
sharePost: { screen: SharePostScreen }
});
import React from "react";
import { TabNavigator, TabBarBottom } from "react-navigation";
import { ClickableImage, ClickableIcon } from "../mixing/UI";
import TAB_NAVIGATOR_IMAGES from "../config/tabNavImgs";
import { Image } from "react-native";
import {
MainFeedScreen,
WorkoutScreen,
VideosScreen,
ChatScreen,
ProfileMainScreen
} from "../screens";
export const TabNavFooter = TabNavigator(
{
mainfeed: { screen: MainFeedScreen },
workout: { screen: WorkoutScreen },
video: { screen: VideosScreen },
chat: { screen: ChatScreen },
profile: { screen: ProfileMainScreen }
},
{
navigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, tintColor }) => {
const { routeName } = navigation.state;
let imageSource;
if (routeName === "mainfeed") {
imageSource =
TAB_NAVIGATOR_IMAGES[
`${focused ? "mainfeedActive" : "mainfeedInactive"}`
];
} else if (routeName === "workout") {
imageSource =
TAB_NAVIGATOR_IMAGES[
`${focused ? "workoutActive" : "workoutInactive"}`
];
} else if (routeName === "video") {
imageSource =
TAB_NAVIGATOR_IMAGES[
`${focused ? "videoActive" : "videoInactive"}`
];
} else if (routeName === "chat") {
imageSource =
TAB_NAVIGATOR_IMAGES[`${focused ? "chatActive" : "chatInactive"}`];
} else if (routeName === "profile") {
imageSource =
TAB_NAVIGATOR_IMAGES[
`${focused ? "profileActive" : "profileInactive"}`
];
}
return (
<Image
source={imageSource}
style={{
width: 25,
height: 25,
tintColor: tintColor,
marginBottom: 0
}}
/>
);
}
}),
tabBarComponent: TabBarBottom,
tabBarPosition: "bottom",
tabBarOptions: {
activeTintColor: "blue",
inactiveTintColor: "gray"
},
swipeEnabled: false,
lazyLoad: true,
animationEnabled: false
}
);
How can I solve these errors?
It's due to upgrade in react-navigation version. you have probably upgraded to v2 of the package.
They have documentation for that version but still not complete and lack in some minute details. you can see the doc in this link
the configuration differs between v1 and v2. you could manage to get v2 work with some difficulties. you can ask specific difficulties you face in that process here or in some other question. But if you find still tough, you can move back to lower version which is well documented.
Replace StackNavigator with createStackNavigator. And TabNavigator with createBottomTabNavigator.
TabBarBottom I have not used yet, but it looks like it was put into its own package called react-navigation-tabs that needs to be installed and pulled from there instead.

React Native - Creating Navigator dynamically with React Navigation

I am building a mobile application with React Native and I am using React Navigation to build a navigator inside my application. React navigation provided me a good way to handle nested Tab bars inside a drawer which is also inside a Stack Navigator.
The problem is that I need to specify components so that I can provide these into the Tab Bar. Lets say we have to fetch some categories from an API and we do not know how many categories are inside the data. Besides, I could not figure out that even if I try to fetch data at start, the navigator and redux configuration takes place at start which means the application has to know the components in those tab navigators. I could not figure out that even if I fetched the data from the API, how I can create multiple components and while stopping the application configuration.
The code below, just demonstrates how I implemented the tab bar. This code works in index.js because as I mentioned before, the application have to know the components inside the Navigator.
const TabStack = TabNavigator({
Food: { screen: FoodStack},
Drink : { screen: DrinkStack },
HealthCare : { screen: SnackProducts },
Snacks: { screen: SnackStack },
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
Thanks
here the root code
import { AppRegistry } from 'react-native';
import React from 'react';
import { Text, Image, ScrollView, View, List, ListItem, TouchableWithoutFeedback } from 'react-native';
import { Icon, Avatar } from 'react-native-elements';
import { Provider, connect } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware, combineReducers } from 'redux';
import {
addNavigationHelpers, StackNavigator,
DrawerNavigator,
DrawerItems,
TabNavigator,
TabView,
TabBarTop,
NavigationActions
} from 'react-navigation';
// importing starting screen
import StartingContainer from './src/containers/StartingScreen/StartingContainer';
// Menu Containers
import MenuCredentials from './src/containers/MenuCredentials';
// Containers
import LoginContainer from './src/containers/LoginContainer';
import PhoneNumberValidation from './src/containers/SubLoginContainers/PhoneNumberValidation';
import MainOrderContainer from './src/containers/OrderContainers/MainOrderContainer';
import MainCartContainer from './src/containers/CartContainers/MainCartContainer';
// Components
// Login Components
import SMSLogin from './src/containers/SubLoginContainers/SMSLogin';
// Profil Components
import Profil from './src/components/ProfileComponents/Profile';
import AdressComponent from './src/components/ProfileComponents/AdressComponent';
import SettingsComponent from './src/components/ProfileComponents/SettingsComponent';
import creditCardComponent from './src/components/ProfileComponents/creditCardComponent';
// Reducers
import initialReducer from './src/reducers/initialReducer';
import cartReducer from './src/reducers/cartReducer';
import starterReducer from './src/reducers/starterReducer';
// import tab bar containers
import FoodProducts from './src/containers/TabBarContainers/FoodProducts';
import HealthProducts from './src/containers/TabBarContainers/HealthProducts';
import SnackProducts from './src/containers/TabBarContainers/SnackProducts';
// Building Navigation
import MenuItem from './src/containers/MenuItemContainer/MenuItem';
import CustomTabItems from './src/containers/CustomTabItems';
import CustomSubTabItems from './src/containers/CustomSubTabItems';
import DrawerButton from './src/containers/DrawerButton';
// Tab Bar Navigation
const ChocolateStack = TabNavigator({
Tadelle: { screen: MenuItem},
Milka: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Tadelle',
swipeEnabled: true,
tabBarOptions: {
scrollEnabled: true
},
})
const SnackStack = TabNavigator({
Çikolatalar: { screen: MenuItem},
Gofretler: { screen: MenuItem},
Krakerler: { screen: MenuItem},
Bisküviler: { screen: MenuItem},
Kuruyemişler: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Çikolatalar',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const DrinkStack = TabNavigator({
'Gazlı İçecekler': { screen: MenuItem},
'Soğuk Çaylar': { screen: MenuItem},
'Alkol': { screen: MenuItem},
'Süt Ürünleri': { screen: MenuItem},
'Spor İçecekleri': { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Alkol',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const FoodStack = TabNavigator({
Sandviç : { screen: MenuItem},
Çorba: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Sandviç',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const TabStack = TabNavigator({
Food: { screen: FoodStack},
Drink : { screen: DrinkStack },
Health : { screen: SnackProducts },
Snacks: { screen: SnackStack },
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
tabStyle : {
width : 250
},
scrollEnabled : true
}
})
// cart navigation will be drawernavigator and drawerItems will be custom !!
const CartNavigation = StackNavigator({
Cart: {
screen: MainCartContainer,
}
},
{
headerMode: 'float',
navigationOptions: ({ navigation }) => ({
title: 'Sepet',
headerLeft: <Icon
name='arrow-back'
color='#517fa4'
onPress={() => navigation.navigate('drawerStack')}
/>,
headerRight:
<Icon
name='payment'
color='#517fa4'
onPress={() => navigation.navigate('drawerStack')}
/>
})
}
)
const DrawerStack = DrawerNavigator({
Sipariş: { screen: TabStack },
Profil: {
screen: Profil ,
navigationOptions : ({ navigation }) => ({
title : 'Profilim',
})
},
Adreslerim: {
screen: AdressComponent,
navigationOptions: ({ navigation }) => ({
title: 'Teslimat Adreslerim'
})
},
Ayarlar: { screen: SettingsComponent }
},
{
drawerPosition: 'left',
headerMode : 'none',
navigationOptions: ({ navigation }) => ({
headerStyle: { backgroundColor: '#87CEFA' },
headerRight: <Icon
name='shopping-cart'
color='#517fa4'
onPress={() => navigation.navigate('cartStack')}
/>,
}),
contentOptions: {
inactiveTintColor: 'white',
activeTintColor: 'purple',
style: {
marginTop: 80,
marginLeft: 25,
}
},
contentComponent: props => <MenuCredentials {...props} />
})
const DrawerNavigation = StackNavigator({
DrawerStack: {
screen: DrawerStack
}},
{
style : {
leftDrawerWidth : 40
},
index : 0,
navigationOptions : ({ navigation }) => ({
headerStyle: { backgroundColor: '#87CEFA' },
gesturesEnabled : false,
headerRight : <Icon
name='shopping-cart'
color='#517fa4'
onPress={() => navigation.navigate('cartStack')}
/>,
headerLeft: <Icon
name='menu'
color='#517fa4'
onPress={() => {
console.log(navigation.state.routes[0]);
navigation.navigate({
key : null,
index : 0,
action : [
navigation.navigate('DrawerToggle')
]
})
}}
/>
}),
initialRouteParams : {
name : 'Welcome'
}
}
)
const LoginStack = StackNavigator({
Login: {
screen: LoginContainer,
navigationOptions: ({ navigation }) => ({
title: ' GİZLİ UYGULAMA ! '
})
},
Ss: {
screen: SMSLogin,
navigationOptions: ({ navigation }) => ({
title: ' SMS ONAYI '
})
},
PhoneNumberValidation: {
screen: PhoneNumberValidation,
navigationOptions: ({ navigation }) => ({
title: 'Kaydolma'
})
},
},{
headerMode : 'none',
initialRouteName : 'Login'
})
// IMPORTANT NOTE ***!!!
// CARRY drawerStack to the PrimaryNavigator !!
// CHANGE LoginContainer so that it will navigate to the drawerStack
// NOT FROM ACTION BUT FROM COMPONENT INSIDE COMPONENTWILLUPDATE
// BY CHANGING isAuth variable in initialReducer !!
const PrimaryNavigator = StackNavigator({
loginStack: {
screen: LoginStack
},
cartStack: {
screen: CartNavigation
},
drawerStack: {
screen: DrawerNavigation
},
starter : {
screen : StartingContainer
}
},
{
headerMode: 'none',
title: 'Main',
initialRouteName : 'starter'
}
)
const navReducer = (state, action) => {
const nextState = PrimaryNavigator.router.getStateForAction(action, state);
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
};
// combining Reducers
const AppReducer = combineReducers({
initialR: initialReducer,
cartR: cartReducer,
starterR : starterReducer,
nav: navReducer
})
// Creating redux store
const store = createStore(
AppReducer,
applyMiddleware(thunkMiddleware)
)
// Navigation initilizator to App
class App extends React.Component {
render() {
return (
<PrimaryNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
const AppWithNavigationState = connect(mapStateToProps)(App);
class brilliantApp extends React.Component{
render(){
return(
<Provider store={store}>
< AppWithNavigationState />
</Provider>
)
}
}
AppRegistry.registerComponent('brilliantApp', () => brilliantApp);
Your TabStack file:
const CATEGORIES = {
"Food": { screen: FoodStack },
// ...
}
export default (screenNames) => {
const screens = screenNames.reduce((total, name) => ({...total, [name]: CATEGORIES[name]}), {})
const TabStack = TabNavigator(screens,
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
return TabStack
}
Your Root file:
import getTabStack from './TabStack'
class Root extends Component {
state = {
categoriesNames: null
}
componentWillMount() {
// assuming result is ["Food", "Drink", ... ]
Api.fetchCategories().then((result) => {
this.setState({ categoriesNames: result })
})
}
render() {
const { categoriesNames } = this.state
if (!categoriesNames) {
return <SplashScreen />
}
const TabStack = getTabStack(categoriesNames)
return (
<Provider store={store} >
<TabStack />
</Provider>
);
}
}
Here I would like to post a method for creating tab bar according to the data we fetched from some API etc programmatically.
Here we fetch the data from API in this example, this code from the top level component :
renderShopTab() {
const { client } = this.props;
try {
const { categories } = client.readQuery({
query: gql`
{
categories{
id
name
products{
id
name
price
quantity
}
}
}`
})
console.log("Categories :" + categories);
return (
<ShopCreator categories={categories} />
)
} catch (error) {
console.log("Error occured creating the categories due to the : " + error);
return (
<View>
<Text>
Loading...
</Text>
</View>
)
}
}
This code snippet is from creator of the tab bar dynamically :
export const ShopCreator = ({ categories }) => {
// This script will create a TabNavigator for categories and StackNavigators for each member of the categories !
let categoryStack = {};
let routes = {};
categories.forEach((category) => {
if (category.products.length > 0) {
const { catname } = category.name;
if (category.name != undefined) {
routes[category.name] = {
screen: StackNavigator({
'isim': {
screen: ProductPage
}
},{
headerMode : 'none',
initialRouteParams : {
categoryName : category.name,
products : category.products
}
})
}
}
} else {
console.log("This category has no products !");
}
})
console.log("OHA : " + JSON.stringify(routes));
const ShopCatTabNav = TabNavigator(routes, {
tabBarPosition: 'top',
tabBarComponent: props => <TabMenuItems props={props} />
})
return <ShopCatTabNav />
}
As last , I will show you customized tab navigation bar I built :
const TabMenuItems = ({props}) => {
const { activeTintColor, tab, tabbar, tabText, inactiveTintColor } = styles;
const { index } = props.navigation.state;
return(
<View>
<ScrollView contentContainerStyle={{ flex : 1 }} horizontal showsHorizontalScrollIndicator={false} style={{backgroundColor : '#FFAEB9'}}>
{
props.navigation.state.routes.length ? (
props.navigation.state.routes.map((route,number)=>{
const focused = ( index === number ) ? '#1874CD' : '#FF6A6A';
const tintColor = focused ? activeTintColor : inactiveTintColor;
return (
<TouchableWithoutFeedback
key={route.key}
onPress={() => {
props.jumpToIndex(number)
}}
delayPressIn={0}
>
<View style={{marginLeft : 20, marginTop : height / 40, shadowOpacity : 25, alignSelf : 'flex-start' }}>
<Text style={{borderRadius : 5, fontWeight : 'bold', borderWidth :2, paddingTop : 5,color : 'white', height : height/18, width : width/5,textAlign : 'center', backgroundColor : focused, borderStyle: 'dashed',borderColor : '#CD2626'}}>
{props.getLabel({route, number})}
</Text>
</View>
</TouchableWithoutFeedback>
)
})
) : null
}
</ScrollView>
</View>
)
}
export default TabMenuItems;
It's been a while this q was posted but if there are still some people looking at this. I'd rather using react-native-navigation instead this library.

Change loading icon size for different DPI deivces

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.