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

Images inside Marker views are not always rendered on Android #100

Closed
priithaamer opened this issue Feb 26, 2016 · 53 comments
Closed

Images inside Marker views are not always rendered on Android #100

priithaamer opened this issue Feb 26, 2016 · 53 comments
Labels
bug Something isn't working

Comments

@priithaamer
Copy link

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

@christopherdro
Copy link
Collaborator

@priithaamer What version of android are you running?

@priithaamer
Copy link
Author

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

@labeeb
Copy link

labeeb commented Feb 29, 2016

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

@mihaisampaleanu
Copy link

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.

@heydabop
Copy link
Contributor

heydabop commented Mar 1, 2016

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

@stinju
Copy link

stinju commented Mar 3, 2016

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:

├── react-native@0.20.0
├── react-native-maps@0.3.0

@programmersharp
Copy link

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.

@DennisMG
Copy link
Contributor

Did anyone find a workaround for this issue??

@programmersharp
Copy link

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

@lellex
Copy link

lellex commented Mar 15, 2016

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>

@DennisMG
Copy link
Contributor

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

@programmersharp
Copy link

@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%.

@cnsilvan
Copy link

I had the same issue.

@subtirelumihail
Copy link

+1

@jrichardlai jrichardlai added bug Something isn't working Android labels Apr 21, 2016
@sungjinoh
Copy link

+1

1 similar comment
@madox2
Copy link

madox2 commented May 6, 2016

+1

@jonestheguitar
Copy link

+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.

@lwinkyawmyat
Copy link

+1
I had the same problem.

@rickysahu
Copy link

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

@nghenglim
Copy link

+1, Same with android 6.0 emulator

@heydabop
Copy link
Contributor

heydabop commented Jun 3, 2016

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

@BigPun86
Copy link

@jonesdar thanks mate, worked for me as well!

@pudgereyem
Copy link

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

@felipegarcia92
Copy link

felipegarcia92 commented Aug 18, 2016

@pudgereyem

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

@frnas
Copy link

frnas commented Aug 28, 2016

@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.

@maluramichael
Copy link

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.

@felipegarcia92
Copy link

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

@ericapply
Copy link
Contributor

@betiol That is not a replacement solution because

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

@hiddentao
Copy link

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.

@tugorez
Copy link

tugorez commented Apr 6, 2017

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

@mmailhos
Copy link

mmailhos commented Apr 7, 2017

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.

@macdonjo
Copy link

macdonjo commented Apr 8, 2017

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>

@tugorez
Copy link

tugorez commented Apr 8, 2017

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.

<Marker /> component

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>                                                                     
); 

<Markers /> component

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 :)

@hiddentao
Copy link

@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.

@formula1
Copy link

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

@tugorez
Copy link

tugorez commented Jul 5, 2017

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 <Icon /> component will put a "default" image as an icon while the "real" image is loaded.
Sorry for my bad english.

@yonahforst
Copy link

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

@ignivarahulsaini
Copy link

ignivarahulsaini commented Jul 18, 2017

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.

@martnd
Copy link

martnd commented Jul 19, 2017

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 <Marker source... AND my custom view.

eg.

<MapView.Marker
{...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

@yaronlevi
Copy link

yaronlevi commented Jul 26, 2017

I can confirm this bug still happens.
React Native @0.45.1
react-native-mapes@0.15.3

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>);

@hungdev
Copy link

hungdev commented Sep 6, 2017

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

@SeuZeRicardo
Copy link

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

RN: 0.41
Maps: 0.13

@chrismcleod
Copy link

I had the same issue with:
react-native@0.49.3
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 })
  }
}

@alvelig
Copy link
Contributor

alvelig commented Dec 12, 2017

Merging to #1870

@anaibol
Copy link

anaibol commented Feb 12, 2018

+1

@shafayeatsumit
Copy link

shafayeatsumit commented Mar 9, 2018

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.

@abhideepmallick
Copy link

Working solution: #2734 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests