Ask for permission in specific screen in android - react-native

For react native application navigation I use react-native-navigation v2 here I've created navigation with bottomTabs. Here is the navigation handler
import { Navigation } from "react-native-navigation";
import { width, height } from "../utils/screenResolution";
import { bottomTabIcon, topBarOpts } from "../components";
const sideBarWidth = width * 0.65;
export const goToAuth = () =>
Navigation.setRoot({
root: {
stack: {
id: "AuthNav",
children: [
{
component: {
name: "SignInScreen",
options: {
topBar: { visible: false, height: 0 }
}
}
}
]
}
}
});
export const goHome = async () => {
let icon1 = await bottomTabIcon("CollectionScreen", "Collection", "archive");
let icon2 = await bottomTabIcon("MainScreen", "Home", "home");
let icon3 = await bottomTabIcon("CaptureScreen", "Capture", "camera");
Navigation.setRoot({
root: {
sideMenu: {
right: {
component: {
name: "SideBar"
}
},
center: {
bottomTabs: {
id: "AppNav",
children: [icon1, icon2, icon3]
}
},
options: {
sideMenu: {
right: {
width: sideBarWidth
}
}
}
}
}
});
Navigation.mergeOptions("MainScreen", {
bottomTabs: {
currentTabIndex: 1
}
});
};
Icons tab creating with this bottomTabIcon function.
import Icon from "react-native-vector-icons/FontAwesome";
import { topBarOpts } from "./";
import { PRIMARY, BOTTOM_TAB_BACKGROUND, TAB_ICON } from "../../assets/color";
let bottomTabIcon = async (name, text, iconName) => {
let icon = {
stack: {
children: [
{
component: {
name: name,
id: name,
options: {
bottomTab: {
text: text,
fontSize: 12,
selectedIconColor: PRIMARY,
iconColor: TAB_ICON,
icon: await Icon.getImageSource(iconName, 20)
}
}
}
}
]
}
};
if (name === "CaptureScreen") {
icon.stack.children[0].component.options.bottomTabs = {
visible: false,
drawBehind: true,
animate: true
};
icon.stack.children[0].component.options.topBar = {
visible: false,
height: 0
};
} else {
icon.stack.children[0].component.options.bottomTabs = {
backgroundColor: BOTTOM_TAB_BACKGROUND
};
icon.stack.children[0].component.options.topBar = await topBarOpts("Example");
}
return icon;
};
export { bottomTabIcon };
Here is the problem, when user logging in application,its asking for permissions (camera,audio etc.) in MainScreen I want to do that in specific screen,after that I find out that all screens in bottomTabs mounted.So if I call something to do in componentDidMount in CaptureScreen it will work in MainScreen.How I can solve this part? I am pretty new in react-native so something you can find weird in this code.Thanks for help and for attention.

Have the permission calls only in did mount of the specific screens or in the child screen where it is needed not in the parent screen or in navigators.
In your case you are calling them by taking the route index in the react navigation. Add the permission code on the child screen or where the permissions are required and they will start working.

Related

Storybook.js (Vue) Docs Template Output

Using StoryBook.js, when I navigate to a component, view its "Docs" and click the "Show Code" button, why do I get code that looks like this...
(args, { argTypes }) => ({
components: { Button },
props: Object.keys(argTypes),
template: '<Button v-bind="$props" />',
})
...as opposed to this...
<Button type="button" class="btn btn-primary">Label</Button>
Button.vue
<template>
<button
:type="type"
:class="'btn btn-' + (outlined ? 'outline-' : '') + variant"
:disabled="disabled">Label</button>
</template>
<script>
export default {
name: "Button",
props: {
disabled: {
type: Boolean,
default: false,
},
outlined: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'button',
},
variant: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'success', 'warning', 'danger'].includes(value)
}
}
}
}
</script>
Button.stories.js
import Button from '../components/Button'
export default {
title: 'Button',
component: Button,
parameters: {
componentSubtitle: 'Click to perform an action or submit a form.',
},
argTypes: {
disabled: {
description: 'Make a button appear to be inactive and un-clickable.',
},
outlined: {
description: 'Add a border to the button and remove the fill colour.',
},
type: {
options: ['button', 'submit'],
control: { type: 'inline-radio' },
description: 'Use "submit" when you want to submit a form. Use "button" otherwise.',
},
variant: {
options: ['primary', 'success'],
control: { type: 'select' },
description: 'Bootstrap theme colours.',
},
},
}
const Template = (args, { argTypes }) => ({
components: { Button },
props: Object.keys(argTypes),
template: '<Button v-bind="$props" />',
})
export const Filled = Template.bind({})
Filled.args = { disabled: false, outlined: false, type: 'button', variant: 'primary' }
export const Outlined = Template.bind({})
Outlined.args = { disabled: false, outlined: true, type: 'button', variant: 'primary' }
export const Disabled = Template.bind({})
Disabled.args = { disabled: true, outlined: false, type: 'button', variant: 'primary' }
I thought I followed their guides to the letter, but I just can't understand why the code output doesn't look the way I expect it to.
I simply want any of my colleagues using this to be able to copy the code from the template and paste it into their work if they want to use the component without them having to be careful what they select from the code output.
For anyone else who encounters this issue, I discovered that this is a known issue for StoryBook with Vue 3.
As mine is currently a green-field project at the time of writing this, I put a temporary workaround in place by downgrading Vue to ^2.6.
This is OK for me. I'm using the options API to build my components anyway so I'll happily upgrade to Vue ^3 when Storybook resolve the above linked issue.
One of possible options is to use current workaround that I found in the GH issue mentioned by Simon K https://github.com/storybookjs/storybook/issues/13917:
Create file withSource.js in the .storybook folder with following content:
import { addons, makeDecorator } from "#storybook/addons";
import kebabCase from "lodash.kebabcase"
import { h, onMounted } from "vue";
// this value doesn't seem to be exported by addons-docs
export const SNIPPET_RENDERED = `storybook/docs/snippet-rendered`;
function templateSourceCode (
templateSource,
args,
argTypes,
replacing = 'v-bind="args"',
) {
const componentArgs = {}
for (const [k, t] of Object.entries(argTypes)) {
const val = args[k]
if (typeof val !== 'undefined' && t.table && t.table.category === 'props' && val !== t.defaultValue) {
componentArgs[k] = val
}
}
const propToSource = (key, val) => {
const type = typeof val
switch (type) {
case "boolean":
return val ? key : ""
case "string":
return `${key}="${val}"`
default:
return `:${key}="${val}"`
}
}
return templateSource.replace(
replacing,
Object.keys(componentArgs)
.map((key) => " " + propToSource(kebabCase(key), args[key]))
.join(""),
)
}
export const withSource = makeDecorator({
name: "withSource",
wrapper: (storyFn, context) => {
const story = storyFn(context);
// this returns a new component that computes the source code when mounted
// and emits an events that is handled by addons-docs
// this approach is based on the vue (2) implementation
// see https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
return {
components: {
Story: story,
},
setup() {
onMounted(() => {
try {
// get the story source
const src = context.originalStoryFn().template;
// generate the source code based on the current args
const code = templateSourceCode(
src,
context.args,
context.argTypes
);
const channel = addons.getChannel();
const emitFormattedTemplate = async () => {
const prettier = await import("prettier/standalone");
const prettierHtml = await import("prettier/parser-html");
// emits an event when the transformation is completed
channel.emit(
SNIPPET_RENDERED,
(context || {}).id,
prettier.format(`<template>${code}</template>`, {
parser: "vue",
plugins: [prettierHtml],
htmlWhitespaceSensitivity: "ignore",
})
);
};
setTimeout(emitFormattedTemplate, 0);
} catch (e) {
console.warn("Failed to render code", e);
}
});
return () => h(story);
},
};
},
});
And then add this decorator to preview.js:
import { withSource } from './withSource'
...
export const decorators = [
withSource
]

Setting up react native push notification with wix react native navigation v3 - problem when app is closed

I am sending a notification that navigates the user to a specific screen when the notification is clicked.
This works perfectly when the app is opened or running in the background, however, when the app is closed onNotification is not being called.
I am using react native push notification and wix react native navigation V3.
I notice the problem by putting a console log inside on notification and it was never called.
In index.js I have the follwing code
import { start } from './App';
start();
In App.js
import React from 'react';
import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';
import configureStore from './src/configureStore';
import { configurePush } from './src/utils/push-notifications';
import Login from './src/components/views/Login';
import Home from './src/components/views/Home';
import Cart from './src/components/views/Cart';
import CartDetail from './src/components/views/Cart/Detail';
import Orders from './src/components/views/Orders';
... the rest of the screens
const store = configureStore();
configurePush(store);
export function registerScreens() {
Navigation.registerComponent('provi.Login', () => (props) => (
<Provider store={store}>
<Login {...props} />
</Provider>
), () => Login);
Navigation.registerComponent('provi.Home', () => (props) => (
<Provider store={store}>
<Home {...props} />
</Provider>
), () => Home);
Navigation.registerComponent('provi.Cart', () => (props) => (
<Provider store={store}>
<Cart {...props} />
</Provider>
), () => Cart);
... the rest of the screens
}
export function start() {
registerScreens();
Navigation.events().registerAppLaunchedListener(async () => {
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'provi.Login',
options: {
animations: {
setStackRoot: {
enabled: true
}
},
topBar: {
visible: false,
drawBehind: true,
background: {
color: '#30DD70'
},
},
bottomTabs: {
visible: false
}
}
}
}],
}
}
});
});
}
Then the configuration of the notification is the following:
import PushNotificationIOS from "#react-native-community/push-notification-ios";
import { Navigation } from 'react-native-navigation';
import PushNotification from 'react-native-push-notification';
import DeviceInfo from 'react-native-device-info';
import fetchApi from "../store/api";
import { addNotification } from '../store/notifications/actions';
import { SENDER_ID } from '../constants';
export const configurePush = (store) => {
PushNotification.configure({
onRegister: function(token) {
if (token) {
const registerData = {
token: token.token,
uid: DeviceInfo.getUniqueID(),
platform: token.os
}
// console.log(registerData);
fetchApi('/notificaciones/register', 'POST', registerData).catch(err => console.log(err))
}
},
onNotification: function(notification) {
if (notification) {
store.dispatch(addNotification(notification)); // Almacena la notification
const action = notification.data.click_action;
if (action === 'oferta') {
const remotePost = notification.data.data;
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'provi.Home',
options: {
animations: {
setStackRoot: {
enabled: true
}
},
topBar: {
visible: true,
drawBehind: false,
},
passProps: {
test: 'test',
notification: remotePost
}
}
}
}],
}
}
});
} else if (action === 'seller') {
const remoteSeller = notification.data.data;
Navigation.push('Home', {
component: {
name: 'provi.Seller',
passProps: {
id: remoteSeller._id,
featureImage: remoteSeller.featureImage
},
options: {
topBar: {
title: {
text: 'Nueva Marca!'
}
},
bottomTabs: {
visible: false,
drawBehind: true
}
}
}
});
} else if (action === 'sellerClosingSoon') {
const remoteSeller = notification.data.data;
Navigation.push('Home', {
component: {
name: 'provi.ClosingSoon',
passProps: {
id: remoteSeller._id,
featureImage: remoteSeller.featureImage
},
options: {
topBar: {
title: {
text: 'Marcas que cierran pronto'
}
},
bottomTabs: {
visible: false,
drawBehind: true
}
}
}
});
}
}
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
senderID: SENDER_ID,
popInitialNotification: true,
requestPermissions: true
});
}
I am expecting to see the console.log at least but it's not happening.
What is the correct setup for RNN V3 with RN push notification?
the notifications are two types: background and foreground.
You use foreground notifications. But when an app is closed and you receive a new notification your callback cannot be fired.
You should use something like getInitialNotification to get the notification data.
https://github.com/zo0r/react-native-push-notification/blob/master/component/index.android.js#L19
I hope that helps.
Thanks

How to pass multiple mutations to update Vuex store?

If I mutate the state only once (by committing either of the two mutations shown below), there is no error and chart is updated correctly.
If I run both mutations:
commit('SET_COURSE_MATERIAL', data)
commit('SET_TOOLS_EQUIPMENT', data)
then I get Maximum call stack exceeded: RangeError.
If I comment out the code in the watch property of chart.vue, there are no errors and I can see the state with correct values in console.log
I am getting the error regarding maximum call stack only when I run "npm run dev". When I deploy it to Google Cloud, the site works as expected and I don't get any errors. I even re-checked this by editing some code and re-deploying it twice while also noticing the time in the build logs.
summary.vue
<v-card-text>
<chart
:chart-config.sync="this.$store.state.summary.courseMaterial"
/>
</v-card-text>
...
<v-card-text>
<chart
:chart-config.sync="this.$store.state.summary.toolsEquipment"
/>
</v-card-text>
chart.vue
<template>
<v-flex>
<no-ssr><vue-c3 :handler="handler"/></no-ssr>
</v-flex>
</template>
<script>
import Vue from 'vue'
export default {
name: 'chart',
props: ['chartConfig'],
data() {
return {
handler: new Vue()
}
},
watch: {
chartConfig: function(val) {
console.log('chart component > watch > chartConfig', val)
this.drawChart()
}
},
created() {
this.drawChart()
},
methods: {
drawChart() {
this.handler.$emit('init', this.chartConfig)
}
}
}
</script>
store/summary.js
import axios from 'axios'
import _ from 'underscore'
import Vue from 'vue'
import {
courseMaterialChartConfig,
toolsEquipmentChartConfig,
} from './helpers/summary.js'
axios.defaults.baseURL = process.env.BASE_URL
Object.filter = (obj, predicate) =>
Object.assign(
...Object.keys(obj)
.filter(key => predicate(obj[key]))
.map(key => ({ [key]: obj[key] }))
)
export const state = () => ({
courseMaterial: '',
toolsEquipment: '',
})
export const getters = {
courseMaterial(state) {
return state.courseMaterial
},
toolsEquipment(state) {
return state.toolsEquipment
}
}
export const actions = {
async fetchData({ state, commit, rootState, dispatch }, payload) {
axios.defaults.baseURL = process.env.BASE_URL
let { data: initialData } = await axios.post(
'summary/fetchInitialData',
payload
)
console.log('initialData', initialData)
let [counterData, pieChartData, vtvcData, guestFieldData] = initialData
//dispatch('setCourseMaterial', pieChartData.coureMaterialStatus)
//dispatch('setToolsEquipment', pieChartData.toolsEquipmentStatus)
},
setCourseMaterial({ commit }, data) {
commit('SET_COURSE_MATERIAL', courseMaterialChartConfig(data))
},
setToolsEquipment({ commit }, data) {
commit('SET_TOOLS_EQUIPMENT', toolsEquipmentChartConfig(data))
}
}
export const mutations = {
// mutations to set user in state
SET_COURSE_MATERIAL(state, courseMaterial) {
console.log('[STORE MUTATIONS] - SET_COURSEMATERIAL:', courseMaterial)
state.courseMaterial = courseMaterial
},
SET_TOOLS_EQUIPMENT(state, toolsEquipment) {
console.log('[STORE MUTATIONS] - SET_TOOLSEQUIPMENT:', toolsEquipment)
state.toolsEquipment = toolsEquipment
},
}
helpers/summary.js
export const courseMaterialChartConfig = data => {
return {
data: {
type: 'pie',
json: data,
names: {
received: 'Received',
notReceived: 'Not Received',
notReported: 'Not Reported'
}
},
title: {
text: 'Classes',
position: 'right'
},
legend: {
position: 'right'
},
size: {
height: 200
}
}
}
export const toolsEquipmentChartConfig = data => {
return {
data: {
type: 'pie',
json: data,
names: {
received: 'Received',
notReceived: 'Not Received',
notReported: 'Not Reported'
}
},
title: {
text: 'Job Role Units',
position: 'right'
},
legend: {
position: 'right'
},
size: {
height: 200
}
}
}
Deep copy the chart config.
methods: {
drawChart() {
this.handler.$emit('init', {...this.chartConfig})
}
}

