Create custom multiple image picker with Compress image feature in React native (Expo)

March 24, 2022

Create custom multiple image picker with Compress image feature in React native (Expo)

Create custom multiple image picker with Compress image feature in React native (Expo)

App.js

import React from 'react'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import PhotoBrowser from './PhotoBrowser'
import LandingPage from './LandingPage'
const Stack = createNativeStackNavigator();
const App = () => {
  return ( 
    <NavigationContainer>
    <Stack.Navigator>
      <Stack.Screen name="LandingPage" component={LandingPage} />
      <Stack.Screen name="PhotoBrowser" component={PhotoBrowser} />
    </Stack.Navigator>
   </NavigationContainer> 
  )
}

export default App;

PhotoBrowser.js

import React,{useEffect,useState} from 'react'
import { View,ActivityIndicator, Text,SafeAreaView,StyleSheet,
  Linking,AppState,FlatList,Dimensions,ImageBackground,TouchableHighlight,TouchableOpacity} from 'react-native';
import * as MediaLibrary from 'expo-media-library';
import * as ImageManipulator from 'expo-image-manipulator';
import Icon from '@expo/vector-icons/Ionicons';
const { width } = Dimensions.get('window');

const PhotoBrowser = ({navigation}) => {
  const [Permission, SetPermission] = useState(null);
  const [ChoosePhoto, SetChoosePhoto] = useState([]); 
  const [HasNext, setHasNext] = useState(true); 
  const [Photos, SetPhotos] = useState([]); 
  const [isEmpty, SetEmpty] = useState(false); 
  const [After, SetAfter] = useState(null); 
  
   const CallLoadFunction=()=>{
        console.info('focus')
        CheckPermissionAndSetImageData();
   }

   useEffect(() => {
    const subscription = AppState.addEventListener('change', handleChange);  
    CallLoadFunction();
    return () => {
      if(subscription){
        subscription.remove();
      }
    } 
  }, []);

  const handleChange = (newState) => {
    if (newState === "active") {
      if(Permission?.canAskAgain==false){
        CallLoadFunction();
      }
     }
    }
    
   const CheckPermissionAndSetImageData=async()=> {
     if(HasNext==false){
       return false
     }
     let status= await MediaLibrary.requestPermissionsAsync();
     console.info(status)
     if(status.status==='denied'){
         SetPermission(status)
         return false;
     }
     let options = {after:After,first:100,sortBy: [MediaLibrary.SortBy.creationTime], mediaType: MediaLibrary.MediaType.photo }
     let {assets,hasNextPage,endCursor} = await MediaLibrary.getAssetsAsync(options)
     setHasNext(hasNextPage)
     SetPhotos(Photos => [...Photos, ...assets])
     SetAfter(endCursor)
     if(assets?.length==0){
      SetEmpty(true)
     }
  }
  const ChoosePhotos=async()=>{
    const selectedPhotos = ChoosePhoto.map(i => Photos[i])
      const cPhotos = [];
      for(let photo of selectedPhotos) {
        const pPhoto = await CompressImage(photo.uri);
        cPhotos.push({
          uri: pPhoto.uri,
          name: photo.filename,
          type: 'image/jpg'
        })
      }
       navigation.navigate('LandingPage',cPhotos)
   }


  const  CompressImage=async(uri)=> {
    const file = await ImageManipulator.manipulateAsync(
      uri,
      [{resize: { width: 800 }}],
      { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG }
    );
    return file;
  };

  const selectImage = (index) => {
    console.info(index)
    let newSelected = Array.from(ChoosePhoto)
    if (newSelected.indexOf(index) === -1) {
      newSelected.push(index)
    } else {
      const deleteIndex = newSelected.indexOf(index)
      newSelected.splice(deleteIndex, 1)
    }
    if (newSelected.length > 5){
      alert('You can selected only 5 photos')
      return false;
    }
    if (newSelected.length === 0) newSelected = []
    SetChoosePhoto(newSelected)
  }

   const ImageGrid=({item,index})=>{
      if (!item) return null
      const selected = ChoosePhoto.indexOf(index) !== -1
      const selectedItemCount = ChoosePhoto.indexOf(index) + 1
      return   <TouchableHighlight
      style={{ opacity: item.selected ? 0.8 : 1 ,padding: .5,backgroundColor:'white'}}
      underlayColor='transparent'
      onPress={() => selectImage(index)} >
        <View style={{ position: 'relative' }}>
          <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <ImageBackground
              style={{ width: width / 4, height: width / 4 }}
              source={{ uri: item.uri }} >
              {selected &&
              <View style={{ ...styles.countBadge, backgroundColor: 'green' }}>
                <Text style={styles.countBadgeText}>{selectedItemCount}</Text>
              </View>
              }
            </ImageBackground>
          </View>
        </View>
        </TouchableHighlight>
   }

  const getItemLayout = (data, index) => {
    let length = width / 4
    return { length, offset: length * index, index }
  }

  const renderEmpty = ({emptyText}) => {
    return (
      <View style={styles.emptyContent}>
        <Text style={styles.emptyText}>{emptyText ?emptyText : 'No image'}</Text>
      </View>
    )
  }
  const RenderLoading = () => {
    return (
      <View style={styles.emptyContent}>
        <ActivityIndicator size='large' color={'#bbb'} />
      </View>
    )
  }

  const DoneButton=()=>{
    return <TouchableOpacity
    onPress={()=>ChoosePhotos()}
    style={{
      borderWidth: 1,
      alignItems: 'center',
      justifyContent: 'center',
      width: 50,
      position: 'absolute',
      bottom: 50,
      right: 10,
      height: 50,
      backgroundColor: 'white',
      borderRadius: 10,
    }}
  >
    <Icon name="md-checkmark-circle" size={40} color="green" />
  </TouchableOpacity>
  }


  return (
    <>
        <SafeAreaView style={[styles.container,{backgroundColor:"#000"}]}>
             <View style={styles.ImageBrowserContainer}>
                {Permission?.status==='denied'?
                  <View>
                      <>
                       {Permission?.canAskAgain==true?
                       <>
                        <Text style={{padding:5,margin: 10}}>Your Photo Library Permission is deny</Text>
                        <Text onPress={()=>CheckPermissionAndSetImageData()} style={styles.GoSetting}>Allow Permission</Text>
                        </>
                        :
                        <>
                        <Text style={{padding:5,margin: 10}}>Your Photo Library Permission is disable you can enable it from app setting</Text>
                        <Text onPress={()=>Linking.openSettings()} style={styles.GoSetting}>Go to app setting</Text>
                        </>
                       }
                       </>
                  </View>:
                   <>
                        <FlatList
                            contentContainerStyle={{ flexGrow: 1 }}
                            data={Photos}
                            numColumns={4}
                            renderItem={ImageGrid}
                            keyExtractor={(_, index) => index}
                            onEndReached={() => CheckPermissionAndSetImageData()}
                            onEndReachedThreshold={0.5}
                            ListEmptyComponent={isEmpty ? renderEmpty() : RenderLoading()}
                            initialNumToRender={24}
                            getItemLayout={getItemLayout}
                          />
                        {ChoosePhoto.length!==0&&DoneButton()}
                   </>
                }
             </View>
        </SafeAreaView>
    </>
  )
}

