MonstroDev/expo-image-picker-multiple

I need to know how I can use this?

shahzadwaris opened this issue ยท 19 comments

Can you please provide a fully working snack as the snack link you mentioned is not working. Thank You

Hi @shahzadwaris
Expect a new version with fixes tomorrow.
Thanks

I'm using this as below:

<ImageBrowser max={6} onChange={(count, prepareCallback) => { console.log(count); prepareCallback(); }} callback={onSubmitCallback} />
const onSubmitCallback = async (assetsInfo) => { await assetsInfo.then((x) => console.log(x)); };
count is always right but I always keep getting older assets, Eg. I select one image, there is no object, I deselect the same image, object is logged. Does this have something to do with this being async?

Seems to have figured it out after reading code in the module. Here is my code that got this working:

// above render
let onSubmit;

// In render()
<ImageBrowser max={6} onChange={(count, callback) => { console.log(count); onSubmit = callback; }} callback={(assetsInfo) => { assetsInfo.then((x) => console.log(x)); }} />

// Finally button to get images from promise and log/use
<Button text={"Import Photos"} style={{ alignSelf: "center" }} onPress={() => { onSubmit(); }} />
An example with a running snack and proper documentation would be highly appreciated. I understand contribution is demanding but this module if improved can be a saver for those looking to pick multiple images as the expo ImagePicker doesn't support this yet.

Maybe we can contribute as this has potential.

Hey @rewaant , this doesn't to work for me. Any idea what i could be doing wrong, please?

The onSubmit variable is undefined outside but it's defined inside the onChange function

@MonstroDev please assist with this issue, thanks

Try storing onSubmit in state, for eg. if you're using hooks and a functional component, try doing this:
let [onSubmit, setOnSubmit] = useState(null);
Inside onChange function, do:
setOnSubmit(() => callback);
If it's a class component, you can adjust this with setting the state.

Hey @rewaant, you're a life saver!!!

Thank you very much!

I'm using this as below:

<ImageBrowser max={6} onChange={(count, prepareCallback) => { console.log(count); prepareCallback(); }} callback={onSubmitCallback} />
const onSubmitCallback = async (assetsInfo) => { await assetsInfo.then((x) => console.log(x)); };
count is always right but I always keep getting older assets, Eg. I select one image, there is no object, I deselect the same image, object is logged. Does this have something to do with this being async?

I also have the same problem @MonstroDev

@m0w4hhed use it the way @rewaant suggested. It worked for me

not working with me, i use redux instead of navigation prop for passing selected photos,

by changing the code: src/ImageBrowser.js in line:66
this.props.onChange(newSelected.length, () => this.prepareCallback());

with:
this.props.onChange(newSelected.length, () => { const { photos } = this.state; console.log('prepare: ', newSelected); const selectedPhotos = newSelected.map(i => photos[i]); const assetsInfo = Promise.all(selectedPhotos.map(i => MediaLibrary.getAssetInfoAsync(i))); this.props.callback(assetsInfo); });

and its working for me, i dont know why @MonstroDev @rewaant

Guys this would be super a useful feature because Expo doesn't provide multiple images selection and I really do no know why. But I can't get back a list of selected images with your solutions which makes it unusable. I have tried the suggested solutions but nothing works for me.

What I need is a list of all selected images that's it. Here you can see my code. Thanks in advance :)

const TestScreen = (props) => {
    const [selectedImages, setSelectedImage] = useState([])
    let [onSubmit, setOnSubmit] = useState(null);

    const emptyStayComponent = <Text style={styles.emptyStay}>Empty =(</Text>;
    const noCameraPermissionComponent = <Text style={styles.emptyStay}>No access to camera</Text>;

    var imagesCallback = (callback) => {
        callback.then((photos) => {
            console.log(photos);
        }).catch((e) => console.log(e))
    };

    var renderSelectedComponent = (number) => (
        <View style={styles.countBadge}>
            <Text style={styles.countBadgeText}>{number}</Text>
        </View>
    );

    var updateHandler = (count, onSubmit) => {
        // this.props.navigation.setParams({
        //   headerTitle: "{{count}} selected",
        //   headerRight: onSubmit,
        // });
    };

    return (
        <SafeAreaView style={styles.container}>
            <View style={{ height: (StatusBar.currentHeight || 0), backgroundColor: "#FFFFFF" }}></View>
            <View style={{ height: 60, flexDirection: 'row', backgroundColor: "#FFFFFF" }}>
                <View style={{ flex: 1, alignItems: "flex-start", justifyContent: "center" }}>
                    <CloseButton onPress={() => props.navigation.pop()} style={{ paddingLeft: 10 }}></CloseButton>
                </View>
            </View>
            <View style={{ height: 150 }}></View>
            <ImageBrowser
                max={4}
                emptyStayComponent={emptyStayComponent}
                noCameraPermissionComponent={noCameraPermissionComponent}
                renderSelectedComponent={renderSelectedComponent}
                callback={(assetsInfo) => { assetsInfo.then((x) => console.log(x)); }}
                onChange={(count, callback) => { console.log(count); setOnSubmit(() => callback); }}
            />
            <Button title="Import Photos" style={{ alignSelf: "center" }} onPress={() => { onSubmit() }} />
        </SafeAreaView>
    );
};

export default TestScreen;

Your updateHandler function should be something like this

const updateHandler = (count: number, onSubmitCb: any) => { //update selected images count here setOnSubmit(() => onSubmitCb); //set the function };

In your imagesCallback function, you need loop through the photos array to get the uri, name and type. Something like this

        callback
            .then(async (photos) => {
                const cPhotos: { uri: string; name: any; type: string }[] = [];
                for (let photo of photos) {
                    const pPhoto = await resizeImage(photo.uri);
                    cPhotos.push({
                        uri: pPhoto.uri,
                        name: photo.filename,
                        type: "image/png",
                    });
                }
                setImageArray((imageArray) => [...imageArray, ...cPhotos]);
            })
            .catch((e) => console.log(e))
            .finally(() => setLoading(false));
    }

So when they click on Done (or any button to indicate that they've selected all the images that they want), you'd call onSubmit()

Thanks a lot! I implemented your ideas into my structure and now it works! Perfect! Thank you very much! Here is the fully working code:

import React, { useState } from "react";
import { Button, SafeAreaView, StatusBar, StyleSheet, Text, View } from "react-native";
import { ImageBrowser } from 'expo-image-picker-multiple';

import CloseButton from './Components/closeButton'

const TestScreen = (props) => {
    let [onSubmit, setOnSubmit] = useState(null);
    var [imageArray, setImageArray] = useState([])

    const emptyStayComponent = <Text style={styles.emptyStay}>Empty =(</Text>;
    const noCameraPermissionComponent = <Text style={styles.emptyStay}>No access to camera</Text>;

    var imagesCallback = (callback) => {
        callback
            .then(async (photos) => {
                const cPhotos = [];
                for (let photo of photos) {
                    console.log(photo)
                    cPhotos.push({
                        uri: photo.uri,
                        name: photo.filename,
                        type: "image/png",
                    });
                }
                setImageArray([...imageArray, ...cPhotos]);
            })
            .catch((e) => console.log(e))
    };

    var renderSelectedComponent = (number) => (
        <View style={styles.countBadge}>
            <Text style={styles.countBadgeText}>{number}</Text>
        </View>
    );

    var updateHandler = (count, onSubmitCb) => {
        setOnSubmit(() => onSubmitCb)
    };

    return (
        <SafeAreaView style={styles.container}>
            <View style={{ height: (StatusBar.currentHeight || 0), backgroundColor: "#FFFFFF" }}></View>
            <View style={{ height: 60, flexDirection: 'row', backgroundColor: "#FFFFFF" }}>
                <View style={{ flex: 1, alignItems: "flex-start", justifyContent: "center" }}>
                    <CloseButton onPress={() => props.navigation.pop()} style={{ paddingLeft: 10 }}></CloseButton>
                </View>
            </View>
            <View style={{ height: 150 }}></View>
            <ImageBrowser
                max={4}
                emptyStayComponent={emptyStayComponent}
                noCameraPermissionComponent={noCameraPermissionComponent}
                renderSelectedComponent={renderSelectedComponent}
                onChange={(count, callback) => { updateHandler(count, callback) }}
                callback={(imageCallback) => { imagesCallback(imageCallback) }}
            />
            <Button title="Import Photos" style={{ alignSelf: "center" }} onPress={() => { onSubmit() }} />
        </SafeAreaView>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        position: 'relative'
    },
    countBadgeText: {
        fontWeight: 'bold',
        alignSelf: 'center',
        padding: 'auto',
        color: '#ffffff'
    },
    countBadge: {
        paddingHorizontal: 8.6,
        paddingVertical: 5,
        borderRadius: 50,
        position: 'absolute',
        right: 3,
        bottom: 3,
        justifyContent: 'center',
        backgroundColor: '#0580FF'
    },
    emptyStay: {
        textAlign: 'center',
    },
});

export default TestScreen;

Nice, you're welcome!

The way it's setup is a bit confusing and the lack of proper documentation makes it even harder. I could only figure it out when I dug into the source code and it all started making sense. This definitely has potential as, annoyingly enough, the official support for multiple images isn't there and we can also play with the layout, even if the library is a wrapper.

One more thing that I noticed is that the Image picker for the iOS camera roll (apple's native/official) has a feature that displays the image in actual dimensions in a Modalized way (not sure what word to use) for a second or so when selecting the image. Adding this feature to this library will be a nice addition for many. I'm looking into options for this right now and will share here if we can integrate this.

I had a similar issue and it was basically the setState being asynch in ImaegeBrowser.js code. I have put a fix by calling the prepareCallback inside setState callback. i.e.

this.setState({selected: newSelected}, () =>{
      this.props.onChange(newSelected.length, () => this.prepareCallback());
});

full code.

selectImage = (index) => {
    let newSelected = Array.from(this.state.selected);
    if (newSelected.indexOf(index) === -1) {
      newSelected.push(index);
    } else {
      const deleteIndex = newSelected.indexOf(index);
      newSelected.splice(deleteIndex, 1);
    }
    if (newSelected.length > this.props.max) return;
    if (!newSelected) newSelected = [];
      this.setState({selected: newSelected}, () =>{
      this.props.onChange(newSelected.length, () => this.prepareCallback());
    });
  }

Please @rewaant, @Man7hano or any other person, can you give an example where the user clicks on a button to open the image picker; and when user clicks another button, the image picker closes and logs result? If I can get how to call the image picker when button clicked, I think I can continue from there. I'm still new to React Native. So, finding a bit confusing to adapt it too my needs. Thanks

Thank you, everyone.