Crossplay Photo Share: Seamless Photo Sharing Across iOS and Android

As a developer who's experienced the frustration of sharing high-quality photos between iOS and Android devices, I set out to solve this common problem. The result is Crossplay Photo Share, a cross-platform mobile application that makes sharing full-resolution images between different mobile operating systems effortless and efficient.

The Tech Stack: Powering Seamless Cross-Platform Functionality

Crossplay Photo Share is built on a robust, modern tech stack:

  • Expo & React Native: Enabling rapid development and true cross-platform compatibility
  • Supabase: Providing robust authentication and PostgreSQL database management
  • Expo FileSystem & MediaLibrary: Handling local file operations and media library interactions
  • Expo Router: Offering web-like navigation and deep linking capabilities

Key Features and Technical Insights

1. Effortless Authentication with Supabase

const onSignInPress = async () => {
  setLoading(true);
  const { error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });
  if (error) Alert.alert(error.message);
  setLoading(false);
};

const onSignUpPress = async () => {
  setLoading(true);
  const { error } = await supabase.auth.signUp({
    email: email,
    password: password,
  });
  if (error) Alert.alert(error.message);
  setLoading(false);
};

This code showcases:

  • Seamless integration with Supabase for authentication
  • Error handling with user-friendly alerts
  • Loading state management for improved UX

2. Efficient Batch File Operations

One of the standout features is the ability to download all user files in one go:

const downloadAll = async (user: any) => {
  try {
    const mediaPermission = await MediaLibrary.requestPermissionsAsync();
    if (mediaPermission.status !== 'granted') {
      alert('We need permission to access your media library to download files.');
      return;
    }

    const { data } = await supabase.storage.from('files').list(user.id);
    if (data && data.length > 0) {
      for (const file of data) {
        const response = await supabase.storage.from('files').download(`${user.id}/${file.name}`);
        if (response.data) {
          const fileLocalUri = FileSystem.documentDirectory + encodeURIComponent(file.name);
          const reader = new FileReader();
          reader.onload = async () => {
            if (reader.result && typeof reader.result === 'string') {
              const base64data = reader.result.split(',')[1];
              await FileSystem.writeAsStringAsync(fileLocalUri, base64data, { encoding: FileSystem.EncodingType.Base64 });
              await MediaLibrary.saveToLibraryAsync(fileLocalUri);
            }
          };
          reader.readAsDataURL(response.data);
        }
      }
    }
  } catch (error) {
    console.error('Error:', error);
  }
};

This function demonstrates:

  • Efficient handling of permissions across platforms
  • Batch downloading and saving of files
  • Use of FileSystem and MediaLibrary for cross-platform compatibility
  • Robust error handling

3. Sleek UI with React Native

The app's interface is built with React Native, ensuring a native feel on both platforms:

return (
  <View style={styles.container}>
    <ScrollView>
      {files.map((item, index) => (
        <ImageItem key={item.id} item={item} userId={user!.id} onRemoveImage={() => onRemoveImage(item, index)} />
      ))}
    </ScrollView>
    <TouchableOpacity onPress={onSelectImages} style={styles.fab}>
      <Ionicons name="camera-outline" size={30} color={'#fff'} />
    </TouchableOpacity>
  </View>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: 'rgb(30 41 59)',
  },
  fab: {
    borderWidth: 1,
    alignItems: 'center',
    justifyContent: 'center',
    width: 70,
    position: 'absolute',
    bottom: 40,
    right: 30,
    height: 70,
    backgroundColor: 'rgb(15 23 42)',
    borderRadius: 100,
  },
});

This code showcases:

  • Efficient list rendering with ScrollView
  • Custom ImageItem component for each file
  • Floating Action Button (FAB) for easy image addition
  • Styled components using React Native's StyleSheet

4. Sophisticated Media Handling

The app handles both images and videos with equal proficiency:

const onSelectImages = async () => {
  try {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsMultipleSelection: true,
      base64: true,
    });

    if (!result.canceled && result.assets && result.assets.length > 0) {
      for (const media of result.assets) {
        if (media.type === 'video') {
          const filePath = `${user!.id}/${new Date().getTime()}.${media.uri.split('.').pop()}`;
          await supabase.storage.from('files').upload(filePath, media.uri);
        } else if (media.type === 'image' && media.base64) {
          const filePath = `${user!.id}/${new Date().getTime()}.${media.uri.split('.').pop()}`;
          await supabase.storage.from('files').upload(filePath, decode(media.base64), { contentType: 'image/png' });
        }
      }
      loadImages();
    }
  } catch (error) {
    console.error('Failed to process images:', error);
    alert('An error occurred while processing the images. Please try again.');
  }
};

This function demonstrates:

  • Use of Expo's ImagePicker for media selection
  • Handling of both images and videos
  • Efficient file naming and storage with Supabase
  • Error handling and user feedback

Overcoming Challenges

Developing Crossplay Photo Share came with its share of challenges. The primary hurdle was ensuring a seamless experience across both iOS and Android, particularly when it came to file handling and permissions. iOS, in particular, required careful handling of photo library access.

Another significant challenge was optimizing the download and upload processes to handle large, high-resolution images and videos efficiently. This required implementing careful memory management and progress tracking to prevent app crashes and maintain a responsive UI.

Future Enhancements

As I continue to develop Crossplay Photo Share, I'm excited about several potential enhancements:

  • Implementing real-time photo syncing using Supabase's real-time capabilities
  • Adding shared albums for collaborative photo collections

Conclusion

Crossplay Photo Share represents more than just a solution to a common problem – it's a testament to the power of React Native and Expo in creating cross-platform solutions. This project has not only sharpened my skills in mobile development but also reinforced my ability to tackle real-world problems through innovative application of technology.

As I continue to refine and expand this application, I'm excited about the potential to push the boundaries of what's possible in cross-platform mobile development. Whether it's implementing more advanced features, enhancing the UI, or further optimizing performance, I'm looking forward to the challenges and learning opportunities ahead.

Feel free to explore the GitHub repository for a closer look at the code.