Closed arunmenon1975 closed 5 years ago
Update: i no longer receive errors. I updated the tsx
files and added the new build to my project.
However, the default example
<ToolbarAndroid
logo={require('./app_logo.png')}
style={{flex:1, backgroundColor: "blue"}} /*my addition*/
title="AwesomeApp"
actions={[{title: 'Settings', icon: require('./icon_settings.png'), show: 'always'}]}
onActionSelected={this.onActionSelected}
/>
throws an error at the logo
property even after i changed it to a valid local path.
The error:
If i comment that attribute out, there is no error but i only get the title to appear in the Toolbar. The actions icon/title do not appear.
Have you kept the resolveAssetSource call in the JavaScript?
import {Image} from 'react-native'
if (this.props.logo) {
nativeProps.logo = Image.resolveAssetSource(this.props.logo);
}
I'm excited by your idea of bringing the ToolbarAndroid
component into the Navigation router. Let me know how you get on with your copy/paste experiment and we can discuss the next steps
Have you kept the resolveAssetSource call in the JavaScript?
import {Image} from 'react-native' if (this.props.logo) { nativeProps.logo = Image.resolveAssetSource(this.props.logo); }
In my enthusiasm i forgot requisite JS changes considering this is a react-native
component clone and thus would be having its implementation details separately and possibly alongside other react native components.
I did take a look. This can turn out tricky simply because JS methods used by ToolbarAndroid
to integrate functionality like images/icons (including parsing the URI and for getting the dimensions like width/height) and onPress
are tied into common methods used in the core react-native
codebase. Copy/pasting this would mean having a companion utility .ts/x
file (or embedded within the main .tsx
file) that has the requisite methods in them. Even if this works out ok, there is a possibly of code duplication between methods introduced by react-native and similar methods implemented in the router. Since the router doesn't ship with react-native it cannot be built if i attempt a relative path import for the required JS files.
I currently see 2 options:
ToolbarAndroid
and focus on just a few. This would also mean reducing the breadth that the functionality attempts (like support for network images, checking for dev environment, overflow icons and functionality etc etc) and have it very focussed. Then attempt to reuse react-native code that does this in either a separate companion file(say ToolbarAndroidUtils.ts/x
or embed within the main file.ToolbarAndroid
. This can help avoid duplication and possibly can come in use in the future for any other additions in the router.I'm excited by your idea of bringing the
ToolbarAndroid
component into the Navigation router. Let me know how you get on with your copy/paste experiment and we can discuss the next steps
Thanks for the go-ahead. It will indeed be a useful addition to the router if things go well.
My thoughts currently:
react-native
implementation since it does work and has worked consistently for several users over the years. What im not sure is if all the functionality would be required or not. What i am attempting:
ToolbarAndroid.tsx
file in the router library. Instead of a requireNativeComponent()
call have content from Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
. Unfortunately this file sources methods from other files from the core react-native codebase including resolveAssetSource
. Snippet:...
const React = require('react');
const UIManager = require('../../ReactNative/UIManager');
const ToolbarAndroidNativeComponent = require('./ToolbarAndroidNativeComponent');
const resolveAssetSource = require('../../Image/resolveAssetSource');
import type {SyntheticEvent} from '../../Types/CoreEventTypes';
import type {ImageSource} from '../../Image/ImageSource';
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
import type {ViewProps} from '../View/ViewPropTypes';
import type {NativeComponent} from '../../Renderer/shims/ReactNative';
...
Im still unsure whether to embed code within this file or have a companion file.
resolveAssetSource
functionality to a limited focussed standalone methodFor your copy/paste experiment I don’t think we should remove any functionality. We should verify that we can get all the functionality working. That doesn’t mean that we’ll bring all the functionality into the Navigation router but it will help us with decision-making to know that we can.
The JavaScript doesn’t look too scary. We should be able to bring all that over without creating any utilities. For example, the resolveAssetSource is static function on Image so you can call it like this:
import {Image} from 'react-native'
if (this.props.logo) {
nativeProps.logo = Image.resolveAssetSource(this.props.logo);
}
You shouldn’t need to import the UIManager
. It’s used for a string lookup which we can hard-code instead.
You don’t need to copy over any of the type imports or type related code because that’s all related to flow.
Thanks for the suggestions. I made changes, as much as i understood, but i still get the same error. This is the current code that i have (please disregard the types - it was just for personal testing):
import React, { SyntheticEvent } from 'react'
import { Image, requireNativeComponent, Platform, NativeComponent } from 'react-native'
type ImageURISource = {
uri?: string,
bundle?: string,
method?: string,
headers?: Object,
body?: string,
cache?: ('default' | 'reload' | 'force-cache' | 'only-if-cached'),
width?: number,
height?: number,
scale?: number
};
export type ImageSource = ImageURISource | number | Array<ImageURISource>;
type Action = {
title: string,
icon?: ImageSource,
show?: 'always' | 'ifRoom' | 'never',
showWithText?: boolean
};
type ToolbarAndroidChangeEvent = {
nativeEvent: {
position: number
}
};
type NativeProps = {
onSelect: (event: ToolbarAndroidChangeEvent) => any,
nativeActions?: Array<Action>
};
type ColorValue = null | string;
type ToolbarAndroidProps = {
//...ViewProps, ...NativeProps,
onSelect: (event: ToolbarAndroidChangeEvent) => any,
nativeActions?: Array<Action>,
actions?: Array<Action>,
logo?: ImageSource,
navIcon?: ImageSource,
onActionSelected?: (position: number) => void,
onIconClicked?: () => void,
overflowIcon?: ImageSource,
subtitle?: string,
subtitleColor?: ColorValue,
title?: string,
titleColor?: ColorValue,
contentInsetStart?: number,
contentInsetEnd?: number,
rtl?: boolean,
testID?: string
};
//type NativeToolbarAndroidProps = <NativeComponent<ToolbarAndroidProps>>;
const ToolbarAndroidNativeComponent = ((requireNativeComponent('ToolbarAndroid')));
const resolveAssetSource = Image.resolveAssetSource;
type Props = {
// ...ToolbarAndroidProps,
forwardedRef: React.Ref<typeof ToolbarAndroidNativeComponent>
};
class ToolbarAndroid extends React.Component<Props & ToolbarAndroidProps> {
_onSelect = (event) => {
const position = event.nativeEvent.position;
if (position === -1) {
this.props.onIconClicked && this
.props
.onIconClicked();
} else {
this.props.onActionSelected && this
.props
.onActionSelected(position);
}
};
render() {
const {
onIconClicked,
onActionSelected,
forwardedRef,
...otherProps
} = this.props;
const nativeProps = {
...otherProps
};
if (this.props.logo) {
nativeProps.logo = resolveAssetSource(this.props.logo);
}
if (this.props.navIcon) {
nativeProps.navIcon = resolveAssetSource(this.props.navIcon);
}
if (this.props.overflowIcon) {
nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon);
}
if (this.props.actions) {
const nativeActions = [];
for (let i = 0; i < this.props.actions.length; i++) {
const action = {
icon: this.props.actions[i].icon,
show: this.props.actions[i].show
};
const ShowAsAction = ['always', 'ifRoom', 'never']
if (action.icon) {
action.icon = resolveAssetSource(action.icon);
}
if (action.show) {
action.show = ShowAsAction[action.show];
}
/* if (action.show) {
action.show = UIManager.getViewManagerConfig(
'ToolbarAndroid',
).Constants.ShowAsAction[action.show];
} */
nativeActions.push({
...this.props.actions[i],
...action
});
}
nativeProps.nativeActions = nativeActions;
}
return (<ToolbarAndroidNativeComponent
onSelect={this._onSelect}
{...nativeProps}
ref={forwardedRef} />);
}
}
const ToolbarAndroidToExport = React.forwardRef((props, forwardedRef, ) => {
return <ToolbarAndroid {...props} forwardedRef={forwardedRef} />;
});
/* import { requireNativeComponent, Platform } from 'react-native';
*/
// export default Platform.OS === 'ios' ?
// requireNativeComponent('ToolbarAndroid', null) : () => null;
export default ToolbarAndroidToExport;
and after a build, i attempt:
<ToolbarAndroid title="Home" logo={require('../../resources/media/images/icons/calendar-body.png')} style={{flex:1}} />
I think i definitely am nearer a working solution though i still get the same error.
[I still have flaky internet despite having 3 providers. A fresh build in react-native versions upwards of v60 seems to always require internet connectivity for some reason, perhaps due to jettifier
not sure. Thus i have to have a stable enough connection through the build duration for viewing my changes on the emulator or it just seems to idle around midway at some process or the other. And so my changes do take time to verify]
I've simplified the code so that we can see if the problem is with the logo prop or not. Can you try this out and let me know what happens. Send along any error message you get, please.
import React from 'react'
import { Image, requireNativeComponent } from 'react-native'
const ToolbarAndroidNativeComponent = requireNativeComponent('ToolbarAndroid');
export default ({logo}) => (
<ToolbarAndroidNativeComponent logo={Image.resolveAssetSource(logo)} />)
);
I get a build error:
[16:10:18] Using gulpfile ~/Sites/github/navigation/navigation/gulpfile.js
[16:10:18] Starting 'build'...
[16:10:18] Starting 'buildNavigation'...
[16:10:18] Starting 'buildNavigationReact'...
[16:10:18] Starting 'buildNavigationReactMobile'... [16:10:18] Starting 'buildNavigationReactNative'...
/Users/arunmenon/Sites/github/navigation/navigation/NavigationReactNative/src/ToolbarAn
droid.tsx(8,1): error TS1128: Declaration or statement expected.
[16:10:19] 'buildNavigationReactNative' errored after 1.18 s
[16:10:19] Error: There were TypeScript errors transpiling
at Object.transform (/Users/arunmenon/Sites/github/navigation/navigation/node_modules/rollup-plugin-typescript/dist/rollup-plugin-typescript.cjs.js:302:11)
at /Users/arunmenon/Sites/github/navigation/navigation/node_modules/rollup/dist/rollup.js:17680:41
at processTicksAndRejections (internal/process/task_queues.js:85:5)
[16:10:19] 'build' errored after 1.19 s
[16:10:19] The following tasks did not complete: buildNavigation, buildNavigationReact, buildNavigationReactMobile
[16:10:19] Did you forget to signal async completion?
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! navigation-@1.0.0 build: `gulp build`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the navigation-@1.0.0 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/arunmenon/.npm/_logs/2019-10-14T10_40_19_480Z-debug.log
And the log:
0 info it worked if it ends with ok
1 verbose cli [
1 verbose cli '/usr/local/Cellar/node/12.9.0/bin/node',
1 verbose cli '/usr/local/bin/npm',
1 verbose cli 'run',
1 verbose cli 'build'
1 verbose cli ]
2 info using npm@6.10.3
3 info using node@v12.9.0
4 verbose run-script [ 'prebuild', 'build', 'postbuild' ]
5 info lifecycle navigation-@1.0.0~prebuild: navigation-@1.0.0
6 info lifecycle navigation-@1.0.0~build: navigation-@1.0.0
7 verbose lifecycle navigation-@1.0.0~build: unsafe-perm in lifecycle true
8 verbose lifecycle navigation-@1.0.0~build: PATH: /usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/arunmenon/Sites/github/navigation/navigation/node_modules/.bin:/Users/arunmenon/flutter/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/MacGPG2/bin:/Users/arunmenon/flutter/bin:/usr/local/sbin:/usr/local/mysql/bin:/Users/arunmenon/Library/Android/sdk/tools:/Users/arunmenon/Library/Android/sdk/tools/bin:/Users/arunmenon/Library/Android/sdk/platform-tools:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/usr/local/mysql/bin:/Users/arunmenon/Library/Android/sdk/tools:/Users/arunmenon/Library/Android/sdk/tools/bin:/Users/arunmenon/Library/Android/sdk/platform-tools:/usr/local/mysql/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin
9 verbose lifecycle navigation-@1.0.0~build: CWD: /Users/arunmenon/Sites/github/navigation/navigation
10 silly lifecycle navigation-@1.0.0~build: Args: [ '-c', 'gulp build' ]
11 silly lifecycle navigation-@1.0.0~build: Returned: code: 1 signal: null
12 info lifecycle navigation-@1.0.0~build: Failed to exec build script
13 verbose stack Error: navigation-@1.0.0 build: `gulp build`
13 verbose stack Exit status 1
13 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/index.js:326:16)
13 verbose stack at EventEmitter.emit (events.js:209:13)
13 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/lib/spawn.js:55:14)
13 verbose stack at ChildProcess.emit (events.js:209:13)
13 verbose stack at maybeClose (internal/child_process.js:1021:16)
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5)
14 verbose pkgid navigation-@1.0.0
15 verbose cwd /Users/arunmenon/Sites/github/navigation/navigation
16 verbose Darwin 18.7.0
17 verbose argv "/usr/local/Cellar/node/12.9.0/bin/node" "/usr/local/bin/npm" "run" "build"
18 verbose node v12.9.0
19 verbose npm v6.10.3
20 error code ELIFECYCLE
21 error errno 1
22 error navigation-@1.0.0 build: `gulp build`
22 error Exit status 1
23 error Failed at the navigation-@1.0.0 build script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 1, true ]
..i think an additional closing parenthesis is the issue. The build is working fine now after i removed it. Let me try and rebuild my project. Slight likelihood of delay but i will update here once its complete
The build finally completed. I get the same error.
Just to be double-sure let me clean and re-build - i didn't do the clean-and-build routine since i was worried about the build taking forever.
Will update back with this result as well.
The build finally completed. I get the same error.
Just to be double-sure let me clean and re-build - i didn't do the clean-and-build routine since i was worried about the build taking forever.
Will update back with this result as well.
Same error with a clean-and-build task as well.
The only reliable way I've found to do a 'clean-and-build' is to delete the navigation-react-native folder from node_modules. Then do a react-native start
and react-native run-android
. This should give the error that navigation-react-native is missing. Then copy the newly built navigation-react-native into node_modules and do a react-native start
and react-native run-android
again.
Can you try that because I'm surprised the current code gives the 'Double to ReadableMap' error. The call to Image.resolveAssetSource
converts the number into a map.
If you're only making JavaScript changes (like we are here), then there's a quicker way to test them without having to mess around with clean and rebuilds. Copy just the newly built navigation.react.native.js
into the node_modules/navigation-react-native
folder and then reload the running app - by pressing Reload on the Dev Menu (or double tap R)
If you debug the JavaScript do you see that Image.resolveAssetSource
turns the number into json?
The only reliable way I've found to do a 'clean-and-build' is to delete the navigation-react-native folder from node_modules. Then do a
react-native start
andreact-native run-ios
. This should give the error that navigation-react-native is missing. Then copy the newly built navigation-react-native into node_modules and do areact-native start
andreact-native run-ios
again.Can you try that because I'm surprised the current code gives the 'Double to ReadableMap' error. The call to
Image.resolveAssetSource
converts the number into a map.
Same error. I did get the 'could not file package.json...' error etc and followed the exact steps.
If you're only making JavaScript changes (like we are here), then there's a quicker way to test them without having to mess around with clean and rebuilds. Copy just the newly built
navigation.react.native.js
into thenode_modules/navigation-react-native
folder and then reload the running app - by pressing Reload on the Dev Menu (or double tap R)
Ok. I will try this in subsequent builds (with a fallback to a full build if things dont work)
If you're only making JavaScript changes (like we are here), then there's a quicker way to test them without having to mess around with clean and rebuilds. Copy just the newly built
navigation.react.native.js
into thenode_modules/navigation-react-native
folder and then reload the running app - by pressing Reload on the Dev Menu (or double tap R)Ok. I will try this in subsequent builds (with a fallback to a full build if things dont work)
Strangely, after doing a build, i get the following javascript entry for ToolbarAndroid
in the file ./build/npm/navigation-react-native/navigation.react.native.js
:
var ToolbarAndroid = Platform.OS === 'android' ? requireNativeComponent('ToolbarAndroid', null) : function () { return null; };
but i have this is in ./NavigationReactNative/src/ToolbarAndroid.tsx
:
import React from 'react'
import { Image, requireNativeComponent } from 'react-native'
const ToolbarAndroidNativeComponent = requireNativeComponent('ToolbarAndroid');
export default ({logo}) => (
<ToolbarAndroidNativeComponent logo={Image.resolveAssetSource(logo)} />)
EDIT:
..in context, and this in my ./NavigationReactNative/src/NavigationReactNative.ts
import { Platform } from 'react-native';
import NavigationStack from './NavigationStack';
import Scene from './Scene';
import NavigationBarIOS from './NavigationBarIOS';
import LeftBarIOS from './LeftBarIOS';
import RightBarIOS from './RightBarIOS';
import BarButtonIOS from './BarButtonIOS';
import SearchBarIOS from './SearchBarIOS';
import TabBar from './TabBar';
import TabBarItem from './TabBarItem';
import SharedElementAndroid from './SharedElementAndroid';
import TitleBarIOS from './TitleBarIOS';
import BackHandlerContext from './BackHandlerContext';
import ToolbarAndroid from './ToolbarAndroid';
var TabBarIOS = Platform.OS === 'ios' ? TabBar : () => null;
var TabBarItemIOS = Platform.OS === 'ios' ? TabBarItem : () => null;
export { NavigationStack, Scene, NavigationBarIOS, LeftBarIOS, RightBarIOS, BarButtonIOS, TitleBarIOS, SearchBarIOS, TabBar, TabBarItem, TabBarIOS, TabBarItemIOS, SharedElementAndroid, BackHandlerContext, ToolbarAndroid };
ok, so i do see my change in ./build/dist/navigation.react.native.js
var ToolbarAndroid = (function (_a) {
var logo = _a.logo;
console.log(logo);
return (React__default.createElement(ToolbarAndroidNativeComponent, { logo: reactNative.Image.resolveAssetSource(logo) }));
});
Is it appearing twice? Where is the other one coming from? It's probably ignoring our one and hitting the wrong one instead
For updating changes, i am following the approach you had suggested in another issue here.
wrt the JS changes, in my local copy of the Navigation
router repo the following is my approach:
./NavigationReactNative/src/NavigationReactNative.ts
i have the same content as the Edit
portion in my earlier comment above./NavigationReactNative/src/ToolbarAndroid.tsx
with the same content as you had suggested in an earlier comment aboveNavigation
repo copy i ran npm run build
. ./build/npm/navigation-react-native
to my project's node_modules
after deleting the existing folder therePlease do correct if i've missed something.
Have you left in some other code at the bottom of ToolbarAndroid.tsx?
Have you left in some other code at the bottom of ToolbarAndroid.tsx?
No. I have it as:
import React from 'react'
import { Image, requireNativeComponent } from 'react-native'
const ToolbarAndroidNativeComponent = requireNativeComponent('ToolbarAndroid');
export default ({logo}) => {
console.log(logo)
return (<ToolbarAndroidNativeComponent logo={Image.resolveAssetSource(logo)} />)
}
I restarted my computer as well. I think ill refetch the Navigation
repo and start again from a fresh copy. The changes are minimal currently so it should not be an issue.
I did a complete refresh of the Navigation
router files from the original repo and re-copied the toolbar changes over. There is no error but somehow the Toolbar isn't appearing. Logging the prop returns the following in my console:
{logo: 1
style: {flex: 1, width: 200, height: 100}
title: "Home"}
for my component declaration:
<ToolbarAndroid title="Home" logo={require('../../resources/media/images/icons/calendar-body.png')} style={{flex:1, width: 200, height: 100}} />
Great, that's progress! If you're using the code I posted earlier then it's only sending the logo prop. It ignores your style prop so the Toolbar maybe has 0 height? Here's the updated component that sends on the other props together with the logo. Can you give that a try?
import React from 'react'
import { Image, requireNativeComponent } from 'react-native'
const ToolbarAndroidNativeComponent = requireNativeComponent('ToolbarAndroid');
export default ({logo,...props}) => (
<ToolbarAndroidNativeComponent logo={Image.resolveAssetSource(logo)} {...props} />
);
It works! And this time, post npm run package
i only copied over the js file as you had suggested in one of the comments above. No issues. My style
prop is picked up as well as the logo
.
It works! And this time, post
npm run package
i only copied over the js file as you had suggested in one of the comments above. No issues. Mystyle
prop is picked up as well as thelogo
.
In continuation and for context, this is my working code currently:
<View style={{flex: 1,
justifyContent: 'flex-start',
alignItems: 'stretch',
backgroundColor: 'blue',
}}>
<ToolbarAndroid title="Home" logo={require('../../resources/media/images/icons/arrow.png')} style={{ height: 100, backgroundColor: "red"}} />
Yippee! Are you interested in working on the PR that brings the Android Toolbar into the Navigation router? I've got an idea of how we should go about it. But before I spec it out I want to know if you’re interested. I should warn you that it will be a lot of work. Whatever you decide is fine, there’s no pressure either way.
Yippee! Are you interested in working on the PR that brings the Android Toolbar into the Navigation router? I've got an idea of how we should go about it. But before I spec it out I want to know if you’re interested. I should warn you that it will be a lot of work. Whatever you decide is fine, there’s no pressure either way.
Definitely interested, thanks for the offer. I actually look at it this way: its not everyday i get opportunities like this that entails being involved in a core piece of a software tool. Please do let me know how you plan to go ahead with this. Im in fully. My only constraint is that the turnaround-time may not be fast since i have a network connectivity issue of late that seems to continue despite best efforts from my side. Another constraint is of course my limited experience so there is the possibility of having moments like 'did he really do that' or suchlike; i will try my best though to get it sailing smoothly.
That's fantastic! I'm so glad you're up for the job!
Don't worry about anything. The most important thing is that you're keen. We'll work through any problems together just like we've already been doing. It will be great working with you on such a nice piece of work. Thanks for bring the work to my attention in the first place!
I'm going to be out the rest of the day but I'll put together the plan shortly. In the meantime, I'd be interested to hear how you think we should bring the Android Toolbar into the Navigation router. Jot down any thoughts you have.
And just for early reference, using most of the existing props:
<ToolbarAndroid
title="Home"
titleColor='yellow'
subtitle="Lorem Ipsum"
subtitleColor="gray"
overflowIcon={require('../../resources/media/images/icons/menu-vertical-dots.png')}
contentInsetEnd={20}
contentInsetStart={20}
logo={require('../../resources/media/images/icons/menu-menu.png')}
style={{ height: 100, backgroundColor: "red"}}
actions={[{title: 'Settings', icon: require('../../resources/media/images/icons/menu-settings.png'), show: 'always', showWithText: true}]}
onActionSelected={_actionSelected}
navIcon={require('../../resources/media/images/icons/arrow.png')}
onIconClicked={()=>Alert.alert('tapped')}
/>
the result looks like:
That's fantastic! I'm so glad you're up for the job!
Don't worry about anything. The most important thing is that you're keen. We'll work through any problems together just like we've already been doing. It will be great working with you on such a nice piece of work. Thanks for bring the work to my attention in the first place!
I'm going to be out the rest of the day but I'll put together the plan shortly. In the meantime, I'd be interested to hear how you think we should bring the Android Toolbar into the Navigation router. Jot down any thoughts you have.
ok, ill post them in a separate comment in a short while.
My plans changed so I found some time to write a High-Level spec!
There’s already a NavigationBarIOS
component but it only works on iOS
<NavigationBarIOS title="Color" />
We should change this into a NavigationBar
component that works on Android as well on iOS. On iOS the implementation stays the same (it uses the UINavigationBar
). But on Android we’ll implement it as a Toolbar
.
<NavigationBar title="Color" />
So, we’re not bringing React Native’s ToolbarAndroid
component into the Navigation router. Instead, we’re implementing the Navigation router’s NavigationBar
component on Android.
For the first release we’ll concentrate on the existing functionality of the NavigationBarIOS
component only. So we won’t need most of the existing props on the ToolbarAndroid
component.
To add buttons to the iOS navigation bar we use the Right/LeftBarIOS
and BarButtonIOS
components.
<NavigationBarIOS title="Color">
<RightBarIOS>
<BarButtonIOS title="Cancel" onPress={() => {
}} />
</RightBarIOS>
</NavigationBarIOS>
The equivalent of navigation bar buttons on Android are Menu Items. So we’ll make the Right/LeftBar
and BarButton
components work on Android by making them behave like the actions
prop on the ToolbarAndroid
component.
<NavigationBar title="Color">
<RightBar>
<BarButton title="Cancel" onPress={() => {
}} />
</RightBar>
</NavigationBar>
The title view can be customized on iOS using the TitleViewIOS
component. Again, we’ll make this work on Android because the Toolbar
supports child views.
There is a SearchBarIOS
component that adds a search view into the navigation bar on iOS. Although there is an Android equivalent, I don’t think we should implement this for the first release. The iOS and Android search implementations are different enough that I think it needs a lot more investigation. So when we complete this PR the SearchBarIOS
component will be the only component in the Navigation router that doesn’t have an Android equivalent.
I haven’t gone into any implementation details because I want to see what you think of the approach first. Once we’re both happy with the high-level idea then we can talk about how we’re going to implement it.
Some thoughts on a toolbar solution:
Assuming there is no platform-independent toolbar in the works
Why re-invent the wheel when there is a tried-and-tested (and now rested) solution. Reuse most if not all of ToolbarAndroid
as was available in the earlier versions.
Toned down solution to achieve near-parity with the functionality exposed by the TitlebarIos
module shipped with the router.
A fully custom Toolbar module
Assuming there is platform-independent toolbar planned I guess this requires a lot more thought.
From my project perspective
I initially wanted to go a non-standard route to present my UI/UX since the decided premise of the solution was to be more fun-like and visual-heavy. There really wouldn’t be ‘standard’ components and UI like tabs or toolbars purposefully to sort of ‘remain different from most of the rest’. I then realised that since i am using RN, it would require some compromising especially on performance since it would all have to be JS-driven considering my skillsets. Or i will have to make a substantial leap and improve my code-writing skills in Java/Kotlin/ObjC/Swift and either write custom native modules or ditch RN entirely and use native supported languages for the complete app. After a lot of pondering i decided to go with RN while proactively trying and using as much native solutions as i could to help improve performance. Using Navigation router was the result of this decision incidentally since it sort of had the right balance.
My ideal native toolbar on Android currently would be (beyond certain features already available now):
All things considered, in my opinion, the best short/medium-term solution would be to continue with integrating the original RN ToolbarAndroid
with a little more than what is considered as base requirements of a toolbar in a normal standard Android app today: a backbutton interactable, a title/subtitle, a logo and a menu intractable. In other words, a toned-down ToolbarAndroid
as it exists now.
My plans changed so I found some time to write a High-Level spec!
Ok. Just fyi, and to reduce confusion since my comment came in late, I just saw this message now after posting my own comment (the 5th or 6th attempt went through). Please disregard my comments for now.
Am reading through your high level specs and will revert shortly
My plans changed so I found some time to write a High-Level spec!
High-Level Idea
I definitely like this idea: custom cross-platform module with (almost full) feature parity with one of the existing implementations. This is circuitous(from an effort and time perspective) but a more robust and long term solution. Or actually not as circuitous since half the work is already done in the form of the Ios equivalent already being available. Integrating the existing ToolbarAndroid
could have been had but i guess its more a stop-gap effort.
So, in my understanding, a more or less full featured bare-basic toolbar (left button, title, right button ) in the Navigation
router would read:
<NavigationBar title="Color">
<LeftBar>
<BarButton title="Back" onPress={() => {
}} />
</LeftBar>
<RightBar>
<BarButton title="Cancel" onPress={() => {
}} />
</RightBar>
</NavigationBar>
with the possibility of being enhanced with a TitleView
as well :
<NavigationBar title="Color">
<LeftBar>
<BarButton title="Back" onPress={() => {
}} />
</LeftBar>
<TitleView>
<Text> Title here </Text>
<Text>Maybe a Sub Title here </Text>
</TitleView>
<RightBar>
<BarButton title="Cancel" onPress={() => {
}} />
</RightBar>
</NavigationBar>
[It just struck me now, would the <TitleView/>
override the title
prop?]
The only glaring miss i see is the need for an additional logo
component. While I really do not have a use for it in my own project(the logo is prominent in a toolbar-like position only in the splash interface and the rest of its usage is within the main body on a few inner screens ), i see that it can be handy for people in their own implementations that need a logo embedded in a toolbar. Can this be as simple as adding an additional <LeftBar/RightBar> <Image source={...} /> </LeftBar/RightBar> adjacent to any existing
<LeftBar/RightBar>`?
A search bar for Android as well would be a big plus for sure but rightly can be pushed into a later release since most greenfield projects might just start with the latest RN and its missing ToolbarAndroid
void may need to be filled in on priority.
Please let me know the next steps.
I’m glad you like the idea. We should merge both the Left and the Right buttons into a single set of Actions/Menu Items. The Left buttons appearing on top of the Right buttons in the menu. You can see what I mean with the following example. On iOS, the Add button is on the left and the Cancel is on the right, but on Android they would both become menu items.
<NavigationBar title="Color">
<LeftBar>
<BarButton title="Add" onPress={() => {
}} />
</LeftBar>
<RightBar>
<BarButton title="Cancel" onPress={() => {
}} />
</RightBar>
</NavigationBar>
We’ll have special Android props for the navigation button and logo because they don’t have iOS counterparts.
We’ll have a look at what happens when we set a title and TitleView on Android. On iOS, the TitleView takes precedence.
We should get the basic use-case working on Android (and iOS). A NavigationBar component with a single title prop. This will put all the key pieces into place and make sure we’re both happy before we move on to the trickier parts.
<NavigationBar title=”Hello” />
How we go about this is up to you. It all depends on how you like to work.
For example, if you like to work things out first before you start coding then you could write a plan in the comments for me to review. It could include a list of the files you plan to create and amend.
Or if you like to get coding first then you could create the PR straight away and push to it whenever you want me to check it over.
What do you think?
We should get the basic use-case working on Android (and iOS). A NavigationBar component with a single title prop. This will put all the key pieces into place and make sure we’re both happy before we move on to the trickier parts.
Thanks for not dropping me at the deep end of the pool :) - this definitely makes me a lot less nervous and sounds like just about the right path to start out with.
As i don't have much/any native code writing experience besides the odd changes here and there, it might be better to have my early outputs vetted. I will create and send out the plan as a a comment by tomorrow or at max day-after[have an out-of-town invite for a birthday celebration tomorrow + need just that bit of brushing up(research) before i commit ].
I open a PR for this issue. Hope this helps!
@emerson233 It does help, thanks a lot! I’m looking forward to reviewing it! @arunmenon1975 I hope you don't feel bad that @emerson233 got in before you.
Sorry for the interruption, but we are trying to migrate our project to use this repo instead of react-native-navigation and we need components like NavigationBar to be added as soon as possible.
I have been looking at the existing code for the Tab implementations that were recently added as an inspiration and have come to some sort of draft finalization, at least for the list of files that may be required and some 'start code' snippets. Please note that my understanding could/would be entirely erroneous; im still at an early learning stage and am concurrently binge watching/reading tutorials and other relevant content.
Please see a sort of rough plan for implementation listed below:
A list of added and/or modified files that could be required.
Since a new platform independent module is being built, all files listed for both the platforms below will be new additions.
Specific to implementing just <NavigationBar title=”Hello” />
, only a subset of files may be required to get going since there is a clear distinction between almost all connected components. The NavigationBarView
will manage all of the inner views, including rendering the mandatory title view from the title
prop.
For the first cut going out, the tentative code that may reside in the files is listed below.
NavigationBarManager.java
package com.navigation.reactnative;
import android.view.View;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
import javax.annotation.Nonnull;
public class NavigationBarManager extends ViewGroupManager<NavigationBarView> {
@Nonnull
@Override
public String getName() {
return "NVNavigationBar";
}
@Nonnull
@Override
protected TabBarView createViewInstance(@Nonnull ThemedReactContext reactContext) {
return new NavigationBarView(reactContext);
}
@ReactProp(name = "title")
public void setTitle(NavigationBarView view, @Nullable Integer title) {
view.setTitle(title);
}
@Override
public View getChildAt(NavigationBarView parent, int index) {
}
@Override
public void addView(NavigationBarView parent, View child, int index) {
}
}
NavigationBarView.java
package com.navigation.reactnative;
import android.content.Context;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
public class NavigationBarItemView extends ViewGroup {
Fragment fragment;
protected String title;
public NavigationBarItemView(Context context) {
super(context);
}
void setTitle(String title) {
title = title;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public Fragment getFragment() {
return fragment;
}
}
TitleViewManager.java TBD
TitleViewView.java TBD
NVNavigationBarManager.h
#import <React/RCTViewManager.h>
@interface NVNavigationBarManager : RCTViewManager
@end
NVNavigationBarManager.m
#import "NVNavigationBarManager.h"
#import "NVNavigationBarView.h"
@implementation NVNavigationBarManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[NVNavigationBarView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
@end
NVTitleViewManager.h TBD
NVTitleViewManager.h TBD
@arunmenon1975 Thanks for the plan. I now have two people working on the same PR. Can you let me know if you're happy for @emerson233 to take over the work. If you're not happy then please tell me and we can decide how to proceed.
@emerson233 It does help, thanks a lot! I’m looking forward to reviewing it! @arunmenon1975 I hope you don't feel bad that @emerson233 got in before you.
Not at all. The Navigation
router needs a navigation bar at the earliest at any cost. My effort will have taken a lot more time
@emerson233 I am looking forward to this being merged in for 2 reasons: 1) my project needs it and 2) i want to see the implementation since this will clear a lot of my 'starter' doubts.
@arunmenon1975 That's good to hear. Thanks for being so generous.
@arunmenon1975 Thanks for the plan. I now have two people working on the same PR. Can you let me know if you're happy for @emerson233 to take over the work. If you're not happy then please tell me and we can decide how to proceed.
Please go ahead with @emerson233 PR. I am looking forward to it as well. There is an element of time (and quality) to be considered and i still don't have the experience to tackle a feature like this quickly. All new RN installations that also use the Navigation
router will now have to counter a broken Twitter sample on Android which is not a happy state to be in.
I am looking forward to the PR merger, and with the start that i have made over the last couple of days, it will be quite an apt learning opportunity to see the implementation.
Your plan deserves a review even though we're not progressing with the work. It looks excellent, I've only a few comments:
NavigationBarView
Java class should inherit from Toolbar
not ViewGroup
. Then you don't need to add a setTitle
method because there's already one on the Toolbar
getChildAt
, addView
and getFragment
NavigationBarIOS
to NavigationBar
and remove the iOS specific logic Hope this helps! Let me know if you have any questions
NavigationBarView
now inherit from AppBarLayout to generate a drop shadow https://github.com/grahammendick/navigation/pull/311/commits/99e2484f1138c7012bbb06a63b40fedd716d8cc7start from scratch with the iOS classes
mean actually?Thanks for your reply!
@Jianing93 Sorry, please ignore that review. That was for @arunmenon1975!
@Jianing93 I will review your work on the PR
Your plan deserves a review even though we're not progressing with the work. It looks excellent, I've only a few comments:
- the
NavigationBarView
Java class should inherit fromToolbar
notViewGroup
. Then you don't need to add asetTitle
method because there's already one on theToolbar
- Make sure you only add what you really need. It's great that you used the Tab implementation as a guide but that means you've added unnecessary methods like
getChildAt
,addView
andgetFragment
- You don't need to start from scratch with the iOS classes. We can use the existing iOS implementations
- You've forgot to include the changes on the React side. You'll need to rename
NavigationBarIOS
toNavigationBar
and remove the iOS specific logicHope this helps! Let me know if you have any questions
Thanks for the feedback. Especially since this is not (rightly)being taken forward for resolving the issue and my initial/beginner doubts would have remained just that.
My early reading led to the official docs, specifically the AppBar
section where the example code seems to extend AppCompatActivity
public class MyActivity extends AppCompatActivity {
// ...
}
Re-reading the overview mentioned about appCompat's Toolbar
widget and about it being more focussed as an app bar than a more broad ActionBar
.
Reg point 4, I did not concentrate on the React-side since i was wholly looking at the native code requirements where i was struggling; the React based changes could be handled. But then i guess ,from a library perspective, every change is equally important, including examples.
Thanks again for the feedback. I intend to involve a little deeper into native code in the future since some level of knowledge will be required for any non-trivial solutions. Perhaps via some mini (2-3 screen) concurrent practice side-projects.
React's lean core effort is going full steam and now
ToolbarAndroid
is no longer available in the codebase though, as of today, the documentation does exist on the main doc site.Examples in this repo that use
ToolbarAndroid
, like the Twitter sample, currently call it fromreact-native
and this is bound to break in versions ofreact-native
higher thatv61
, as i found out as well in my project.Please let me know if you are considering shipping an Android variant sometime in the future.
Pitches for having it in the library:
Navigation React Native
does have an iOS variant (inTitleBarIOS
I think) so it can balance things outTo get a feel of Android native module integration with the router, my initial attempts at a crude/copy-paste integration results in errors currently. I get the dreaded 'Invariant Violation' error ("expected a string for built-in components...you likely forgot to export your component from the file it is defined in..."). I think i did the right things, at least after cross-checking with how the other modules - like the tabs - were integrated, but somehow i keep getting the error i mentioned above.
Steps i undertook:
react-native
release, dumped them into atoolbar
directory injava/com/navigation/reactnative
. The files created areDrawableWithIntrinsicSize.java
,ReactToolbar.java
andReactToolbarManager.java
in the roottoolbar
directory and in a nestedevents
directory i haveToolbarClickEvent.java
. The code is verbatim from an earlier react-native version that did have the implementation.NavigationPackage.java
file with the import:import com.navigation.reactnative.toolbar.ReactToolbarManager;
and subsequent call tonew ReactToolbarManager()
.navigation-react-native
directory to mynode_modules
ToolbarAndroid
fromnavigation-react-native
and attempted calling it in one of my components with a simple<ToolbarAndroid title="Home" />
Im still not to sure if just pulling in the code as-is from an earlier version somehow messes up the licensing bit even if the comments and attributions are intact. My guess is it should be ok for FOSS projects.
This original
ToolbarAndroid
module is quite comprehensive and if indeed you are looking at having a common Toolbar component across platforms across both the platforms, some rationalization or simplification may be warranted i think.If you are not too keen on having it integrated i can always use the same approach but have it exposed via
NativeModules
. It perhaps may work. Or may not. Or maybe totally simplify it just for for my needs -> a back button, a title and perhaps a menu button/iconUPDATE:
I have to make a few changes to
.tsx
files as well. Will update here after completing those.