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

Support for Presenting Modals on iOS and Android #522

Closed
salockhart opened this issue Jun 1, 2021 · 16 comments
Closed

Support for Presenting Modals on iOS and Android #522

salockhart opened this issue Jun 1, 2021 · 16 comments

Comments

@salockhart
Copy link
Contributor

Background

Occasionally in our app while building out flows, we have the need for "modals" so that the user can go do a one-off task.

Historically, we've solved this by using Modal from react-native and react-native-modal on iOS and Android, respectively. Presenting one of those modals wih an embedded navigation stack and screen worked well. However, we're now looking at introducing more of them, and we are looking for a way to improve the experience.

On iOS, we want to achieve a fully native modal presentation, as documented here in the HIG.

On Android, we're not quite sure yet - there's no one-to-one analog between the two platforms that I see as being readily available like it is on iOS.

Prior Art

In our initial investigation, we found that react-native-screens and the native stack they provide there for react-navigation provides this, as documented here: https://github.com/software-mansion/react-native-screens/blob/3.3.0/native-stack/README.md#stackpresentation

The way this appears to work is by declaring the screen like any other screen in the stack, but specifying a specific option to present modally. Then, you "navigate" to the modal same as you would any other screen. On iOS this works as we would expect, and on Android it appears they just push a screen to the stack as normal but force a "slide in from the bottom" animation.

navigation

After an initial investiation, I have the following questions:

  • Is this in scope of this project, since modals live outside of a standard "push/pop" navigation flow? I wouldn't expect this library to handle web modals, for instance. Maybe this is functionality that best lives in another associated project.
    • A counterpoint to this would be that this repo does handle tab based navigation, which is a bit more mobile-specific, so maybe there is room for mobile-specific modals?
  • Would the pattern of declaring modals as standard screens with special attributes would work here? They would require some special care. Would we want the presentation of a modal to impact the URL state, for instance?
  • And finally, is this something that you've thought about before? Do you have an idea of the level of effort we may be looking at to build this, if we decide that it is a good candidate for this project?
@grahammendick
Copy link
Owner

grahammendick commented Jun 1, 2021

Great to hear from you. In the docs, I recommend the Modal from the React Native. It’s 100% native on iOS and Android and you can embed a navigation stack in it on both platforms. You can see this in action in the Medley sample.

Why don’t you use the React Native Modal on Android? Why do you say there’s no one-to-one analog between the 2 platforms?

The way React Navigation handles Modals was forced upon them. In the early days, they had no native code at all. It was all written in JavaScript. So there was no way for them to make a Modal take up the whole view
without treating it like another screen (you can only do this kind of trickery on the native side).

Modals work better as child components rather than as something you ‘navigate’ to. For example, you might open a modal to gather data that you want to send back to the opening screen. If the Modal is a child component then it’s easy to talk to the opening screen because it’s in the same closure scope.

You said you’re looking for a way to improve the experience. Could you tell me about the problems you’re currently having?

@salockhart
Copy link
Contributor Author

You're right, and that's the direction we've been taking up til this point on both iOS and Android. It works great! But the ask from our design team was to see if we could get the sort of modal behaviour that you get on iOS where the previous screen gets inset, and the new one appears from the bottom. Then you can swipe down to close it, that sort of thing.

When I say there's not much of an analog on Android, I'm talking about that native modal behaviour. On Android we could likely stay using React Native Modal without a fuss.

I agree with making modals child components, it helps there to have the parent-child relationship, for sure.

When I say improve the experience, it's really about getting that native iOS behaviour for modals. I'm also content with the answer being "we can't do it" but I wanted to reach out and see if you had any ideas.

@grahammendick
Copy link
Owner

Ok, thanks for the clarification.

The behaviour you’re referring to was introduced in iOS 13 and React Native changed their Modal to support it. But they found a problem with it and backed it out and it’s not been fixed since (despite there being lots of issues and PRs on React Native about it)!

Why not apply React Native's original fix via a patch package and see if it works Ok for you? I’m not sure what problem they found with it, but it’s probably an edge case seeing as it got released at one stage.

@salockhart
Copy link
Contributor Author

Fantastic! I will give that a try. Thanks for your help!

@grahammendick
Copy link
Owner

No problem. There’s a good discussion on the PR that could shine some light on why they backed it out.

I hope you get it to work. Please let me know what you find out.

@salockhart
Copy link
Contributor Author

@grahammendick Thank you for your help on this. Foolishly, had I looked closer at the docs for React Native Modals, I would have found the API for presentationStyle on iOS.

This gets us 90% of the way there, which is great! The behaviour around "swiping to dismiss" is what it appears that RN removed support for, so we will dig further into that, too, but that's less of a priority for us.

I'll close this issue out, but if I find anything new I'll let you know

@grahammendick
Copy link
Owner

That's great. Keep me posted on your progress with "swipe to dismiss"

@grahammendick
Copy link
Owner

@salockhart you need to set modalInPresentation to NO to get swipe to dismiss working. Not sure why react native has hard-coded it to true. There's a PR for it but you could comment out the line and patch package it in the meantime

@grahammendick
Copy link
Owner

@salockhart Turns out React Native does support partial swiping to dismiss Modals. When the user starts swiping, React Native calls into your onRequestClose. It’s up to you to set the Modal's 'visible' prop to false (just like when pressing back on Android).

@salockhart
Copy link
Contributor Author

Interesting! Must have missed that behaviour before. I'll take a look at getting that incorporated!

@vbylen
Copy link
Contributor

vbylen commented Jan 28, 2022

@grahammendick onRequestClose is not firing on iOS.

I'm looking for other solutions!

edit: @mrousavy has been kind enought to provide us with a patch.

edit2: I recommend this patch by @jacobp100 over the other one as onDismiss was not working.

@grahammendick
Copy link
Owner

Thanks for the updates @10000multiplier

React Native could easily fix their Modal component. The reason they haven't is because everybody uses React Navigation (RN) or React Native Navigation (RNN) which have their own Modal.

But the React Native Modal is by far the best because it’s a child component and so you can pass it props. The Modals in RN and RNN are separate screens making it hard to pass and receive data.

When everybody is using the Navigation router then React Native will fix their Modal ;)

@mrousavy
Copy link

Honestly I disagree with using a Modal as a child component. I think it should be logically separated as a separate screen, just like in RN and RNN. Imo the Modal in React Native should not exist

@grahammendick
Copy link
Owner

grahammendick commented Jan 29, 2022

A Modal is just a way of showing information without getting in the way of the main screen content. In that sense, it's no different than a BottomSheet or a PopOver. So the Modal itself shouldn't be a separate screen.

But, I think what you’re talking about is the content of the Modal. Modals are different to BottomSheets and PopOvers because Modals can display their own stack of screens, separate from the parent stack. This is how the Navigation router handles that

<Modal>
  <NavigationHandler stateNavigator={contactNavigator}>
    <NavigationStack />
  </NavigationHandler>
</Modal> 

@vbylen
Copy link
Contributor

vbylen commented Jan 30, 2022

@grahammendick is it possible to also open the modal when navigating through a deeplink?

@grahammendick
Copy link
Owner

Let's take Twitter as an example. Say we want to deep link to a tweet and open a modal so the user can reply to that tweet. We build a link that navigates to Home → Tweet and pass a reply indicator in navigation data.

const link = stateNavigator.fluent()
  .navigate('home')
  .navigate('tweet', {id: 12, reply: true}).url;
stateNavigator.navigateLink(link);

Then on the Tweet screen we initialise the modal visibility from the reply indicator in navigation data

const Tweet = () => {
  const {data} = useContext(NavigationContext);
  const {reply} = data;
  const {open, setOpen} = useState(!!reply);
  return (
    <Modal visible={open}>
    </Modal>
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants