React-native-maps: Images inside Marker views are not always rendered on Android

Created on 26 Feb 2016  ·  53Comments  ·  Source: react-native-maps/react-native-maps

When using images within custom views on Markers like this, they are not always rendered on Android (sometimes they appear though):

<MapView.Marker coordinate={...}>
  <View>
    <Image source={require('./image.png')} />
  </View>
</MapView.Marker>

With adb logcat i always see this error when image is not rendered:

E/unknown:DraweeEventTracker(19143): 1b82b058: Draw requested for a non-attached controller e80013b. DraweeHolder{controllerAttached=false, holderAttached=false, drawableVisible=true, activityStarted=true, events=[ON_SET_HIERARCHY, ON_SET_CONTROLLER]}

Images on markers only appear correctly on Android, when using <Marker image="..." />

I'm using React Native 0.20

Android bug react-native

Most helpful comment

To get things working nicely on both Android and iOS (RN 0.40, Maps 0.13) I do:

const isAndroid = (Platform.OS === 'android')
const key = 'uniqueMarkerKey'
const markerImage = require('../images/marker.png') /* 480x480px */

return(
      <MapView.Marker
        key={key}
        anchor={{x: 0.5, y: 0.5}}
        flat={true}
        image={isAndroid ? markerImage : null}
        identifier={key}
        coordinate={{ latitude: .., longitude: }}
      >
        {isAndroid ? null : <Image source={markerImage} style={{ width: 480, height: 480 }} />}
      </MapView.Marker>
)

The limitation here is that the marker image needs to already be sized correctly for Android - but for iOS you can then use the style attribute to correct the width/height for display.

All 53 comments

@priithaamer What version of android are you running?

I've seen it with devices running Android 5.1. Had no chance to check with other versions yet.

I'm also facing this issue. If I set image source attribute image={imageSource} it work. While if we set as an Image component inside the marker, not rendering

<MapView.Marker coordinate={...}>
  <View>
    <Image source={imageSource} />
    <Text>hello</Text>
  </View>
</MapView.Marker>   

Shows Draw requested for a non-attached controller 1261b63 error

I'm facing the same issue, android 6.0 with RN version 0.20.

    <MapView.Marker
      key={car.key}
      coordinate={car.coordinates}
    >
      <Image source={pin} style={styles.pin} />
    </MapView.Marker>

Sometimes some markers are rendered without the image.

I've also experienced this. It usually seems to happen on the first marker being placed.

Also having this issue. It generally occurs when I first use an icon. Even if a marker renders correctly the first time, if I switch the marker state such that a different icon is used, the new icon will not be rendered. If I switch it back to the original, and then again back to the second icon, the second icon renders correctly. It might be related to whether the image has fully loaded by the time the marker is processed.

My marker definition is as follows:

        <MapView.Marker
          ref='marker'
          coordinate={coords}
          centerOffset={{ x: 0, y: purchase ? 0 : 0-(size/2) }}
          //onSelect={this.props.onSelect} // not supported on android yet
          onDeselect={this.props.onDeselect}
          onPress={this.props.onSelect}

          // need to change key in order for new image/opacity to take effect
          // https://github.com/lelandrichardson/react-native-maps/issues/65
        >
          <View key={bl.id+iconName+opacity} style={{opacity: opacity}}>
            <Image source={icon} style={{height: size, width: size}}/>
          </View>
        </MapView.Marker>

Versions:

├── [email protected]
├── [email protected]

I have similar issue.
Image is not shown in some cases in marker with custom view. I found one coincidence - images that are declared somewhere else on the view are shown in marker with custom view correctly too. Imagine somewhere in application (outside of the map) I have image with uri equals URL1 and it's shown correctly there. If I have image with the same uri equals URL1 in marker with custom view it's shown correctly in the map. But if I don't have image with uri equals URL2 outside of the map, in this case image is not shown in marker with custom view at all.

Did anyone find a workaround for this issue??

I still was not able to resolve the problem. Very frustrating(..

I had the same issue. The only way I found is to declare marker's images before MapView.

 <View style={styles.map}>
     {this.state.markers.map(marker => (
         <Image source={{uri: marker.image}} />
     ))}
     <MapView>
         {this.state.markers.map(marker => (
             <MapView.Marker
                 key={marker.id}
                 coordinate={marker.latlng}>
                 <Image source={{uri: marker.image}} style={{width: 42, height: 42}} />
             </MapView.Marker>
         ))}
     </MapView>
 </View>

@lellex I tried your solution but didn't work for me :(

@lellex I also used this approach to make workaround, and I thought it solved the problem. But after some testing I found cases when even this hack will not help. Believe me, it will not work in 100%.

I had the same issue.

+1

+1

+1

+1

A workaround is to initially render an invisible Image and then render the MapView after the image icon has loaded:

render() {
  let renderContent = null;
  if (!this.state.iconLoaded) {
    renderContent = <Image style={{opacity: 0}} source={MY_ICON} onLoadEnd={() => {this.setState({iconLoaded: true});}}/>
  } else {
    renderContent = <MapView> etc.
  }
  return renderContent;
}

Alternatively update the marker after the image loads:

<MapView.Marker
  key={this.state.iconLoaded ? 'markerLoaded' : 'marker'}>
  <Image source={MY_ICON} onLoadEnd={() => {if (!this.state.iconLoaded) this.setState({iconLoaded: true});}}/>
</MapView.Marker>

Downside of this is that the user sees the icon 'pop' into the empty marker.

+1
I had the same problem.

Same problem here, and its on the ios simulator as well as android.

+1, Same with android 6.0 emulator

@jonesdar Thank you for that fix!! It's been incredibly helpful.

@jonesdar thanks mate, worked for me as well!

I solved this problem for now by declaring an <Image> before <MapView>. Using React Native 0.29.2.

@pudgereyem

How is that you did that?
Edit: solved, I did it just as @lellex did

@felipegarcia92, and for future reference:

By adding an image element before the mapview in the render function. I've verified this myself, eg:

<Image
style={{opacity: 0}}
source={require('SRC_OF_IMAGE')}
/>
<MapView />

This ensures that the image resource for the marker is loaded before the mapview tries to render it, I guess.

Has someone a clue why this is happening? And a solution without workarounds? For me it happens when i try to load more pins some of the images are then removed. The pins themself are still existing.

Sorry I can't help now, that workaround was enough at the time for me. Hope this gets fixed 😃

+1
Same problem here with react 0.41.2 and maps 0.13.0

Same problem here with react 0.42 and maps 0.13.0

+1
Same problem with react native 0.42 and maps 0.13.0

Same problem here, react native 0.42 and maps 0.13.0. It really is an issue.

+1
Same problem here, react native 0.41.2 and maps 0.13.0.

change the tag to image={imgMarker} on MapView.Marker and define constant with the image out of class: Ex: const imgMarker = require('.....');
Ex: <MapView.Marker key={"marker-" + index} image={imgMarker} .... />
this solved for me.

@betiol That is not a replacement solution because

  1. you cannot specify the style such as width and height.
  2. You cannot nest components

To get things working nicely on both Android and iOS (RN 0.40, Maps 0.13) I do:

const isAndroid = (Platform.OS === 'android')
const key = 'uniqueMarkerKey'
const markerImage = require('../images/marker.png') /* 480x480px */

return(
      <MapView.Marker
        key={key}
        anchor={{x: 0.5, y: 0.5}}
        flat={true}
        image={isAndroid ? markerImage : null}
        identifier={key}
        coordinate={{ latitude: .., longitude: }}
      >
        {isAndroid ? null : <Image source={markerImage} style={{ width: 480, height: 480 }} />}
      </MapView.Marker>
)

The limitation here is that the marker image needs to already be sized correctly for Android - but for iOS you can then use the style attribute to correct the width/height for display.

@hiddentao does your workaround works with remote images { uri: 'http://someresource...' } ?

The problem with @hiddentao solution is that you can not set a style for image property.

Also, workarounds about loading Images before MapView are not ideal in the case you wish to update a list of Markers in the background (and basically have Markers lifecycle).
In that case, I had to wait for all _background workers_ to finish before rendering Images, then wait for LoadEnd callbacks to finish (and store status somewhere in this.state), then finally render the MapView. All of that makes the entire page rendering slower.

This is for callouts, but I believe the problem is the same thing.

If I click Marker A, nothing will show but an empty white box. Then I'll click Marker B and it won't load either. Then I'll click Marker A again and it still won't load anything. I'll click Marker B, still nothing. My next click on Marker A will show the image. My next back to Marker B will also show the image. Try it. It works every time.

A, B, A, B, A (works), B (works). 5th and 6th clicks. So a marker needs to be tapped 3 times. You can replicate this by just tapping a marker 3 times.

    <MapView.Marker
              key={marker.key}
              coordinate={marker.latlng}
              title={marker.title}
              description={marker.description}
            >

              <MapView.Callout style={styles.annotation}>
                <Image
                  key={marker.key}
                  source={{ uri: marker.image }}
                  style={styles.thumbnail}
                />
              </MapView.Callout>
            </MapView.Marker>

yet another workaround in case you're rendering a dynamic list of markers (which in my case are region based, so everytime the user changes the region I make a request and rerender new markers).
I'm using redux.

\

const Marker = ({                                                                
  coordinate,                                                                    
  following,                                                                     
  image,                                                                         
  loaded,                                                                        
  onLoad,                                                                        
  onPress,                                                                       
}) => (                                                                          
  <MapView.Marker                                                                       
    style={styles.marker}                                                        
    coordinate={coordinate}                                                      
    onPress={onPress}                                                            
  >                                                                              
    <View>                                                                       
      {(image && !loaded) &&                                                     
        <Image                                                                   
          source={image}                                                         
          style={styles.invisible}                                               
          onLoad={onLoad}                                                        
        />                                                                       
      }                                                                          
      {(!image || !loaded) &&                                                    
        <View style={styles.container}>                                          
          <Icon name="logo" size={35} color="#432A5F" />                         
        </View>                                                                  
      }                                                                          
      {(image && loaded) &&                                                      
        <Image                                                                   
          source={image}                                                         
          style={styles.image}                                                   
        />                                                                       
      }                                                                     
    </View>                                                                      
  </MapView.Marker>                                                                     
); 

\

const Markers = ({                                                               
  markers,                                                                       
  onMarkerPress,                                                                 
  onMarkerLoad,                                                                  
}) => (                                                                          
  <View>                                                                         
    {markers.map(m =>                                                            
      <Marker                                                                    
        {...m}                                                                   
        key={m.id}                                                               
        onPress={() => onMarkerPress(m.id)}                                      
        onLoad={() => onMarkerLoad(m.id)}                                        
      />,                                                                        
    )}                                                                           
  </View>                                                                        
);  

So you have to include a flag "loaded" for every marker in order to know which ones were loaded.
In my case, if the image is not being loaded I render an Icon and the Image(with opacity 0) until the image is loaded.

I Hope you find this helpful :)

@tugorez I haven't tried it with remote images, sorry. For Android it probably won't work since the documentation states that the image must be local.

@MathieuMailhos You're right, the Android image can't be styled. Then again, I only needed to control the size of the marker, nothing else.

A project that I'm working on also ran into this bug

Like others said, a work around can be done through

  • Loading "Invisible"

    • 0 width and 0 height didn't work for me

    • for me { position: absolute, opacity: 0, width: 1, height: 1 }

  • Removing on load
  • Readding next tick

Here is what I know

  • It does not matter whether its a url or a file
  • It does not matter whether the url has been prefetched (preloaded)
  • can be recreated quite simply by

    • displaying the image with { width: 1000, height: 1000 }

    • updating after 500 milliseconds to { width: 50, height: 50 }

    • the image does not change

What I have not tried

  • Changing the url to attempt to trigger an update
  • adding an unuseful property to attempt to trigger an update

I saw else where this.forceUpdate() may also work however that was not tried

Im sorry not to being enough prepared (I'm not an Android nor IOS developer) to fix this bug :/ .
I think as a workaround is better to handle it locally on the custom marker component.
Writing a custom component like this avoids the image's problem (at least in Android 5 & 6 with RN43).

import React, { Component, PropTypes } from 'react';
import { Image, View } from 'react-native';
import MapView from 'react-native-maps';
import Icon from 'components/Icon';
import styles from './styles';

class Marker extends Component {
  constructor(props) {
    super(props);
    this.state = { loaded: false };
  }

  onLoad() {
    this.setState({ loaded: true });
  }

  render() {
    const { image, latitude, longitude, onPress } = this.props;
    const loaded = image && this.state.loaded;
    const onLoad = this.onLoad.bind(this);
    return (
      <MapView.Marker coordinate={{ latitude, longitude }} onPress={onPress}>
        <View style={styles.container}>
          { loaded &&
            <Image source={{ uri: image }} style={styles.image} />
          }
          { !loaded &&
            <View style={styles.logo}>
              <Icon name="logo" color="#4A0063" size={35} />
              <Image source={{ uri: image }} onLoad={onLoad} />
            </View>
          }
      </MapView.Marker>
    );
  }
}

Marker.propTypes = {
  image: PropTypes.string,
  latitude: PropTypes.number,
  longitude: PropTypes.number,
  onPress: PropTypes.func,
};

export default Marker;

The \ component will put a "default" image as an icon while the "real" image is loaded.
Sorry for my bad english.

just a heads up, i solved my problem by removing resizeMode from my Image component

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.

Hey guys,

I've been having issues with this and nothing was working. I am displaying an image with some text over the top of it and I would only ever see the text.

It works now though by using both

eg.

{...this.props} collapsible={false}
key={this.props.i}
onPress={() => this.onMarkerPress(location)}
image={site_pin}
coordinate={this.props.coordinate}>

                    <View >
                        <Image source={site_pin} style={{width: 37, height: 55}} >
                            { price ?
                                <Text collapsible={false}
                                    allowFontScaling={false}
                                    style={{
                                        position: 'absolute',
                                        top: price > 99 ? price > 999 ? 15 : 13 : Platform.OS === 'ios' ? 11 : 14,
                                        left: Platform.OS === 'ios' ? 10 : 11,
                                        zIndex: 66,
                                        fontSize: price > 99 ? price > 999 ? 9 : 10 : 13,
                                        textAlign: 'center'
                                    }}>${price}</Text> : null}
                        </Image>
                    </View>
                </MapView.Marker>

As you can see, there's a little bit of faff with aligning text image and this may be a pixel or two out on some devices but so far its the best I've come up with and seems to work on every device/OS I've tested so far.

Hope it helps someone.

Martin

I can confirm this bug still happens.
React Native @0.45.1
[email protected]

I use my custom view as a marker:

const MyMarker = ({ title, latitude, longitude }) =>
  (<MapView.Marker coordinate={{ latitude, longitude }}>
    <View>
      <Image
        style={{ width: 100, height: 42 }}
        source={{ uri: 'https://some_image_url...' }}
      />
      <Text>
        {title}
      </Text>
    </View>
  </MapView.Marker>);

@yonahforst thank you, it works for me.
removing resizeMode from my Image component

@yaronlevi I have the same issue.
I tap 2x to apper the Image.

RN: 0.41
Maps: 0.13

I had the same issue with:
[email protected]
android version 7.1.2 API 25

I seem to have solved it by using a module for all my assets and having a HOC preload them before displaying any other part of the app.

assets.ts

export const images = {
  myCoolIcon: require('../../assets/images/my-cool-icon.png')
}

AssetPreloader.tsx

import * as Assets from '../assets'
import * as React from 'react'

import { Image, View } from 'react-native'

export class AssetPreloader extends React.Component<any, any> {

  constructor(props: any) {
    super(props)
    const images = Object.values(Assets.images)
    this.incLoadCount = this.incLoadCount.bind(this)
    this.state = { preloaded: 0, total: images.length, images: Assets.images }
  }

  render() {
    return (
      <View style={ { opacity: 0, position: 'absolute' } }>
        { Object.entries(this.state.images).map(([ key, src ]: any) => <Image key={ key } source={ src } onLoad={ this.incLoadCount } />) }
      </View>
    )
  }

  private incLoadCount() {
    this.setState({ preloaded: this.state.preloaded + 1 }, () => {
      if (this.state.preloaded >= this.state.total) this.props.onComplete()
    })
  }
}

App.tsx

import * as React from 'react'
import { AssetPreloader } from './components/AssetPreloader'

export class App extends React.Component<any, any> {

  constructor(props: {}) {
    super(props)
    this.donePreloadingAssets = this.donePreloadingAssets.bind(this)
    this.state = { loading: true }
  }

  render() {
    return (
      this.state.loading ? 
      <Preloader onComplete={ this.donePreloadingAssets } /> 
      :
      <MainContent />
    )
  }

  private donePreloadingAssets() {
    this.setState({ loading: false })
  }
}

Merging to #1870

+1

I have found a workaround of this problem. first you set the coordinate into an animated region.
this.state = { coordinate: new MapView.AnimatedRegion({ latitude: 0, longitude: 0, }) }

<MapView
  style={styles.container}
  initialRegion = {initCoordinates}
  ref={ref => { this.map = ref; }}
>
  <MapView.Marker.Animated
    coordinate={this.state.coordinate}
    ref={marker => { this.marker = marker; }}
    >

    <Image
      style={{
        width: 40,
        height: 40,
        resizeMode: 'contain',
        zIndex:3
      }}                
      source={markerIcon}
    />
  </MapView.Marker.Animated>           
</MapView>      

and then you update the marker with new coordinates, in the following way.
this.state.coordinate.timing(newCoordinate,1000).start();
and this is the full example in a public gist.
marker smooth animation example.

Note: placing marker Image component before Mapview will also solve the issue. However, when you drag/pan the map it will not move the marker around, the marker will stay in the same place.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Hyllesen picture Hyllesen  ·  48Comments

kytwb picture kytwb  ·  54Comments

ghost picture ghost  ·  197Comments

vinceyuan picture vinceyuan  ·  61Comments

R1ckye picture R1ckye  ·  68Comments