export default PhotoBrowser;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  Header:{
    flexDirection: 'row',
    padding:10,
    alignItems:'center',
    justifyContent:'space-between'
  },
  ImageBrowserContainer:{
    flex:1,
    backgroundColor:'white'
  },
  HeadingTitle:{
    color:'white',
    fontWeight:'bold'
  },
  GoSetting:{
    color:'blue',
    textAlign:'center'
  },
  emptyContent: {
    flexGrow: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  emptyText: {
    color: '#bbb',
    fontSize: 20,
    textAlign:'center'
  },
  countBadge: {
    paddingHorizontal: 8.6,
    paddingVertical: 5,
    borderRadius: 60,
    position: 'absolute',
    right: 3,
    bottom: 3,
    justifyContent: 'center'
  },
  countBadgeText: {
    color: '#fff',
    fontWeight: 'bold',
    alignSelf: 'center',
    padding: 'auto'
  }
});

LandingPage.js

import React from 'react'
import { SafeAreaView, Text ,Image} from 'react-native'

const LandingPage = ({navigation,route}) => {
  const  Photos  = route?.params;
  return (
    <SafeAreaView>
       <Text onPress={()=>navigation.navigate('PhotoBrowser')}>Open PhotoBrowser</Text>
       {Photos?.map((data,index)=>{
         return <Image key={index} 
                       source={{uri: data.uri}}
                      style={{width: 250, height: 150,margin: 10}} />
       })
       }
    </SafeAreaView>
  )
}

export default LandingPage;

Written by Manoj Bhardwaj who lives and works in Dharamshala Himachal Pradesh (India). My stackoverflow