Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade React Navigation v3 and v4. #4249

Merged
merged 7 commits into from
Sep 17, 2020

Conversation

chrisbobbe
Copy link
Contributor

@chrisbobbe chrisbobbe commented Sep 4, 2020

This is blocking the RN v0.61 -> v0.62 upgrade (#3782); see discussion.

This supersedes #3502, which has gone stale. One part of that PR that stands out as not covered here (or in prior merged work on upgrading to v2):

9b101f7 navigation: Replace getSameRoutesCount with popToTop

This looks like a followup to the v2 upgrade, using something that wasn't available in v1. (Seems like props.navigation.popToTop was available, but not StackActions.popToTop.) We could do that in a followup, or alongside other work in reshaping our navigation logic (like #3954 or #3804), or we could lump it in here.

Fixes: #3649
Fixes: #4248
Fixes: #3997

@chrisbobbe chrisbobbe added dependencies Pull requests that update a dependency file a-navigation labels Sep 4, 2020
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Sep 4, 2020

Posting some details on my v3 upgrade procedure that seem too long to fit into the main commit message for that upgrade, which is where they apply:

Update / Add Deps

We start by just yarn upgradeing react-navigation to ^3.13.0 (the latest 3.x).

With that, we get the following peer-dep output:

warning " > react-navigation-tabs@0.8.4" has incorrect peer dependency "react-navigation@^2.0.0".
warning "react-navigation > react-navigation-stack@1.5.3" has unmet peer dependency "react-native-gesture-handler@^1.0.0".
warning "react-navigation > react-navigation-drawer@1.4.0" has unmet peer dependency "react-native-gesture-handler@^1.0.12".
warning "react-navigation > @react-navigation/native@3.6.5" has unmet peer dependency "react-native-gesture-handler@*".

We upgrade react-navigation-tabs to ~1.2.0, which we choose because our new react-navigation v3 has that as a direct dependency (more on this below).

The react-navigation v4.x [sic] upgrade guide [3] recommends starting that upgrade by adding three direct dependencies, for an interesting reason:

  • react-navigation-stack@^1.7.3
  • react-navigation-tabs@^1.2.0 (note we didn't use this version range; see below)
  • react-navigation-drawer@^1.4.0

That reason is,

"""
This will install the versions compatible with your code if you were using react-navigation@3.x, so you wouldn't need any more changes beyond changing the imports.
"""

So it seems like we'll want (or need) to depend on these three directly, in react-navigation v4, probably because they'll newly be peer-deps of react-navigation.

Might as well keep react-navigation-tabs (and close #4114), update its version, and add the other two right now. However, the indicated version ranges aren't quite right for v3 compatibility; they would resolve react-navigation-stack to 1.10.3, which appears to require react-navigation 4.x (expressed as a peer-dep). To be safe, we just pick the version ranges straight from react-navigation v3's direct dependencies:

  • react-navigation-drawer@~1.4.0
  • react-navigation-stack@1.5.3
  • react-navigation-tabs@~1.2.0

At this point, the peer-dep output is the following:

warning "react-navigation > @react-navigation/native@3.6.5" has unmet peer dependency "react-native-gesture-handler@*".
warning " > react-navigation-drawer@1.4.0" has unmet peer dependency "@react-navigation/core@*".
warning " > react-navigation-drawer@1.4.0" has unmet peer dependency "@react-navigation/native@*".
warning " > react-navigation-drawer@1.4.0" has unmet peer dependency "react-native-gesture-handler@^1.0.12".
warning " > react-navigation-stack@1.5.3" has unmet peer dependency "@react-navigation/core@^3.5.0".
warning " > react-navigation-stack@1.5.3" has unmet peer dependency "@react-navigation/native@^3.6.2".
warning " > react-navigation-stack@1.5.3" has unmet peer dependency "react-native-gesture-handler@^1.0.0".

For each package name, we take the intersection of its valid ranges, adding

  • @react-navigation/core@^3.5.0
  • @react-navigation/native@^3.6.2
  • react-native-gesture-handler@^1.0.12

at which point there are no more peer-dep warnings.

The 3.x installation guide also says we need to add react-native-reanimated (we've seen this once before, in unrelated work [4]). But I don't think we need to yet, and in particular, I'd rather not add it until we know what version of it we want:

  • No peer-dep warning is telling us to use it.
  • react-navigation-tabs doesn't import it until react-navigation/tabs@e6efae638, which is tags/v2.0.0-alpha.0~3. yarn why shows we've resolved to use react-navigation-tabs@1.2.0.
  • react-navigation-drawer doesn't import it until react-navigation/drawer@ec5e99cd4, which is tags/v2.0.0-alpha.0~7). yarn why shows we've resolved to use react-navigation-drawer@1.4.0.
  • react-navigation-stack doesn't import it until react-navigation/stack@40a33b3fb, which is tags/v2.0.0-alpha.6~64). yarn why shows we've resolved to use react-navigation-stack@1.5.3.
  • The above three, plus @react-navigation/core and @react-navigation/native, are the only things listed under dependencies in v3 of react-navigation. The latter two don't use react-native-reanimated at any version.
  • (react-navigation-animated-switch is advertised as making use of react-native-reanimated; naturally, that project starts importing it at its initial commit. But yarn why shows that we're not using react-navigation-animated-switch at all.)

Next, the doc says we need to add some Java to our MainActivity.java, to get react-native-gesture-handler up and running. Since bdce7d0, we don't have a MainActivity.java; ours was converted to Kotlin. The react-native-gesture-handler setup doc [5] doesn't have a Kotlin version of the added code either, so I temporarily revived MainActivity.java from bdce7d0^, added it in there, used Android Studio to convert the file to Kotlin, and inserted the translated new Kotlin into our MainActivity.kt.

At this point, Jest fails because react-native-gesture-handler depends on a native module that won't be available in Jest. So, follow a recommendation [6] to run that package's own Jest setup, as a fix. (I wish more packages shipped with these and suggested this approach!)

Even though we're trying to stop using react-navigation-redux-helpers (and we'll need to by react-navigation v5; see #3804), we haven't yet. And we have to take its v3 and handle the breaking changes [7]. All three breaking changes apply to us, but they're small and simple. Also, removing this library from our Flow config's [ignore]s seems to be fine, and gets us more type-checking coverage. There's actually a known gap in type-checking, though; see the v3.0.8 release notes, which suggests that react-navigation-redux-helpers's types will be incomplete without the @react-navigation/core libdef. I tried adding that, and it clashes with the react-navigation v3 libdef in a few places, and decided not to. I figure we'd rather not have to keep tweaking two libdefs to keep in sync with each other. Also:

  • Where the two disagreed and it led to an error, it seemed like react-navigation_v3.x.x was generally the one out-of-date, but in multiple places, those issues get fixed with react-navigation_v4.x.x
  • @react-navigation/core describes itself as a thing for "building navigators independent of the platform". We're not in the business of building navigators; we use ones (like react-navigation-tabs) that are already built. Given this, it seems unlikely that we'll reasonably need a libdef for it
  • As mentioned earlier, we want to move away from using react-navigation-redux-helpers ASAP.

Also, run yarn yarn-deduplicate && yarn as prompted by tools/test deps.

Libdefs

A react-navigation v3 libdef from FlowTyped exists, and it looks good! This saves us hours of work. Particularly helpful and admirable is the fact that they've gone and painstakingly copied some things over from other modules (with a few minor errors, some or all of which will be fixed in v4; see above), since they can't be imported [8]. There is a small hiccup (not that libdef's fault); react-navigation-tabs shows some dubious errors that I traced back that library's built-in types being badly written. I found that they go away with the built-in types in v2.4.1, but peer-dep warnings tell us that we can't use that version until we're on react-navigation v4 (#4248). Ah, well; add a temporary line to our Flow config's [ignore]s.

For the other dependencies added in this commit:

  • There are libdefs for @react-navigation/core (mentioned above) and @react-navigation/native, but since we don't directly consume anything from these, we don't expect to need the libdefs, and at least the @react-navigation/core libdef at this version conflicts with the react-navigation libdef.
  • We don't directly consume anything in react-native-gesture-handler in our JS.
  • There's a libdef for react-navigation-tabs, but for "2.x.x"; we're not there yet. We'll be there soon, with react-navigation v4, though.
  • There's a libdef for react-navigation-stack for "1.x.x", great! Add that.
  • There's a libdef for react-navigation-drawer, but for "2.x.x"; we're not there yet. We'll be there soon, with react-navigation v4, though.

Breaking changes

Now all we have to do is handle the announced breaking changes [2], as follows:

  • "Explicit app container required for the root navigator"
    • Easy enough; make sure our AppNavigator is wrapped with the appropriate function. Consulting the new version of react-navigation-redux-helpers, they'd actually like us to use something they provide instead, createReduxContainer, so that that library's functionality (which we hope to stop using in Decouple navigation from Redux #3804) still works. Sure.
    • This seems associated with a breakage we found a workaround for; see discussion
  • "Renamed navigationOptions in navigator configuration"
    • This doesn't seem to apply to us; when we use navigationOptions, we always seem to do so in a way that's specifically intended for the corresponding navigator. We don't set navigationOptions intending for the settings to cascade as defaults to descendent navigators, which was possible. That cascading is still possible, you just have to explicitly use defaultNavigationOptions if you want it. Seems much clearer this way.
  • "Drawer now keeps inactive tabs in memory by default"
    • We're not using drawer navigators.
  • "Default stack background color is now white"
    • In our one call site of createStackNavigator, we already set the background color to white as the only setting in cardStyle, so we stop setting cardStyle. (Note: if adding cardStyle back, note that in v4 it belongs under navigationOptions or, more likely, defaultNavigationOptions.)

[1] https://reactnavigation.org/docs/3.x/getting-started/
[2] https://reactnavigation.org/blog/2018/11/17/react-navigation-3.0/
[3] https://reactnavigation.org/docs/4.x/upgrading-from-3.x/#install-the-new-packages
[4] #4217 (comment)
[5] https://docs.swmansion.com/react-native-gesture-handler/docs/#installation
[6] software-mansion/react-native-gesture-handler#344 (comment)
[7] https://github.com/react-navigation/redux-helpers/releases/tag/3.0.0
[8] #3458 (comment)

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 4, 2020
In this commit:

- Update react-navigation and its libdef, handle breaking changes in
  code

- Update react-navigation-redux-helpers to keep compatibility;
  handle breaking changes and let it wrap AppNavigator in its
  preferred way

- Add react-navigation-stack and react-navigation-drawer to prepare
  for the requirement in v4 that we depend on these directly. Match
  the versions of these (and that of react-navigation-tabs, which
  we're already depending on) to the versions react-navigation v3
  has for these in its `dependencies` in its `package.json`. Grab a
  compatible libdef for react-navigation-stack.

- Add @react-navigation/core, @react-navigation/native, and
  react-native-gesture-handler to satisfy peer dependencies. Follow
  instructions for some additional setup for
  react-native-gesture-handler in Jest config and `MainActivity.kt`.

- Add a note about a console error that we're getting, which will go
  away in react-navigation v4. It doesn't seem to break any
  functionality.

See more detail on GitHub at
zulip#4249 (comment).

Fixes: zulip#3649
@chrisbobbe
Copy link
Contributor Author

@chrisbobbe said:

Posting some details on my v3 upgrade procedure that seem too long to fit into the main commit message for that upgrade, which is where they apply:

My notes on the v4 upgrade were smaller, so I just have those in the commit message.

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 11, 2020
In this commit:

- Update react-navigation and its libdef, handle breaking changes in
  code

- Update react-navigation-redux-helpers to keep compatibility;
  handle breaking changes and let it wrap AppNavigator in its
  preferred way

- Add react-navigation-stack and react-navigation-drawer to prepare
  for the requirement in v4 that we depend on these directly. Match
  the versions of these (and that of react-navigation-tabs, which
  we're already depending on) to the versions react-navigation v3
  has for these in its `dependencies` in its `package.json`. Grab a
  compatible libdef for react-navigation-stack.

- Add @react-navigation/core, @react-navigation/native, and
  react-native-gesture-handler to satisfy peer dependencies. Follow
  instructions for some additional setup for
  react-native-gesture-handler in Jest config and `MainActivity.kt`.

- Add a note about a console error that we're getting, which will go
  away in react-navigation v4. It doesn't seem to break any
  functionality.

Also, run `yarn yarn-deduplicate && yarn` as prompted by
`tools/test deps`.

See more detail on GitHub at
zulip#4249 (comment).

Fixes: zulip#3649
@chrisbobbe
Copy link
Contributor Author

Just fixed a conflict.

@gnprice
Copy link
Member

gnprice commented Sep 12, 2020

Thanks @chrisbobbe !

These changes all look good. Now trying it out for some manual testing.

Next, the doc says we need to add some Java to our MainActivity.java, to get react-native-gesture-handler up and running. Since bdce7d0, we don't have a MainActivity.java; ours was converted to Kotlin. The react-native-gesture-handler setup doc [5] doesn't have a Kotlin version of the added code either, so I temporarily revived MainActivity.java from bdce7d0^, added it in there, used Android Studio to convert the file to Kotlin, and inserted the translated new Kotlin into our MainActivity.kt.

Eep, sorry for the runaround.

One thing I actually quite appreciated about Kotlin when I first started really using it was that because its designers have taken great care over their smooth interoperation with Java, it's very straightforward to translate Java code into Kotlin. I found in particular that I could search the web for how to do something in Android, find some possible solution on StackOverflow that was in Java (because until recent years Java was the one primary language for Android), and look at the Java code and transcribe it as Kotlin into my editor just about as fast as I could have simply retyped it as Java.

(Then in many cases there are fancier Kotlin features you could use to improve the code from there -- but there's always a perfectly reasonable Kotlin version that is just a straightforward translation from Java.)

But naturally that only works once you have some level of familiarity with both Java and Kotlin, which I think you haven't yet had occasion to really work with. That's a clever workaround you found, and I'm glad it worked out smoothly!

@gnprice
Copy link
Member

gnprice commented Sep 12, 2020

Some observations from playing with this branch, on Android both on a physical and an emulated device:

  • The transitions on navigation are a bit different.
    • When pushing a new screen on the nav stack:
      • We currently have an animation where the new screen sort of appears on top (in the z-sense), and comes in quickly from the bottom (in the y-sense), like a card being placed on top of a deck.
      • The new animation is longer, and is more like a "wipe" from the bottom (in the y-sense): midway through, we show the bottom of the new screen at the bottom of the display, and the top of the old one at the top of the display, and the line between them sweeps from bottom to top. (Going to the lightbox, while in light mode, makes an especially good occasion to see what's happening here.)
      • The new animation feels worse to me. Partly this is that the wipe doesn't make sense to me as a metaphor.
    • When going between top tabs (e.g. in the streams screen, or the reaction-list screen):
      • Currently there is no animation; we just switch from old to new.
      • The new animation is a rapid sideways scroll from old to new.
      • The new animation feels nicer to me... though it really suggests that swiping ought to work to switch between tabs, and we don't offer that.
    • When switching between bottom tabs (on the main screen of the app):
      • No animation, before or after.
  • Scrolling becomes pretty painful, at least in the message list, when in the emulator.
    • It seems to work OK as long as you come to a complete stop before releasing.
    • If you don't... then instead of coasting as it should, the scroll abruptly turns around and goes sharply in the other direction.
    • Happily this doesn't appear on my physical device. So perhaps it's an interaction specifically with the emulator.
    • Presumably this is related to the new react-native-gesture-handler that gets pulled in with the v3 upgrade.

When you try it out, do you reproduce these?

I don't think any of these are a blocker, if we can't find a way to avoid them while upgrading this library. But the scrolling issue seems like it'll be pretty annoying in future development on Android with the emulator (which is the environment I use most often.) And the "wipe" animation seems like a regression, though a subtle one, in the UX.

So if we can, I'd like to find a way to address each of those.

@gnprice
Copy link
Member

gnprice commented Sep 12, 2020

I did a bit of investigation on the "wipe animation" issue.

This source file looks relevant:
https://github.com/react-navigation/react-navigation/blob/050447b9ac6260d971e91ce79115ac38855e2ca2/packages/stack/src/TransitionConfigs/CardStyleInterpolators.tsx#L212

/**
 * Standard Android-style reveal from the bottom for Android Pie.
 */
export function forRevealFromBottomAndroid({

An earlier version used the term "wipe" instead of "reveal", which helped me find it.

And, poking around at stock apps on my (emulated) Android 9 P device, I can indeed find some examples of a wipe/reveal animation! Specifically it's how the system settings app brings up new screens.

But there are also lots of different animations that appear in other circumstances in stock apps. In particular I can't find any examples of a wipe/reveal animation where the old and new screens don't have the same style of app bar at the top, and the same background color both for the app bar and the rest of the screen respectively. We have lots of transitions that don't fit that description, thanks to the stream-colored app bars as well as the lightbox.

Also, I'm seeing this same wipe/reveal animation when I use the app on Android 10 Q. And it is not normal there.

Perhaps we can resolve this by simply picking a different type of animation? They do seem to have a nice selection of them, here:
https://github.com/react-navigation/react-navigation/blob/050447b9ac6260d971e91ce79115ac38855e2ca2/packages/stack/src/TransitionConfigs/TransitionPresets.tsx
and in particular that eventually identifies a "default" -- so presumably there's somewhere we can override that default.

@chrisbobbe
Copy link
Contributor Author

Eep, sorry for the runaround.

No problem! 🙂

@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Sep 15, 2020

When going between top tabs (e.g. in the streams screen, or the reaction-list screen):

  • Currently there is no animation; we just switch from old to new.
  • The new animation is a rapid sideways scroll from old to new.
  • The new animation feels nicer to me... though it really suggests that swiping ought to work to switch between tabs, and we don't offer that.

We can offer swiping between tabs! It's a prop, swipeEnabled. Might as well?

I did a bit of investigation on the "wipe animation" issue.
[...]
Perhaps we can resolve this by simply picking a different type of animation? They do seem to have a nice selection of them, here:
https://github.com/react-navigation/react-navigation/blob/050447b9ac6260d971e91ce79115ac38855e2ca2/packages/stack/src/TransitionConfigs/TransitionPresets.tsx
and in particular that eventually identifies a "default" -- so presumably there's somewhere we can override that default.

Sure, looks like we can do this! We only have one stack navigator. In it, we can set one of those animation presets, either in navigationOptions or defaultNavigationOptions (if we want child navigators to be informed of the choice).

@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Sep 15, 2020

Scrolling becomes pretty painful, at least in the message list, when in the emulator.

  • It seems to work OK as long as you come to a complete stop before releasing.
  • If you don't... then instead of coasting as it should, the scroll abruptly turns around and goes sharply in the other direction.
  • Happily this doesn't appear on my physical device. So perhaps it's an interaction specifically with the emulator.
  • Presumably this is related to the new react-native-gesture-handler that gets pulled in with the v3 upgrade.

Hmm, I'm not seeing this in the Android emulator. It seems as smooth as ever, given that for me it's always faster on a physical device. I'm not seeing an abrupt turnaround when I do a "coasting" scroll.

@chrisbobbe
Copy link
Contributor Author

Hmm, one thing I noticed on iOS: the navigation away from the LoadingScreen (seen on startup) is now a slide-from-right, which looks a little odd—but maybe just because I'm not used to seeing it?

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 15, 2020
In one of the recent React Navigation upgrades, the animation for a
new screen entering and leaving the stack, on Android, changed.

We like how it was before [1]. So, put it back.

[1] zulip#4249 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 15, 2020
In one of the recent React Navigation upgrades, a rapid sideways
scroll animation was added for when you tap another tab to switch to
it. This feels better than having no animation, but it really
suggests that swiping ought to work to switch between tabs [1]. We
haven't been offering that; so, do that.

[1] zulip#4249 (comment)
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Sep 15, 2020

OK, I just pushed a revision that should address everything except the scrolling issues you saw on the Android emulator (I didn't reproduce these) and the new slide-from-right animation when leaving the LoadingScreen on iOS (should we address that?).

I added two new commits. One thing I could do that I haven't done yet is identify whether the issues they address were introduced with the v3 upgrade or the v4 upgrade; this would be useful if we want to plan for urgently reverting back to v3 and staying there for some time, and perhaps not very useful otherwise.

@gnprice
Copy link
Member

gnprice commented Sep 17, 2020

Hmm, I'm not seeing this in the Android emulator. It seems as smooth as ever, given that for me it's always faster on a physical device. I'm not seeing an abrupt turnaround when I do a "coasting" scroll.

Well, and now I'm seeing the same symptom on master. 🙂 So I guess it's not about the new gesture handler after all.

I'm also not seeing the symptom when I go use a different emulated device, at least on master. (I should go try it on this branch, I guess.) Specifically:

  • on my emulated Pixel 2 running Android 9 P, I see the issue on master (and that's also where I saw it on this branch the other day);
  • on my emulated Pixel 3 running Android 10, I don't see the issue on master -- scrolling is very nice and smooth.

edit: Yup, and same result on this branch -- on that second emulated device, scrolling is very nice and smooth.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can offer swiping between tabs! It's a prop, swipeEnabled. Might as well?

Sounds good to me! And I just played with it on both the streams tabs and the message-reaction list, and it felt like a good interaction.

I think that fixes #3997, too.

Hmm, one thing I noticed on iOS: the navigation away from the LoadingScreen (seen on startup) is now a slide-from-right, which looks a little odd—but maybe just because I'm not used to seeing it?

I agree that sounds odd. What we've previously had (and I just fact-checked this on my iPad, which is running v26.29.152) is that when we're done loading it goes immediately to the main screen, with no transition. That feels like the right behavior -- anything else just prolongs the effect of the loading screen. So fixing that would be a good followup task.

On Android, similarly, what I see in the release on my phone is an instant switch with no transition. I'm not quite sure if this branch introduces a transition there or not; I'd guess it does, but I'm not clearly seeing it. But then the normal "fade from bottom" transition is quick enough that I'm not sure that means it isn't happening.

defaultNavigationOptions: {
...Platform.select({
android: TransitionPresets.FadeFromBottomAndroid,
ios: TransitionPresets.SlideFromRightIOS,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps ios: TransitionPresets.DefaultTransition? That way it's explicit that we're not intending to change what we get in this case, only in the Android case.

@gnprice
Copy link
Member

gnprice commented Sep 17, 2020

One thing I could do that I haven't done yet is identify whether the issues they address were introduced with the v3 upgrade or the v4 upgrade; this would be useful if we want to plan for urgently reverting back to v3 and staying there for some time, and perhaps not very useful otherwise.

I think we can leave that to be done lazily if needed. If we do end up doing such a revert for whatever reason, then in the worst case we'll reintroduce these issues. Both are subtle UI polish issues that don't break any functionality.

See my small code comment above, and I think you'll want to mark the PR and that other new commit as fixing #3997. Then please merge at will!

This is the recommended thing to do when it's necessary to defy
react-navigation's warnings [1] against "explicitly rendering more
than one navigator". In the upcoming react-navigation v2 -> v3
upgrade, this would otherwise produce a crashing error; see
discussion [2].

[1] https://reactnavigation.org/docs/2.x/common-mistakes/#explicitly-rendering-more-than-one-navigator
[2] https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Dynamic.20routes.20in.20react-navigation/near/1008268
This will prevent a crashing error on iOS and Android that would
otherwise be introduced in the upcoming react-navigation v2 -> v3
upgrade, on opening the message-reaction list. See discussion [1].

[1] https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Dynamic.20routes.20in.20react-navigation/near/1008268
In this commit:

- Update react-navigation and its libdef, handle breaking changes in
  code

- Update react-navigation-redux-helpers to keep compatibility;
  handle breaking changes and let it wrap AppNavigator in its
  preferred way

- Add react-navigation-stack and react-navigation-drawer to prepare
  for the requirement in v4 that we depend on these directly. Match
  the versions of these (and that of react-navigation-tabs, which
  we're already depending on) to the versions react-navigation v3
  has for these in its `dependencies` in its `package.json`. Grab a
  compatible libdef for react-navigation-stack.

- Add @react-navigation/core, @react-navigation/native, and
  react-native-gesture-handler to satisfy peer dependencies. Follow
  instructions for some additional setup for
  react-native-gesture-handler in Jest config and `MainActivity.kt`.

- Add a note about a console error that we're getting, which will go
  away in react-navigation v4. It doesn't seem to break any
  functionality.

Also, run `yarn yarn-deduplicate && yarn` as prompted by
`tools/test deps`.

See more detail on GitHub at
zulip#4249 (comment).

Fixes: zulip#3649
v4 includes lots of housekeeping changes that aim to make
maintenance easier [1].

In particular, we no longer grab stack, tab, or drawer navigation
logic from react-navigation itself; we must now depend directly on
separate libraries that provide these and import from them.

- We got a head start on this in the v3 upgrade (in a recent commit)
  by ensuring we had react-navigation-{tabs,stack,drawer} installed
  at v3-compatible versions.

- Now, we bump these to their latest versions (no peer-dep warnings
  when we do this!) and default to using carets in their version
  ranges since we see no indication that we can't.

- This means installing react-native-reanimated, as we knew we'd
  need to. (We mock it in our Jest setup, following some
  instructions [2].) For react-navigation-stack, we add peer
  dependencies react-native-safe-area-context and
  @react-native-community/masked-view.

The upgrade guide asks us to stop depending on
@react-navigation/core and @react-navigation/native and change any
imports of those to react-navigation imports instead. We don't have
any imports to change, and we can freely remove
@react-navigation/native. We can't yet remove @react-navigation/core
because it's still a peer dependency of
react-navigation-redux-helpers [3]. So we keep it, and align its
version range with the one in react-navigation's dependencies.

There's a long list of identifiers whose usage has changed.
Searching our code for each one, these are the interesting ones:

- `create*Navigator` being imported from the respective
  react-navigation-{tabs,stack,drawer} library; quite easy to handle

- `cardStyle` (in stack nav config) being moved to
  `navigationOptions` (or `defaultNavigationOptions`). We deleted
  our only use of `cardStyle` in the recent react-navigation v3
  upgrade because we only used it to make the background color white
  and, white became the default background color in that upgrade.

Finally, attempt to upgrade libdefs for the dependencies we touched,
if we import from them directly. We get new version-appropriate
libdefs for react-navigation, react-navigation-drawer, and
react-navigation-tabs. Unfortunately react-navigation-stack only has
a v1 libdef.

[1] https://reactnavigation.org/docs/4.x/upgrading-from-3.x/

[2] See https://reactnavigation.org/docs/testing/. We don't include
    the following line also recommended there:

    ```
    // Silence the warning: Animated: `useNativeDriver` is not
    // supported because the native animated module is missing
    jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
    ```

    We don't get that warning in the first place, and it's not clear
    what mock is being applied (apart from React Native's own
    default one). If we need to mock `NativeAnimatedHelper` in the
    future, we shouldn't point to the internal path in react-native;
    it should go in our existing react-native mock (alongside
    NativeModules, etc.).

[3] zulip#3804 (comment)

Fixes: zulip#4248
In one of the recent React Navigation upgrades, the animation for a
new screen entering and leaving the stack, on Android, changed.

We like how it was before [1]. So, put it back.

[1] zulip#4249 (comment)
In one of the recent React Navigation upgrades, a rapid sideways
scroll animation was added for when you tap another tab to switch to
it. This feels better than having no animation, but it really
suggests that swiping ought to work to switch between tabs [1]. We
haven't been offering that; so, do that.

[1] zulip#4249 (comment)

Fixes: zulip#3997
@chrisbobbe chrisbobbe merged commit ad0b5f3 into zulip:master Sep 17, 2020
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 17, 2020
New in the recent React Navigation upgrades to v3 and v4, there's
now a transition animation when the loading screen exits. We don't
want to prolong the effect of the loading screen [1], so, get rid of
that exit animation.

Most accurately, we want to target the exit of the loading screen
and disable the animation there. But it seems that the
`animationEnabled` switch in `navigationOptions` operates on the
*entrance* of particular screen; I haven't found a similar option
for the *exit* of a particular screen.

A good workaround, to the extent that it's practical, would be to
tell all screens to disable their entrance animations if and only if
they're being reached directly from the loading screen. The only
reasonably simple way I can think of to implement this would be to
set `defaultNavigationOptions` to a function instead of an
object [2] and use that function's inputs to conditionalize on the
identity of the route being navigated *from*, i.e., the previous
route. Unfortunately, that doesn't seem possible; the `navigation`
object is the most likely-looking of those inputs, and logging shows
that it doesn't have data about the previous route, only the current
route (in `navigation.state`).

So, we use a heuristic: always cancel the entrance animation of the
main-tabs screen. This is supported by the following observations
(but naturally there may be edge cases I haven't considered):

- An extremely frequent and high-visibility action is to navigate
  from the loading screen to the main-tabs screen.
- We don't generally navigate from the loading screen to anywhere
  other than the main-tabs navigator.
- We don't generally navigate to the main-tabs screen from
  anywhere other than the loading screen.

[1] zulip#4249 (review)
[2] The best link I've found for this is
    https://reactnavigation.org/docs/4.x/headers/#using-params-in-the-title.
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Sep 17, 2020

Then please merge at will!

OK, done! Thanks for the review.

Hmm, one thing I noticed on iOS: the navigation away from the LoadingScreen (seen on startup) is now a slide-from-right, which looks a little odd—but maybe just because I'm not used to seeing it?

I agree that sounds odd. What we've previously had (and I just fact-checked this on my iPad, which is running v26.29.152) is that when we're done loading it goes immediately to the main screen, with no transition. That feels like the right behavior -- anything else just prolongs the effect of the loading screen. So fixing that would be a good followup task.

OK, just sent #4260 for this.

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Sep 18, 2020
New in the recent React Navigation upgrades to v3 and v4, there's
now a transition animation when the loading screen exits. We don't
want to prolong the effect of the loading screen [1], so, get rid of
that exit animation.

Most accurately, we want to target the exit of the loading screen
and disable the animation there. But it seems that the
`animationEnabled` switch in `navigationOptions` operates on the
*entrance* of particular screen; I haven't found a similar option
for the *exit* of a particular screen.

A good workaround, to the extent that it's practical, would be to
tell all screens to disable their entrance animations if and only if
they're being reached directly from the loading screen. The only
reasonably simple way I can think of to implement this would be to
set `defaultNavigationOptions` to a function instead of an
object [2] and use that function's inputs to conditionalize on the
identity of the route being navigated *from*, i.e., the previous
route. Unfortunately, that doesn't seem possible; the `navigation`
object is the most likely-looking of those inputs, and logging shows
that it doesn't have data about the previous route, only the current
route (in `navigation.state`).

So, we use a heuristic: always cancel the entrance animation of the
main-tabs screen. This is supported by the following observations
(but naturally there may be edge cases I haven't considered):

- An extremely frequent and high-visibility action is to navigate
  from the loading screen to the main-tabs screen.
- We don't generally navigate from the loading screen to anywhere
  other than the main-tabs screen.
- We don't generally navigate to the main-tabs screen from
  anywhere other than the loading screen.

[1] zulip#4249 (review)
[2] The best link I've found for this is
    https://reactnavigation.org/docs/4.x/headers/#using-params-in-the-title.
@chrisbobbe chrisbobbe deleted the pr-react-navigation-3-and-4 branch November 5, 2021 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-navigation dependencies Pull requests that update a dependency file
Projects
None yet
2 participants