React-native-maps: Custom marker with Image won't render first time on Android

Created on 2 Jan 2017  路  44Comments  路  Source: react-native-maps/react-native-maps

When making custom MapView.Marker with children as prop to Marker like this:

<MapView.Marker coordinate={coordinate}>
  <View source={styles.container}>
    <Image source={require('./my-image.png')} style={style.icon}>
    // other stuff here
  </View>
</MapView.Marker>

It does not render image on first render time on Android.

But if i use Marker's image prop like this:

<MapView.Marker coordinate={coordinate} image={require('./my-image.png')}>
  <View source={styles.container}>
  // other stuff here
</MapView.Marker>

It does render on first time on Android too.

But I cant use second option because I have to set custom size to the image via styles. So either would be nice to be:

  1. able to style image which is passed by image prop OR
  2. fix bug where image won't render on first try.

Also note that in order to notice the first render bug you have to actually TURN OFF the app and then back on. :) Tell me if you experience this too

bug

Most helpful comment

Here's a workaround that doesn't require hiding random elements:

class PinMarker extends Component {
  state = {
    initialRender: true
  }
  render() {
    return (
      <MapView.Marker coordinate={coordinate}>
        <Image
          source={...}
          onLayout={() => this.setState({ initialRender: false })}
          key={`${this.state.initialRender}`}
        />
      </MapView.Marker>
    )
  }
}

The idea is to force the <Image /> to re-render after it is loaded the first time.

All 44 comments

I'm having the same issue. I have also tried to pass the image as a prop to MapView.Marker however compromised the styles.

I believe it's happening for the same reason as the bug exhibited here:

https://github.com/airbnb/react-native-maps/pull/814

... the image is loaded async and when it's "ready" nothing forces an update. The reason this happens is because on Android custom markers are rendered by generating a bitmap. But when the image gets loaded nothing triggers the re-generation of the bitmap. The only thing that ever triggers the bitmap to regenerate currently is when there is a change in the view hierarchy.

Okay so is there is hacky fix to this at the moment? :D

@henrikra you could try forcing a view hierarchy change in the Image component's onLoad prop. The way I forced a view hierarchy change is by having some hidden text that changes value: https://github.com/airbnb/react-native-maps/pull/814/files#diff-36881e0ead965fdaf7cdcadb269a42cfR103

This issue has been reported a bunch of times recently. Definitely a bug being experienced by everyone.

Yeap, could this be a priority to fix since it has a big impact on the user experience?

You're welcome to fix it @Jazz747 ... like most commits to this particular repo, things improve when someone steps up to the plate. We currently use the workaround I mentioned since it's difficult to fix this bug.

I'd like to but I have nowhere near experience and knowledge to try it since I've just started coding :/ @gilbox could you elaborate on how to "force a view hierarchy" in this particular case to get all custom markers displayed for the first time the app's started?

@Jazz747 I mean something other than your marker has to change in order to trigger your marker to render again. Here is a concrete example of that ... when something else changes, it means that a component will re-render. The marker in question should be part of that view hierarchy, iow the marker should be in that component, or a descendent of that component.

Yes, figured it out in the meantime :) calling state to switch a simple boolean variable linked to the markers to trigger the re-render. I've put it in componentDidMount() and it works. I'll try to pull the code and dig into it to try to better understand the complete mechanics of the render,

I can reproduce this issue on both emulator and real devices.

Using @gilbox's idea of having a hidden element that changes using the image's onLoad, here's a hack that works - onLoad force a reload of the component which regenerates a random number in the hidden text element...BARF! ...but works! Need a fix!

<Image source={pin} onLoad={() => this.forceUpdate()}>
     <Text style={{width:0, height:0}}>{Math.random()}</Text>
</Image>

I have my Custom Marker function and i want to be able to give it a style to my image because it's original size it's 200 x 200. I've been struggling with this because when i try the image props from MapView.Marker, i can't resize my image. Can someone share me their temporal fix or their solution to this issue?
I'm kind of new in RN , so i'm not able to find a way to make it work on Android. iOS it's working properly.
`

return (
  <MapView.Marker
    key={location._id}
    coordinate={{ latitude: location.geometry.coordinates[0], longitude: location.geometry.coordinates[1] }}
    title={location.name}
    description={location.description}
    style={styles.marker}
    anchor={{ x: 0.20, y: 0.5 }}
  >
    <Image
      source={img_marker}
      style={{ width: 20, height: 20 }} 
    />
  </MapView.Marker>
)

`

I need to display image from the internet but had the same problem with Image component I ended up using WebView instead.

<MapView.Marker coordinate={coordinate}>
  <MapView.Callout>
    <View>
      <WebView source={{uri: 'https://...'}} style={{height: 200, width: 200}} />
      <Text>A Building</Text>
    </View>
  </MapView.Callout>
</MapView.Marker>

screenshot_1488382308

@shahonseven how did you manage to show a custom image marker ? Your code seems to solve the issue with images inside the Callout, however i cant manage to show my custom image markers to android .

@seppemarotta this is my code for custom image marker

<MapView.Marker coordinate={marker.coordinate} image={{uri: marker.icon}}>
  ...
</MapView.Marker>

screenshot_1488565622

but as mentioned here, you cannot resize it via style. If you want to control the marker via style you probably should take a look here and here.

<MapView.Marker coordinate={marker.coordinate}>
  <Image
    style={styles.customMarker}
    source={{uri: marker.icon}}
    onLoad={() => this.forceUpdate()}>
      <Text style={{width:0, height:0}}>{Math.random()}</Text>
  </Image>
</MapView.Marker>

screenshot_1488565683

screenshot_1488565689

@shahonseven Thanks a lot , i understand everything now! I'll give it a try . May i ask how much width and height does your custom marker image has ?

@seppemarotta for my custom image marker, I set 100x100 and for the custom callout image I set 200x200.

Having this issue as well and it is a huge deal.

When adding an hidden Text or View inside the picture with a random element inside, it works well but the parent style changes: it becomes a square while it should be a circle. Like if it modified parent's style.

Does anyone have a fix for this ?

any news on that ?

any news on this issue? workarounds didn't work for me. I even set a this.forceUpdate on a button call and change state and props and images didn't get rendered.

@henrikra @justrdk any workaround???

I also experienced the same thing in Android with custom callout image inside. I have to display so many markers on the map but some of them doesn't have the image rendered :(

Here's a workaround that doesn't require hiding random elements:

class PinMarker extends Component {
  state = {
    initialRender: true
  }
  render() {
    return (
      <MapView.Marker coordinate={coordinate}>
        <Image
          source={...}
          onLayout={() => this.setState({ initialRender: false })}
          key={`${this.state.initialRender}`}
        />
      </MapView.Marker>
    )
  }
}

The idea is to force the <Image /> to re-render after it is loaded the first time.

@justrdk have you tried to move styles of the hidden text component into a StyleSheet method like the following?

<Image source={pin} onLoad={() => this.forceUpdate()}>
     <Text style={styles.hiddenText}>{Math.random()}</Text>
</Image>
...

const styles = StyleSheet.create({
    hiddenText: {width:0, height:0},
    ....
    ....
})

It gives a bit advantage in my app.

It seems like that the module can not (or does not) refresh native Google Map app when React-Native refreshing. Well, I guess so. I'm sure @lelandrichardson could give more accurate explanation about it.

@efkan I ended up setting a timer and rendered icons first and then rendered the images.

@justrdk I had tried setting timers too. But I could not manage it.

Is there any clue for me :)

I solved this issue by using the same image again outside the mapViw. but with 0 width and 0 height.

<Image style={{width:0,height:0}} source={ Constants.Images.MarkerImage } />
So that Constants.Images.MarkerImage will load before mapView loads markers.

Hello, every one.
I have one problem on map.maker.
I want to use react-native-circular-action-menu as maker.

             <MapView
                style={ styles.container }
             >
              <MapView.Marker
              coordinate={ this.state.region }
              onPress = {()=> this.showMenu()}
              >
              <View >
                <ActionButton buttonColor="rgba(231,76,60,1)" >
                  <ActionButton.Item buttonColor='#9b59b6' title="New Task" onPress={() => console.log("notes tapped!")}>
                    <Text>aaaa</Text>
                  </ActionButton.Item>
                  <ActionButton.Item buttonColor='#3498db' title="Notifications" onPress={() => {}}>
                  <Text>bbb</Text>
                  </ActionButton.Item>
                  <ActionButton.Item buttonColor='#1abc9c' title="All Tasks" onPress={() => {}}>
                  <Text>ccc</Text>
                  </ActionButton.Item>
                </ActionButton>
            </View>
            </MapView.Marker>
          </MapView>

But It is not working with touch event.
Please help me!

@WonderDev21 put ActionButton after </MapView>

Hi @henrikra :) Did anything further come of this? I'm the same position currently in that I've got a child component MapMarker rendering many Map Markers and I want to dynamically update the image of a MapMarker if its selected and vice versa but it only appears to be that you dynamically set images for map markers is through the prop 'image' on the MapView.Marker but then you don't get to applying stying or anything.

I'm running a question over here https://stackoverflow.com/questions/46295212/how-do-i-dynamically-update-collection-of-mapmarker-images-based-on-whether-sele additionally

Any new updates, i'm stacked

Same, looking for a fix.

Hello @AhmedAly86 and @ewein .

I use j-nolan's solution by changing it a little. Thanks to solutions of gilbox, sators and j-nolan we have workarounds for now.


class PinMarker extends Component {
  state = {
    extraData: false
  }

  componentDidMount () {
    let self = this
    setTimeout(()=>self.setState({ extraData: true }), 100)
  }

  render() {
    return (
      <MapView.Marker coordinate={coordinate}>
        <Image
          source={...}
          key={`${this.state.extraData}`}
        />
      </MapView.Marker>
    )
  }
}

Merge to #1870

facing this error in Android
react-native-maps -> 0.21.0
react -> 16.4.1
react-native -> 56
Android -> Not working

To replicate -> simply setting markers after component did update
Tried -> Tried all above solutions .and some times it renders first and some times it dosen't

@pawarvijay @efkan @michaelstokes @WonderDev21 @justrdk @alvelig @j-nolan
could u guys or anyone else answer this question :
https://stackoverflow.com/questions/51915353/react-native-maps-markers-image-doesnt-show-using-custom-marker-in-react-nativ

Update in your package.json to "react-native-maps": "^0.22.0"

Please use Unique Key about Marker. Then your marker issue will be disappear on mapview(android).
of course your ios version will work well for you. It is just related with native module for map rendering in android version.

I ran into this today. I was able to solve it with the following:

render() {
  const { selected = false, coordinate } = this.props;

  const icon = selected
    ? require(...regular pin...)
    : require(...selected pin...);

  if (Platform.OS === 'android') {
    return <Marker image={icon} coordinate={coordinate} />;
  }

  return (
    <Marker coordinate={coordinate}>
      <Image source={icon} />
    </Marker>
  );
}

@efkan 's solution here seemed to work for me. Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

priithaamer picture priithaamer  路  53Comments

rborn picture rborn  路  75Comments

Hyllesen picture Hyllesen  路  48Comments

alioguzhan picture alioguzhan  路  46Comments

bunnakal picture bunnakal  路  51Comments