Skip to content

How to implement startActivityForResult or similar? #1778

@FMCorz

Description

@FMCorz

Hi,

This is a more a question than an issue, and I've searched but haven't found anything related to this. Effectively, what I'd like to be able to do is to navigate to a view, and return a result from that view as I dismiss it. From this point I'll call this a ResultNavigator. I believe this is similar to this feature of Native Navigation: https://github.com/airbnb/native-navigation/blob/master/docs/api/navigator/present.md.

A ResultNavigator is typically useful when the user navigates to pick something, or make a choice:

  • selecting a user to send a message to from a list of users.
  • confirming an action.

I'll give you that there are ways to solve this using Redux for instance, though I do not necessarily think that Redux is the right approach here. I could have a place in my store which contains the action chosen by the user, or a list of users to act on, but I don't find that clean. Especially when the ResultNavigator is meant to be re-used in many different locations for other means (typical for a screen to pick a user), it really has nothing to do with the state of my app, what I'm looking at is just to receive a result.

To be honest, I have succeeded in making this work (I'll leave an example below), but the way I've done it is so ugly that I'd like to do it properly. I am certain there are ways to achieve this with a dedicated Router and Navigator, but as I'm new to this library I could do with some guidance.

Here are my requirements:

  1. The ResultNavigator is likely to need to be above all other views.
  2. The Android back button should dismiss and return a, for instance, cancelled result.
  3. The ResultNavigator could contain its own navigation (multi-step process).
  4. We can cancel or return a result from any step of the ResultNavigator.

I thought of using a new NavigationAction (dismiss) to indicate to the ResultNavigator that it should discard itself, and I could use the callback _onNavigationStateChange to respond to the pending promises. But it doesn't seem that I can define new NavigationActions, and without that, I'm not certain how to detect when the ResultNavigator should be dismissed or was given a result. I could use props to pass around callbacks, but as the ResultNavigator would likely be a wrapper around another Navigator, I don't think this would work nicely...

Any suggestion is welcome!
Thanks!
Fred

The workaround:

I use the navigation state to pass around callbacks which I inject as properties into the screens. I also inject the property dismissMe which is used when the screen is discarded (back button), or when a thing was chosen.

Note: this was also implemented in such a way that my components ignore everything about the navigation library.

// Navigators.
const ChooseSomething = StackNavigator({
  SimpleChoose: {
    screen: (props) => {
      const goBack = () => props.navigation.dispatch(NavigationActions.back());
      return (
        <SimpleChooser
          onThingChosen={props.navigation.state.params.onThingChosen}
          onCancel={props.navigation.state.params.onCancel}
          dismissMe={() => goBack()}
          goToAdvanced={() => {
            return new Promise((resolve, reject) => {
              props.navigation.navigate('AdvanceChoose', {
                dismissMe: () => {
                  goBack();
                },
                onThingChosen: thing => {
                  resolve(thing);
                },
                onCancel: () => {
                  reject()
                }
              })
            });
          }}
        />
      );
    }
  },
  AdvanceChoose: {
    screen: (props) => {
      return (
        <AdvancedChooser
          onThingChosen={props.navigation.state.params.onThingChosen}
          onCancel={props.navigation.state.params.onCancel}
          dismissMe={props.navigation.state.params.dismissMe}
        />
      );
    }
  },
});

function createRootNavigator(initialRoute = 'NotLoggedIn'){
  return StackNavigator({
    NotLoggedIn: {
      screen: Welcome,
    },
    LoggedIn: {
      screen: (props) => {
        return <Home chooseSomething={() => {
          return new Promise((resolve, reject) => {
            props.navigation.navigate('ChooseSomething', {
              onThingChosen: thing => {
                resolve(thing);
              },
              onCancel: () => {
                reject();
              }
            });
          });
        }} />
      },
    },
    ChooseSomething: {
      screen: ChooseSomething
    },
  }, {
    headerMode: 'none',
    mode: 'modal',
    initialRouteName: initialRoute
  });
}

// Components.
class Home extends Component {
  pickAthing = (day) => {
    this.props.chooseSomething().then(thing => {
      doSomethingWithTheThing(thing);
    }).catch(() => {
      console.log('Pick cancelled');
    });
  }
}

// Chooser, screen 1.
class SimpleChooser extends Component {

  responded = false;

  componentDidMount() {
    this.responded = false;
    BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
  }

  componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
    if (!this.responded) {
      // The window is being dismissed, make sure we responded.
      this.cancel();
    }
  }

  handleBackButton = () => {
    this.props.onCancel();
    this.responded = true;
    this.props.dismissMe();
    return true;
  }

  onChooseHere(thing) {
    this.props.onThingChosen(thing);
    this.responded = true;
    this.props.dismissMe();
  }

  onClickOnSomething() {
    this.props.goToAdvanced().then((thing) => {
      this.props.onThingChosen(thing);
      this.responded = true;
      this.props.dismissMe()
    }) 
  }
}

// Chooser, screen 2, optional.
class AdvancedChooser extends Component {
  onClickOnSomething() {
    const thing = 'aThing';
    this.props.onThingChosen(thing);
    this.props.dismissMe();
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions