Lesson 09-React Native Media and File Handling

Media and File Handling

Image Processing and Display

Basic Image Component

import React from 'react';
import { View, Image, StyleSheet } from 'react-native';

const ImageExample = () => {
  return (
    <View style={styles.container}>
      {/* Network image */}
      <Image
        source={{ uri: 'https://example.com/image.jpg' }}
        style={styles.image}
        resizeMode="cover" // Image scaling mode: cover, contain, stretch, repeat, center
      />

      {/* Local static image (requires proper configuration in Xcode/Android Studio) */}
      <Image
        source={require('./assets/local-image.png')}
        style={styles.image}
      />

      {/* Dynamic local image reference (using a workaround for require) */}
      <Image
        source={getImageSource('dynamic-image')} // Custom function below
        style={styles.image}
      />
    </View>
  );
};

// Workaround for dynamically referencing local images (since require doesn't support variable paths)
const imageMap = {
  'dynamic-image': require('./assets/dynamic-image.png'),
  'another-image': require('./assets/another-image.png'),
};

const getImageSource = (imageName) => imageMap[imageName] || require('./assets/fallback-image.png');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    flexWrap: 'wrap',
    padding: 10,
  },
  image: {
    width: 150,
    height: 150,
    margin: 5,
  },
});

export default ImageExample;

Image Caching and Performance Optimization

import React, { useState } from 'react';
import { View, Image, ActivityIndicator, StyleSheet } from 'react-native';
import FastImage from 'react-native-fast-image'; // Third-party high-performance image library

const CachedImageExample = () => {
  const [loading, setLoading] = useState(true);

  return (
    <View style={styles.container}>
      {/* Using FastImage for image caching */}
      <FastImage
        style={styles.image}
        source={{
          uri: 'https://example.com/large-image.jpg',
          priority: FastImage.priority.high, // Loading priority
          cache: FastImage.cacheControl.immutable, // Cache strategy
        }}
        resizeMode={FastImage.resizeMode.cover}
        onLoadStart={() => setLoading(true)}
        onLoadEnd={() => setLoading(false)}
      />
      
      {/* Loading indicator */}
      {loading && (
        <ActivityIndicator
          style={styles.loader}
          size="large"
          color="#0000ff"
        />
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  image: {
    width: 300,
    height: 200,
  },
  loader: {
    position: 'absolute',
  },
});

export default CachedImageExample;

Audio and Video Processing

Audio Playback Implementation

import React, { useState, useEffect, useRef } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import Sound from 'react-native-sound'; // Third-party audio library

const AudioPlayerExample = () => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [duration, setDuration] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);
  const soundRef = useRef(null);

  useEffect(() => {
    // Initialize audio
    soundRef.current = new Sound('https://example.com/audio.mp3', '', (error) => {
      if (error) {
        console.log('Failed to load the sound', error);
        return;
      }
      
      // Get audio duration
      setDuration(soundRef.current.getDuration());
    });

    // Clean up resources on component unmount
    return () => {
      if (soundRef.current) {
        soundRef.current.release();
      }
    };
  }, []);

  const togglePlayPause = () => {
    if (isPlaying) {
      soundRef.current.pause();
    } else {
      soundRef.current.play((success) => {
        if (success) {
          console.log('Audio played successfully');
        } else {
          console.log('Audio playback failed');
        }
      });
    }
    setIsPlaying(!isPlaying);
  };

  const formatTime = (seconds) => {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
  };

  return (
    <View style={styles.container}>
      <Button
        title={isPlaying ? 'Pause' : 'Play'}
        onPress={togglePlayPause}
      />
      <Text>Duration: {formatTime(duration)}</Text>
      <Text>Current Time: {formatTime(currentTime)}</Text>
      
      {/* Progress bar (simplified) */}
      <View style={styles.progressBar}>
        <View
          style={[
            styles.progressFill,
            { width: `${(currentTime / duration) * 100}%` },
          ]}
        />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  progressBar: {
    height: 10,
    width: '100%',
    backgroundColor: '#e0e0e0',
    borderRadius: 5,
    marginTop: 20,
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#2196f3',
    borderRadius: 5,
  },
});

export default AudioPlayerExample;

Video Playback Implementation

import React, { useState, useRef } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import Video from 'react-native-video'; // Third-party video library

const VideoPlayerExample = () => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const videoRef = useRef(null);

  const togglePlayPause = () => {
    setIsPlaying(!isPlaying);
  };

  const handleProgress = (data) => {
    setCurrentTime(data.currentTime);
  };

  const handleLoad = (data) => {
    setDuration(data.duration);
  };

  const formatTime = (seconds) => {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
  };

  return (
    <View style={styles.container}>
      <Video
        ref={videoRef}
        source={{ uri: 'https://example.com/video.mp4' }}
        style={styles.video}
        paused={!isPlaying}
        onProgress={handleProgress}
        onLoad={handleLoad}
        resizeMode="contain"
        controls={false} // Hide default controls, use custom controls
      />
      
      <View style={styles.controls}>
        <Button
          title={isPlaying ? 'Pause' : 'Play'}
          onPress={togglePlayPause}
        />
        <Text style={styles.timeText}>
          {formatTime(currentTime)} / {formatTime(duration)}
        </Text>
        
        {/* Simplified progress bar */}
        <View style={styles.progressBar}>
          <View
            style={[
              styles.progressFill,
              { width: `${(currentTime / duration) * 100}%` },
            ]}
          />
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#000',
  },
  video: {
    width: '100%',
    height: 300,
  },
  controls: {
    padding: 10,
    alignItems: 'center',
  },
  timeText: {
    color: '#fff',
    marginVertical: 5,
  },
  progressBar: {
    height: 10,
    width: '100%',
    backgroundColor: '#444',
    borderRadius: 5,
    marginTop: 5,
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#2196f3',
    borderRadius: 5,
  },
});

export default VideoPlayerExample;

File System Operations

File Reading and Writing Implementation

import React, { useState } from 'react';
import { View, Button, TextInput, Text, StyleSheet, Alert } from 'react-native';
import RNFS from 'react-native-fs'; // Third-party file system library

const FileSystemExample = () => {
  const [text, setText] = useState('');
  const [fileContent, setFileContent] = useState('');

  // Document directory path
  const documentDir = RNFS.DocumentDirectoryPath;
  // Create file path
  const filePath = `${documentDir}/example.txt`;

  // Write file
  const writeFile = async () => {
    try {
      // Write text to file
      await RNFS.writeFile(filePath, text, 'utf8');
      Alert.alert('Success', 'File written successfully');
    } catch (error) {
      console.error('Error writing file', error);
      Alert.alert('Error', 'Failed to write file');
    }
  };

  // Read file
  const readFile = async () => {
    try {
      // Check if file exists
      const exists = await RNFS.exists(filePath);
      if (!exists) {
        Alert.alert('Error', 'File does not exist');
        return;
      }

      // Read file content
      const content = await RNFS.readFile(filePath, 'utf8');
      setFileContent(content);
    } catch (error) {
      console.error('Error reading file', error);
      Alert.alert('Error', 'Failed to read file');
    }
  };

  // Delete file
  const deleteFile = async () => {
    try {
      // Check if file exists
      const exists = await RNFS.exists(filePath);
      if (!exists) {
        Alert.alert('Error', 'File does not exist');
        return;
      }

      // Delete file
      await RNFS.unlink(filePath);
      setFileContent('');
      Alert.alert('Success', 'File deleted successfully');
    } catch (error) {
      console.error('Error deleting file', error);
      Alert.alert('Error', 'Failed to delete file');
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.textInput}
        multiline
        numberOfLines={4}
        value={text}
        onChangeText={setText}
        placeholder="Enter text to save"
      />
      
      <View style={styles.buttonContainer}>
        <Button title="Write File" onPress={writeFile} />
        <Button title="Read File" onPress={readFile} />
        <Button title="Delete File" onPress={deleteFile} />
      </View>
      
      <Text style={styles.contentTitle}>File Content:</Text>
      <Text style={styles.contentText}>{fileContent}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  textInput: {
    height: 100,
    borderColor: '#ccc',
    borderWidth: 1,
    padding: 10,
    marginBottom: 20,
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 20,
  },
  contentTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  contentText: {
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    height: 100,
  },
});

export default FileSystemExample;

File Download and Management

import React, { useState, useRef } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import RNFS from 'react-native-fs';
import { Alert } from 'react-native';

const FileDownloadExample = () => {
  const [downloadProgress, setDownloadProgress] = useState(0);
  const [isDownloading, setIsDownloading] = useState(false);
  const downloadTaskRef = useRef(null);

  // Download file
  const startDownload = async () => {
    if (isDownloading) return;
    
    const downloadDest = `${RNFS.DocumentDirectoryPath}/downloaded_file.pdf`;
    const options = {
      fromUrl: 'https://example.com/sample.pdf',
      toFile: downloadDest,
      background: true,
      begin: (res) => {
        console.log('Download started', res);
      },
      progress: (res) => {
        const progress = (res.bytesWritten / res.contentLength) * 100;
        setDownloadProgress(progress);
        console.log('Download progress:', progress);
      },
    };

    try {
      setIsDownloading(true);
      downloadTaskRef.current = RNFS.downloadFile(options);
      await downloadTaskRef.current.promise;
      Alert.alert('Success', 'File downloaded successfully');
    } catch (error) {
      console.error('Error downloading file', error);
      Alert.alert('Error', 'Failed to download file');
    } finally {
      setIsDownloading(false);
      setDownloadProgress(0);
    }
  };

  // Cancel download
  const cancelDownload = () => {
    if (downloadTaskRef.current) {
      downloadTaskRef.current.cancel();
      setIsDownloading(false);
      setDownloadProgress(0);
    }
  };

  return (
    <View style={styles.container}>
      <Button
        title={isDownloading ? 'Cancel Download' : 'Download File'}
        onPress={isDownloading ? cancelDownload : startDownload}
      />
      
      {isDownloading && (
        <View style={styles.progressContainer}>
          <Text>Download Progress: {downloadProgress.toFixed(2)}%</Text>
          {/* Progress bar (simplified) */}
          <View style={styles.progressBar}>
            <View
              style={[
                styles.progressFill,
                { width: `${downloadProgress}%` },
              ]}
            />
          </View>
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 20,
  },
  progressContainer: {
    marginTop: 20,
  },
  progressBar: {
    height: 10,
    width: '100%',
    backgroundColor: '#e0e0e0',
    borderRadius: 5,
    marginTop: 5,
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#2196f3',
    borderRadius: 5,
  },
});

export default FileDownloadExample;

Camera Functionality Implementation

import React, { useState, useEffect } from 'react';
import { View, Button, Image, StyleSheet, Alert } from 'react-native';
import { Camera } from 'react-native-camera'; // Third-party camera library

const CameraExample = () => {
  const [cameraRef, setCameraRef] = useState(null);
  const [photo, setPhoto] = useState(null);
  const [isCameraReady, setIsCameraReady] = useState(false);

  // Request camera permission
  useEffect(() => {
    (async () => {
      const granted = await Camera.requestCameraPermission();
      if (!granted) {
        Alert.alert('Permission required', 'Camera permission is required to use this feature');
      }
    })();
  }, []);

  // Take picture
  const takePicture = async () => {
    if (cameraRef && isCameraReady) {
      try {
        const options = { quality: 0.5, base64: true };
        const data = await cameraRef.takePictureAsync(options);
        setPhoto(data.uri);
      } catch (error) {
        console.error('Error taking picture', error);
        Alert.alert('Error', 'Failed to take picture');
      }
    }
  };

  return (
    <View style={styles.container}>
      {!photo ? (
        <Camera
          ref={ref => setCameraRef(ref)}
          style={styles.camera}
          type={Camera.Constants.Type.back}
          onCameraReady={() => setIsCameraReady(true)}
        >
          <View style={styles.buttonContainer}>
            <Button title="Take Picture" onPress={takePicture} />
          </View>
        </Camera>
      ) : (
        <View style={styles.previewContainer}>
          <Image source={{ uri: photo }} style={styles.previewImage} />
          <Button title="Retake" onPress={() => setPhoto(null)} />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  camera: {
    flex: 1,
  },
  buttonContainer: {
    flex: 1,
    backgroundColor: 'transparent',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'flex-end',
    marginBottom: 20,
  },
  previewContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  previewImage: {
    width: '100%',
    height: '100%',
    resizeMode: 'contain',
  },
});

export default CameraExample;
import React, { useState } from 'react';
import { View, Button, Image, StyleSheet, Alert } from 'react-native';
import * as ImagePicker from 'react-native-image-picker'; // Third-party gallery library

const ImagePickerExample = () => {
  const [image, setImage] = useState(null);

  const pickImage = () => {
    const options = {
      mediaType: 'photo',
      includeBase64: false,
      maxHeight: 2000,
      maxWidth: 2000,
    };

    ImagePicker.launchImageLibrary(options, (response) => {
      if (response.didCancel) {
        console.log('User cancelled image picker');
      } else if (response.error) {
        console.log('ImagePicker Error: ', response.error);
        Alert.alert('Error', 'Failed to pick image');
      } else {
        setImage(response.uri);
      }
    });
  };

  return (
    <View style={styles.container}>
      {!image ? (
        <Button title="Pick an image from gallery" onPress={pickImage} />
      ) : (
        <View style={styles.imageContainer}>
          <Image source={{ uri: image }} style={styles.image} />
          <Button title="Pick another image" onPress={() => setImage(null)} />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 20,
  },
  imageContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  image: {
    width: '100%',
    height: 300,
    resizeMode: 'contain',
  },
});

export default ImagePickerExample;

File Sharing and Exporting

File Sharing Implementation

import React, { useState } from 'react';
import { View, Button, Text, StyleSheet, Alert } from 'react-native';
import RNFS from 'react-native-fs';
import Share from 'react-native-share'; // Third-party sharing library

const FileShareExample = () => {
  const [shared, setShared] = useState(false);
  const documentDir = RNFS.DocumentDirectoryPath;
  const filePath = `${documentDir}/example.txt`;

  // Create example file
  const createExampleFile = async () => {
    try {
      await RNFS.writeFile(filePath, 'This is an example file content', 'utf8');
      Alert.alert('Success', 'Example file created');
    } catch (error) {
      console.error('Error creating file', error);
      Alert.alert('Error', 'Failed to create file');
    }
  };

  // Share file
  const shareFile = async () => {
    try {
      // Check if file exists
      const exists = await RNFS.exists(filePath);
      if (!exists) {
        await createExampleFile();
      }

      // Configure sharing options
      const options = {
        url: `file://${filePath}`,
        message: 'Check out this file!',
        type: 'application/octet-stream', // Generic file type
      };

      // Perform sharing
      await Share.open(options);
      setShared(true);
    } catch (error) {
      console.error('Error sharing file', error);
      Alert.alert('Error', 'Failed to share file');
    }
  };

  return (
    <View style={styles.container}>
      <Button title="Create Example File" onPress={createExampleFile} />
      <Button title="Share File" onPress={shareFile} />
      {shared && <Text style={styles.successText}>File shared successfully!</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 20,
  },
  successText: {
    marginTop: 20,
    color: 'green',
    fontWeight: 'bold',
  },
});

export default FileShareExample;

File Export to Other Applications

import React, { useState } from 'react';
import { View, Button, Text, StyleSheet, Alert } from 'react-native';
import RNFS from 'react-native-fs';
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'; // Third-party document picker library

const FileExportExample = () => {
  const [exported, setExported] = useState(false);
  const documentDir = RNFS.DocumentDirectoryPath;
  const filePath = `${documentDir}/exported_file.txt`;

  // Create file to export
  const createExportFile = async () => {
    try {
      await RNFS.writeFile(filePath, 'This is the content to export', 'utf8');
      Alert.alert('Success', 'File created for export');
    } catch (error) {
      console.error('Error creating export file', error);
      Alert.alert('Error', 'Failed to create export file');
    }
  };

  // Export file to other applications
  const exportFile = async () => {
    try {
      // Check if file exists
      const exists = await RNFS.exists(filePath);
      if (!exists) {
        await createExportFile();
      }

      // Configure document picker options
      const options = {
        type: [DocumentPickerUtil.allFiles()], // All file types
      };

      // Open document picker for export
      const result = await DocumentPicker.show(options);
      if (result && result[0]) {
        // Implementation for copying file to selected location would be more complex
        // Requires native modules or platform-specific APIs
        console.log('File export path:', result[0].uri);
        
        // Simplified example: Show success message
        setExported(true);
        Alert.alert('Success', 'File export initiated');
      }
    } catch (error) {
      console.error('Error exporting file', error);
      Alert.alert('Error', 'Failed to export file');
    }
  };

  return (
    <View style={styles.container}>
      <Button title="Create Export File" onPress={createExportFile} />
      <Button title="Export File" onPress={exportFile} />
      {exported && <Text style={styles.successText}>File export initiated!</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    padding: 20,
  },
  successText: {
    marginTop: 20,
    color: 'green',
    fontWeight: 'bold',
  },
});

export default FileExportExample;

Summary of Media and File Handling

React Native provides various methods for handling media and files, from simple image display to complex file system operations. Developers can choose the appropriate solution based on their needs:

  1. Image Processing:
    • Use the Image component to display network and local images.
    • Use react-native-fast-image for high-performance image caching.
    • Handle limitations and workarounds for dynamically loading local images.
  2. Audio and Video:
    • Use react-native-sound for audio playback.
    • Use react-native-video for video playback functionality.
    • Consider custom controls for a better user experience.
  3. File System:
    • Use react-native-fs for file reading, writing, downloading, and management.
    • Pay attention to file permissions and platform differences.
    • Implement progress feedback to enhance user experience.
  4. Camera and Gallery:
    • Use react-native-camera to access the device camera.
    • Use react-native-image-picker to select images from the gallery.
    • Handle permission requests and error cases.
  5. File Sharing and Exporting:
    • Use react-native-share for file sharing.
    • Consider platform-specific export methods.
    • Provide clear user feedback.

Best practices include:

  • Always handle permission requests and error cases.
  • Provide operation feedback (e.g., progress bars, loading indicators).
  • Manage memory for large file operations.
  • Follow platform-specific design guidelines.
  • Test on different devices and operating system versions.

By effectively combining these techniques, developers can build feature-rich, user-friendly media and file handling functionalities.

Share your love