Snapshot test with Jest on nested react-navigation component - Object keys change so Snapshot match fails

When I run my jest test on my StackNavigator.test.js it always fails because of generated keys:
- Snapshot
+ Received
## -79,11 +79,11 ##
fromRoute={null}
index={0}
navigation={
Object {
"_childrenNavigation": Object {
- "**id-1542980055400-0**": Object {
+ "**id-1542980068677-0**": Object {
"actions": Object {
"dismiss": [Function],
"goBack": [Function],
"navigate": [Function],
"pop": [Function],
## -109,11 +109,11 ##
"replace": [Function],
"reset": [Function],
"router": undefined,
"setParams": [Function],
"state": Object {
- "key": "**id-1542980055400-0**",
+ "key": "**id-1542980068677-0**",
"routeName": "SignInOrRegister",
},
},
},
"actions": Object {
## -157,15 +157,15 ##
},
"setParams": [Function],
"state": Object {
"index": 0,
"isTransitioning": false,
- "key": "**id-1542980055400-1**",
+ "key": "**id-1542980068677-1**",
"routeName": "FluidTransitionNavigator",
"routes": Array [
Object {
- "key": "**id-1542980055400-0**",
+ "key": "**id-1542980068677-0**",
"routeName": "SignInOrRegister",
},
],
},
}
## -191,11 +191,11 ##
"overflow": "hidden",
},
undefined,
]
}
- toRoute="**id-1542980055400-0**"
+ toRoute="**id-1542980068677-0**"
>
<View
style={
Object {
"bottom": 0,
The StackNavigator component uses 'react-navigation' and has a nested FluidTransition component from 'react-navigation-fluid-transitions':
StackNavigator.js
import React, { Component } from 'react';
import { Platform, Animated, Easing } from 'react-native';
import { createStackNavigator } from 'react-navigation';
import { ThemeContext, getTheme } from 'react-native-material-ui';
import PropTypes from 'prop-types'
import FluidTransitionNavigator from './FluidTransitionNavigator';
import Dashboard from './../pages/Dashboard';
import Login from './../pages/Login';
import SignInOrRegister from './../pages/SignInOrRegister';
import UniToolbar from './UniToolbar';
const transitionConfig = () => {
return {
transitionSpec: {
duration: 1000,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
useNativeDriver: false,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps
const thisSceneIndex = scene.index
const width = layout.initWidth
const translateX = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex],
outputRange: [width, 0],
})
return { transform: [ { translateX } ] }
},
}
}
const StackNavigator = createStackNavigator(
{
FluidTransitionNavigator: {
screen: FluidTransitionNavigator
},
Dashboard: {
screen: Dashboard
}
},
{
initialRouteName: 'FluidTransitionNavigator',
headerMode: 'float',
navigationOptions: (props) => ({
header: renderHeader(props)
}),
transitionConfig: transitionConfig
}
);
const renderHeader = (props) => {
let index = props.navigation.state.index;
const logout = (props.navigation.state.routeName === 'Dashboard');
let title = '';
switch (props.navigation.state.routeName) {
case 'Dashboard':
title = 'Dashboard';
break;
case 'FluidTransitionNavigator':
if (index !== undefined) {
switch (props.navigation.state.routes[index].routeName) {
case 'Login':
title = 'Sign In';
break;
case 'SignInOrRegister':
title = 'SignInOrRegister';
break;
default:
title = '';
}
}
break;
default:
title = '';
}
return (['SignInOrRegister', 'Sign In'].includes(title)) ? null : (
<ThemeContext.Provider value={getTheme(uiTheme)} >
<UniToolbar navigation={props.navigation} toolbarTitle={title} logout={logout} />
</ThemeContext.Provider>
);
};
renderHeader.propTypes = {
navigation: PropTypes.object
};
const uiTheme = {
toolbar: {
container: {
...Platform.select({
ios: {
height: 70
},
android: {
height: 76
}
})
},
},
};
export default StackNavigator;
Below is my test:
StackNavigator.test.js
import React from "react";
import renderer from "react-test-renderer";
import StackNavigator from "../StackNavigator";
test("renders correctly", () => {
const tree = renderer.create(<StackNavigator />).toJSON();
expect(tree).toMatchSnapshot();
});
I have seen a similar almost identical question here: Jest snapshot test failing due to react navigation generated key
The accepted answer still does not answer my question. It did however lead me down another rabbit hole:
I tried to use inline matching: expect(tree).toMatchInlineSnapshot() to generate the tree after running yarn run test:unit and then tried to insert Any<String> in place of all the keys. That did not work unfortunately.
I'm stumped. I don't know how to resolve this. I have searched and searched, tried multiple things to get around it, I just can't solve this.
Please can someone lend me a hand?
In ReactNavigation v5 you can mock it like this:
jest.mock('nanoid/non-secure', () => ({
nanoid: () => 'routeUniqId',
}));
I solved my problem, but not by mocking Date.now which was suggested in many other cases.
Instead I adapted an answer I found on https://github.com/react-navigation/react-navigation/issues/2269#issuecomment-369318490 by a user named joeybaker.
The rationale for this was given as:
The keys aren't really important for your testing purposes. What you
really care about is the routes and the index.
His code is as follows and assumes the use of actions and reducers in Redux:
// keys are date and order-of-test based, so just removed them
const filterKeys = (state) => {
if (state.routes) {
return {
...state,
routes: state.routes.map((route) => {
const { key, ...others } = route
return filterKeys(others)
}),
}
}
return state
}
it('clears all other routes', () => {
const inputState = {}
const action = { type: AUTH_LOGOUT_SUCCESS }
const state = filterKeys(reducer(inputState, action))
expect(state.routes).toBe........
})
I have adapted this for my case (I do not use Redux yet) as follows:
test("renders correctly", () => {
const tree = renderer.create(<StackNavigator />);
const instance = tree.getInstance();
const state = filterKeys(instance.state.nav);
expect(state).toMatchSnapshot();
});
// keys are date and order-of-test based, so just removed them
const filterKeys = (state) => {
if (state.routes) {
return {
...state,
routes: state.routes.map((route) => {
const { key, ...others } = route
return filterKeys(others);
}),
}
}
return state;
};
The test that gets rendered looks like this:
// Jest Snapshot v1
exports[`renders correctly 1`] = `
Object {
"index": 0,
"isTransitioning": false,
"key": "StackRouterRoot",
"routes": Array [
Object {
"index": 0,
"isTransitioning": false,
"routeName": "FluidTransitionNavigator",
"routes": Array [
Object {
"routeName": "SignInOrRegister",
},
],
},
],
}
`;

react native navigatorIOS How to modify the state in the onLeftButtonPress?

How to modify the state in the onLeftButtonPress?Because I need to click to do animation, show or hide me is stored in the state
_getRoute(component) {
var com = new component();
return {
component: component,
title: com.getNavTitle(),
passProps: { myProp: 'foo' },
leftButtonIcon: com.getNavLeft().icon,
onLeftButtonPress: com.getNavLeft().onPress,
interactivePopGestureEnabled: true
};
}
getNavLeft() {
return {
icon: require('../Images/wo_de.png'),
onPress: () => {
console.log(this);
this.setState({leftShow: true});
Animated.timing( // Uses easing functions
this.state.leftWidthAnim, // The value to drive
{toValue: 0} // Configuration
).start();
}
};
}