Uploading file with React Native Expo framework and Firebase is not very easy and straight forward, because, file need to be converted to blob format before uploading to Firebase Cloud Storage. The easiest way to deal with file upload with Expo and Firebase is to use AWS S3.
In this article I will show how to upload file using Expo and Firebase by building simple News feed App. If you are new to AWS S3 then please check my previous tutorial on Getting Started with AWS S3 .
Below is the code for simple News Feed App build with Expo :
import React from 'react'; import { StyleSheet, Text, View, Dimensions, Button, TextInput, FlatList, Image, ActivityIndicator, YellowBox } from 'react-native'; import * as firebase from 'firebase' import { ImagePicker } from 'expo'; import { RNS3 } from 'react-native-aws3'; YellowBox.ignoreWarnings(['Setting a timer']); var _ = require('lodash'); let config = { apiKey: "AIzaSyBtHKZBvCsmqOQhwSFT2QXm-dFV1kO0outy5", authDomain: "imageupload.firebaseapp.com", databaseURL: "https://imageupload.firebaseio.com", projectId: "imageupload" }; let options = { keyPrefix: "Album/", bucket: "awesometzumt", region: "ap-south-2", accessKey: "AKIAJPY233YYH4KTMLP", secretKey: "TS39s/q4+i+zk2QNqa7jO/QvlwTGYBEc5Db5ty5vz", successActionStatus: 201 } firebase.initializeApp(config); const { width, height } = Dimensions.get('window') export default class App extends React.Component { constructor(props) { super(props) this.state = { text: '', photo: null, postList : [], loading: false, loadingPost: true } } componentDidMount(){ this.firebaseRef = firebase.database().ref('newsfeed') this.firebaseRef.on('value', snap => { const newsfeed = snap.val() const newNewsfeed = _.reverse(_.values(newsfeed)); this.setState({ postList: newNewsfeed, loadingPost: false }) }) } async onAddPhoto(){ let result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, aspect: [4, 3], }); if (!result.cancelled) { this.setState({ photo: result.uri }); options["contentType"] = result.type } } onPressPost(){ this.setState({ loading: true}) const _this = this let post = {} post["id"] = firebase.database.ServerValue.TIMESTAMP post["text"] = this.state.text if(this.state.photo){ const uri = this.state.photo const ext = uri.substr(uri.lastIndexOf('.') + 1); const name = Math.round(+new Date()/1000); let file = { name: name+"."+ext, type: "image/"+ext, uri } RNS3.put(file, options).then(response => { if(response.status === 201){ post["photo"] = response.body.postResponse.location firebase.database().ref('newsfeed').push(post) _this.setState({ text: '', photo: null, loading: false}) } }); }else { firebase.database().ref('newsfeed').push(post) _this.setState({ text: '', photo: null, loading: false}) } } render() { if(this.state.loadingPost) return ( <View style={[styles.container,{justifyContent: 'center'}]}> <ActivityIndicator size="large" color="#00ff00" /> </View> ) else return ( <View style={styles.container}> <TextInput style={styles.textInput} onChangeText={(text) => this.setState({text})} value={this.state.text} underlineColorAndroid="transparent" /> { this.state.loading ? (<ActivityIndicator size="large" color="#0000ff" />): (<View style={styles.containerButton}> <Button onPress={this.onAddPhoto.bind(this)} title="Add Photo" color="#841584" /> <Button onPress={this.onPressPost.bind(this)} title="Post" color="#841584" /> </View>) } <FlatList showsVerticalScrollIndicator={false} data={this.state.postList} keyExtractor={item => item.id} renderItem={({ item }) =>{ if(item.photo) return(<View style={{paddingTop: 10, marginBottom: 10}}><Text>{ item.text }</Text> <Image source={{ uri: item.photo }} style={{ width: width*80/100, height: 200 }} /> </View>) return(<View style={{paddingTop: 10, marginBottom: 10}}><Text>{ item.text }</Text></View>) }} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', marginTop: 60 }, containerButton: { flexDirection: "row", justifyContent: "space-between", width: 80*width/100 }, textInput: { height: 40, width: 80*width/100, borderColor: 'gray', borderWidth: 1, borderRadius: 10, paddingLeft: 5, marginBottom: 10 } });
In the above code, first we are setting up configuration option for Firebase and then configuration option for AWS S3. In the componentDidMount we are establishing a real time connection with Firebase so that whenever there is any change in ‘newsfeed’ node of database it is reflected in the app. When user taps ‘Add Photo’ button then Expo ImagePicker is launched and the path of the image on mobile is initialized in ‘photo’ state. Since, we are dealing with photos ‘options[“contentType”]’ will be initialized as ‘image’. ‘contentType’ can be ‘image’, ‘video’, etc.
When user taps ‘Post’ button, we initialize ‘id’ field with a time-stamp and ‘text’ field with a title text entered by the user for the feed photo. Next, we check if user has added photo. If photo is there we extract the extension of image and give it a random unique name. Now, we have name, type and uri of photo which we use with previously intialised AWS options to upload the photo to AWS S3. If upload is successful a ‘201’ response is returned together with image url. So finally we save the id, title and image URL to Firebase db. Because we have real time connection established, the new feed save to database will be automatically shown on the screen.
Video Tutorial :