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

Image component inside Custom Markers and Callouts in Android #1870

Closed
alvelig opened this issue Dec 11, 2017 · 342 comments
Closed

Image component inside Custom Markers and Callouts in Android #1870

alvelig opened this issue Dec 11, 2017 · 342 comments
Labels
bug Something isn't working

Comments

@alvelig
Copy link
Contributor

alvelig commented Dec 11, 2017

Image component inside Custom Markers and Callouts in Android not appearing or rendering.

Merging all issues to this.

Please, clone https://github.com/alvelig/react-native-maps-example and try if you can reproduce.

Mention your lib version, react-native version, Android version, and is it a device or emulator.

@solodream
Copy link

Tested with:

  • react-native 0.50.4
  • react-native-maps 0.18.3
  • play-services-maps 10.2.4/11.0.4
  • compileSdkVersion 26
  • buildToolsVersion "26.0.3"
  • device
    • A: Sony XZ Premium (Android 8.0.0, Google Play Service 11.9.51)
    • B: Android emulator (7.1.1 Google APIs Intel x86, Google Play Service 11.7.43)
    • C: Android emulator (6.0.0 Google APIs Intel x86, Google Play Service 11.7.43)

Result:

targetSdkVersion 23, A/B targetSdkVersion 24, A/B
Debug, First Run No No
Debug, Ctrl-m/Reload Yes No
Release No No
targetSdkVersion 23, C targetSdkVersion 24, C
Debug, First Run No No
Debug, Ctrl-m/Reload Yes Yes
Release No No

As a work around, you can try setting targetSdkVersion to a number no more than 23, then

constructor(props) {
	super(props);
	this.state = {
		imagemarker_workaround_cnt: 0
	};
}
componentDidMount() {
	let self = this;
	setTimeout(() => {
		self.setState({imagemarker_workaround_cnt: 1});
		setTimeout(() => {
			self.setState({imagemarker_workaround_cnt: 2});
		}, 10);
	}, 10);
}
render() {
	if (this.state.imagemarker_workaround_cnt == 1) return <View></View>;
	return <MapView>
		{this.state.imagemarker_workaround_cnt == 0 && 
		<MapView.Marker coordinate={{latitude: 0, longitude: 0}}>
			<View><Image source={require('./icon.png')} style={{width: 1, height: 1}} /></View>
		</MapView.Marker>}
	</MapView>;
}

@alvelig
Copy link
Contributor Author

alvelig commented Dec 12, 2017

In my case images disappear on marker addition. I'm moving the map and adding only those which fall into the viewport. So they consistently disappear. If I put other elements in the custom view, they are shown. So there's a huge problem with Image rendering. Great investigation anyway.

@foyarash
Copy link
Contributor

foyarash commented Dec 14, 2017

I tried to fix this few months ago but failed to succeed, I guess i've done some messy code.

My idea was actually to implement here https://github.com/react-community/react-native-maps/blob/master/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapMarker.java#L350 a bitmap caching system (https://developer.android.com/topic/performance/graphics/cache-bitmap.html) which would use the marker key as a key of our cache map, and the Bitmap as a value. Maybe this is a wrong idea, just proposing this if someone has time to try and all that stuff

@elieteyssedou
Copy link

Can someone fix this issue ?
It is really annoying that we can't have images on markers. I don't know if someone found a workaround ?

@alvelig
Copy link
Contributor Author

alvelig commented Dec 18, 2017

For now use Marker's image prop. That is working ok. Having Image in custom view may not be resolved recently.

@alvelig
Copy link
Contributor Author

alvelig commented Dec 18, 2017

@foyarash The problem is not the caching system, the problem is that every time it creates a bitmap of the view, and sometimes it does not happen or I don't know what happens, but it does not draw.

@foyarash
Copy link
Contributor

Yeah that's what I noticed.

I actually tried some solution and particularly invalidating the MapView after the image has been loaded, but that didn't work.

That's really a weird behavior and I'm curious about what is wrong with this

@michaelhitzker
Copy link

Putting the Image into a WebView and did the trick for me

@pankajnegi-soar187
Copy link

Putting the Image into a WebView and did the trick for me

@michaelhitzker I am facing the same issue in Android. Are you using http image url in WebView ?? can you please share your code ??

@gelobelo-espiritu
Copy link

Image Background not working still

@akshaygpta007
Copy link

akshaygpta007 commented Apr 22, 2019

Everyone seems to be struggling with this issue here. Let me tell you a proper workaround for this. First of all, using WebView is the only proper solution for this right now. Now rather than using webview for your image. you should be having only webview in the callout and create a proper html for your whole callout view and insert it into your webview. For ex:

<MapView.Callout> <View> <WebView style={{ height: calloutDispensaryMainImageHeight * 1.9, width: calloutDispensaryMainContainerWidth, }} source={{ html: Dispensary(calloutDispensaryMainImageHeight, calloutDispensaryMainContainerWidth, ${url}${cover_image}, name, address, timing, distance, services, ), }} /> <View style={styles.viewOrEnterDispensaryButtonContainer}> <Text style={styles.viewOrEnterDispensaryButtonText}> {strings.dispensariesList.enterDispensary} </Text> </View> </View> </MapView.Callout>


Dispensary file code.

const Dispensary = ( height, width, coverImage, name, address, timing, distance, services, ) => ( <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <style> * { margin:0; padding:0; } </style> </head> <body> <div> <img src="${coverImage}" alt="main image" width="${width}" height="${height}" /> <div style='display: flex; margin-top: 8px;margin-bottom: 8px;'> <p style='flex: 1; font-weight: bold; font-size: 14px; color:${colors.green}'> ${name} </p> <p style='display: flex; font-weight: bold; font-size: 12px;color:${colors.green};'> <img src="image-server-url"(not local) alt="time" width="16" height="16" style='margin-right:4px' /> ${timing} </p> </div> <div style='display: flex;flex: 1'> <div style='display: flex;flex: 1;'> <p style='flex: 2;font-size: 12px;color:${colors.green};'>${address}</p> <div style='background-color:${colors.green};width:1px;margin-left:8px;margin-right:8px'></div> <p style='flex: 1;display: flex;align-items: center;font-size: 12px;color:${colors.green};'>${${distance.toFixed(1)} mi}</p> </div> <div style='display: flex;flex: 1;justify-content: flex-end;align-items: center;'> ${isServiceAvailable(services, servicesNames.kiosk) ? <img src="image-server-url"(not local) alt="time" width="17" height="23" /> : '<p></p>'} ${isServiceAvailable(services, servicesNames.locker) ? <img src="image-server-url"(not local) alt="time" width="30" height="23" style='margin-left:4px' /> : '<p></p>'} </div> </div> </div> </body> </html> );

I know we are all mobile developers here, but writing a quick html is never a big deal, and in CSS too, there is flex box layout system like react native you can take profit of. So change you thought and use html in webview or wait till this is fixed.

This way, you can have multiple images in you callout, rendering exactly you wants and in the very first time, every image will show up.

In the screenshot below, you can see there are 4 images in one callout and callout in a single webview.

Screenshot_1555911407

@xarhsasi
Copy link

Could this work even with local images inside webview?

@akshaygpta007
Copy link

You have to save your local images somewhere on your server. That's where the solutions start. otherwise, there are ways by which you can show local images in webview too. You have to research that part.

@nemanjas1
Copy link

nemanjas1 commented Jul 24, 2019

People, what helped me is adding images inside of Text component. Please notice tree:

  • Marker
  • Callout with tooltip prop
  • View
  • View with absolute
  • then an condition, if ios or android for this part
    ios: normal render
    android:
    -Text with style: absolute, minWidth minHeight, positioned with top negative (it will be half of image down)
    -Image with props tracksViewChanges and style with width and height

@EvanJMarsh
Copy link

Hey @nemanjas1, I came across your fix for this issue and it is the only one I have been having any luck with.

Although my image is still cut off horizontally though the middle. Any idea why?

@tharit-haup
Copy link

@EvanJMarsh, Can you provide some example of tree @nemanjas1 mention.

@arthur322
Copy link

The workaround I've found, but using WebView working on iOs and Android
https://snack.expo.io/@arthur322/google-maps-example

@AnthonyDugarte
Copy link

Answer of another reply to similar issue, if not the same.

If you use Image Component inside Text component image will be rendered.

#431 (comment)

@dkleikesa
Copy link
Contributor

dkleikesa commented Aug 23, 2019

I create a pull request , android google map callout support Image.
#3035

@HoangTrongKhanh
Copy link

I create a pull request , android google map callout support Image.
#3035

It work like a charm. Please merge it!

@amsimoes
Copy link

Guys, what about ImageBackground? Works fine on iOs but on Android it doesn't render...

@karimkhamwani
Copy link

@karimkhamwani
Copy link

for me issue appears only when tracksViewChanges is passed false to resolved try this
<MapView.Marker
coordinate={marker}
key={index}
onLoad={() => this.setState({ initialRender: false })}
tracksViewChanges={this.state.initialRender}
/>

@thanhluantl2304
Copy link

I create a pull request , android google map callout support Image.
#3035

It work like a charm. Please merge it!

@HoangTrongKhanh bạn ơi, phần này họ vẫn chưa merge à bạn?

@adiluzz
Copy link

adiluzz commented Apr 15, 2020

I got it to work with WebView :)
Thank's a lot to you all!!

I added react-native-webview:

yarn add react-native-webview

(For older android versions I had to add react-native-get-random-values):

yarn add react-native-get-random-values

Then, I imported:

import 'react-native-get-random-values'; import { WebView } from 'react-native-webview';

at the top of my map page to work in older Android versions. And, in my render() function I added:

<WebView source={{ uri: myUri }} style={{ height:50, width:50 }} key={index} />

I created a view in my server to serve an html page with only that image, and, got it to work. Please notice that All images are loaded from server When map is populated with markers (one request for each image).

Hope that helps anybody....

@byteab
Copy link

byteab commented May 5, 2020

Ok I solve it by putting width: 0 version of the Image inside the <Text /> Component and a full width version of the same Image outside The <Text />.

    let imageUrl = "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg"
    .
    .
    .
    <Image
      source = {{ uri: imageUrl }}
      style = { { width: 100, height: 100 } }
      resizeMode = "cover"
    />
    <Text style={styles.markerText}>
        { "Company Logo Image" }
        <Image
         source={{ uri: imageUrl }}
         style={{ width: 0, height: 0 }}
         resizeMode="cover" 
        />
    <Text/>

@dominikbrno
Copy link

None of the above workarounds works for me.

@Agent215
Copy link

Agent215 commented Jun 13, 2020

Here is the work around i found that forces a rerender. But now it doesnt work on IOS!


`import React, { useState, useEffect, } from 'react';
import { Image } from 'react-native';


const PinMarker = (props) => {

    const [extraData, setExtraData] = useState(false);
    useEffect(() => { setExtraData(true); }, []);

    let pinIcon;

    if (props.category === "music") {
        pinIcon = require('../assets/Pins/MusicPin.png');
        console.log("Marker Category: " + props.category)
    }
    else if (props.category === "meeting") {
        pinIcon = require('../assets/Pins/MeetingPin.png');
        console.log("Marker Category: " + props.category)
    } else if (props.category === "party") {
        pinIcon = require('../assets/Pins/PartyPin.png');
        console.log("Marker Category: " + props.category)
    }

    return (

        <Image
            style={{ maxHeight: 75, maxWidth: 75 }}
            // check for platform here, this is because with android we need to force a rerender
            key={Platform.OS === 'android' ? `${extraData}` : null}
            source={pinIcon}
        />

    )
}


export default PinMarker;`

@sasan-ebrahimi
Copy link

I tried many things and finally I found a solution. Worked for me on Android. Just putting Image tag inside a Text tag

<Text><Image .../></Text>

@Agent215
Copy link

@sasan-ebrahimi This does not work for me.

@Agent215
Copy link

ok finally i got something working. I used @akshaygpta007 method. using webview, html and css.

I created a function that took the object that had the image path as a prop. then passed that in
to the html.

export const EventImage = (event) => {

    var value = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <style> * { margin: 0px; padding:0; } </style>
   </head>
   <body>
      <div style=' font-size: 70px '>
         <img  style=' margin: 10 ; width: 100% 'src="` + event.image + `" /> 
      </div>
   </body>
</html> `
    return (value)

};

Then i used the above function as demonstrated in the snippet below. You may have to adjust the size and placement of your photo, but it works for me.

export const CustomCallout = (props) => {

   return (
       <Card>
           <View>
               <CardItem>
                   <Left>
                   <WebView style={{ height: 80, width: 80, paddingRight:100 }} source={{
                       html: UserImage(props)
                   }} />
                       <Body>
                           <Text>{props.event.host.name}</Text>
                           <Text note>{props.event.host.email}</Text>
                       </Body>
                   </Left>
               </CardItem>

Then i just place my custom callout out as follows. I do a platform check because normal images display fine on IOS.

  <Marker
            coordinate={{ latitude: event.latitude, longitude: event.longitude }}
            title={event.title}
            pinColor="#341f97"
            icon={FlashOnIcon}
            description={event.description}
            key={event.id}
            onPress={onPinPress.bind(this, event)}
          ><Callout
            style={styles.plainView}
            onPress={onEventCalloutPress}
            tooltip={true}
            key={event.id}
          >
                {Platform.OS === 'ios' ? (<EventCard event={event} />) : (
                  <CustomCallout style ={{height:400}} event={event} />
                )}
            </Callout>
          </Marker>

@mustapha-ghlissi
Copy link

Check this answer 2633

@dorkyboi
Copy link

dorkyboi commented Aug 6, 2021

My issue was that image would not appear if it had any resizeMode, even the default one ('cover'), but only when i had tracksViewChanges={false}. Enabling it fixed the issue but i read somewhere that it is a major performance reducer, so i wanted to keep it disabled.
This comment and @danielgindi's reply helped my a big deal.

Here's an example of my code.

const Marker = (markerInfo) => {
    const [loading, setLoading] = React.useState(true);
    return (
        <MapView.Marker
            tracksViewChanges={loading}
            coordinate={{
                latitude: markerInfo.latitude,
                longitude: markerInfo.longitude,
            }}
        >
            <View style={{width: 40, height: 50, alignItems: 'center', justifyContent: 'center'}}>
                <Image
                    onLoad={() => setLoading(false)}
                    fadeDuration={0}
                    style={{width: 30, height: 40}}
                    resizeMode={'contain'}
                    source={{uri: markerInfo.icon}}
                />
            </View>
        </MapView.Marker>
    );
};

...

<MapView
    data={...}
    initialRegion={...}
    renderMarker={markerInfo => <Marker key={String(markerInfo.id)} {...markerInfo}/>}
/>

@AkibShaih
Copy link

AkibShaih commented Jul 27, 2024

Hopefully, i found a working solution here https://github.com/react-native-maps/react-native-maps/issues/3339#issuecomment-1770843834
Working Code.

const markerRef = useRef<Marker>(null);

const doRedraw = () => {
  markerRef.current?.redraw();
};

<Marker
      ref={markerRef}
      coordinate={coordinate}
      tracksInfoWindowChanges={false}
      tracksViewChanges={false}
      onPress={() => onSelectPartner(partner)}>
      <Image
        source={selected ? icons['pinSelected'] : icons['pin']}
        fadeDuration={0}
        onLoadEnd={doRedraw}
        style={{
          width: 40,
          height: 40,
          resizeMode: 'contain',
        }}
      />
</Marker>

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